Кастомные файловые системы: inputFileSystem, outputFileSystem

inputFileSystem и outputFileSystem являются ключевыми абстракциями в архитектуре Webpack, через которые происходит вся работа с файловой системой при компиляции. Эти интерфейсы позволяют Webpack быть независимым от конкретной реализации файлового хранилища, обеспечивая возможность подмены источника и назначения данных: от реального диска до виртуальной памяти, сетевых хранилищ или специализированных кэширующих слоёв.

Внутри экземпляра компилятора Webpack (Compiler) файловая система не используется напрямую через fs Node.js. Вместо этого применяется абстракция, которая позволяет унифицировать операции чтения и записи:

  • compiler.inputFileSystem — слой чтения исходных файлов и зависимостей
  • compiler.outputFileSystem — слой записи собранных ассетов

Такое разделение критично для процессов:

  • сборки в watch-режиме
  • работы dev-серверов
  • виртуальной компиляции без записи на диск
  • интеграции с нестандартными окружениями (CI, облачные среды, in-memory сборка)

Webpack ожидает, что эти объекты реализуют API, совместимый с Node.js fs (частично), включая методы readFile, stat, readdir, readlink и другие, используемые резолвером и загрузчиком модулей.


inputFileSystem: чтение исходного кода и зависимостей

inputFileSystem используется на этапе резолвинга модулей и анализа зависимостей. Когда Webpack встречает импорт или require, он не обращается напрямую к диску, а вызывает методы абстрактной файловой системы.

Ключевые сценарии использования:

  • загрузка исходных файлов (.js, .ts, .css)
  • чтение конфигурационных файлов
  • обход директорий при динамических импортax
  • проверка существования файлов

Типичный набор методов:

  • readFile(path, callback)
  • stat(path, callback)
  • readdir(path, callback)
  • readlink(path, callback)
  • lstat(path, callback)

В реальной конфигурации Webpack часто использует обёртку CachedInputFileSystem, которая добавляет слой кэширования поверх базовой FS:

const fs = require("fs");
const CachedInputFileSystem = require("enhanced-resolve").CachedInputFileSystem;

compiler.inputFileSystem = new CachedInputFileSystem(fs, 60000);

Здесь происходит важный момент: Webpack ускоряет резолвинг за счёт кеширования результатов чтения файлов и статусов, снижая количество обращений к диску.

В режиме watch это особенно важно, поскольку один и тот же модуль может проверяться многократно при пересборке графа зависимостей.


outputFileSystem: запись результата компиляции

outputFileSystem отвечает за сохранение итоговых ассетов, которые генерирует компилятор после прохождения всех стадий:

  • загрузчики (loaders)
  • оптимизация
  • минификация
  • чанкинг
  • эмиссия файлов

По умолчанию в Node.js-среде используется стандартный fs, но в dev-сценариях почти всегда происходит подмена.

Методы, которые ожидаются от outputFileSystem:

  • writeFile(path, data, callback)
  • mkdirp(path, options, callback) или mkdir(path, callback)
  • stat(path, callback)
  • unlink(path, callback)
  • rmdir(path, callback)

Важный момент: Webpack сам по себе не обязан записывать файлы на диск. Он может работать полностью в памяти, а запись становится задачей внешнего слоя (например, dev middleware).


Использование in-memory файловой системы

В development-среде часто применяется виртуальная файловая система. Один из распространённых вариантов — memfs:

const { Volume } = require("memfs");

const memoryFs = new Volume();

compiler.outputFileSystem = memoryFs;

Это позволяет:

  • хранить результаты сборки в памяти
  • мгновенно отдавать ассеты через dev-server
  • избегать операций записи на диск
  • ускорять hot-reload сценарии

webpack-dev-middleware и webpack-dev-server активно используют этот подход, создавая слой между компилятором и HTTP-ответами.


Связь с webpack-dev-middleware и dev-server

В связке с middleware файловая система становится ключевым элементом потока данных:

  1. Webpack компилирует код
  2. Результат записывается в outputFileSystem
  3. Middleware читает ассеты из этой FS
  4. HTTP сервер отдаёт их клиенту

При этом физический диск может вообще не участвовать.

Типичная архитектура:

  • compiler.outputFileSystem = memfs
  • middleware читает из compiler.outputFileSystem

Это обеспечивает минимальную задержку между изменением кода и обновлением браузера.


Подмена файловой системы в кастомных сценариях

Webpack позволяет полностью заменить файловую систему, что используется в сложных интеграциях:

  • виртуальные проекты (монорепозитории с динамической генерацией файлов)
  • удалённые сборки (S3, HTTP FS)
  • sandboxed environments
  • тестовые окружения

Пример кастомной input FS:

class CustomInputFS {
  readFile(path, callback) {
    // чтение из базы данных или API
  }

  stat(path, callback) {
    // эмуляция структуры файлов
  }

  readdir(path, callback) {
    // генерация виртуальной структуры
  }
}

compiler.inputFileSystem = new CustomInputFS();

Такой подход позволяет Webpack работать с источниками, которые не существуют как физические файлы.


CachedInputFileSystem и производительность

Одним из самых важных компонентов является CachedInputFileSystem. Он уменьшает нагрузку на диск за счёт:

  • кеширования результатов stat
  • кеширования содержимого файлов
  • хранения метаданных о структуре директорий

Механика:

  • первый запрос уходит в реальную FS
  • результат сохраняется в памяти
  • последующие обращения обслуживаются из кеша

Это особенно критично при больших проектах, где число модулей достигает десятков тысяч.


Влияние на watch-режим и пересборку

В режиме наблюдения (watch) Webpack постоянно проверяет изменения файлов. Здесь inputFileSystem играет роль источника истины.

Процесс выглядит так:

  • watcher отслеживает изменения
  • при событии пересчитывается граф зависимостей
  • inputFileSystem.stat проверяет изменения mtime
  • изменённые модули пересобираются

Если используется кэшированная FS, сравнение метаданных происходит быстрее, что уменьшает задержку пересборки.


Интеграция с Resolver и Dependency Graph

Модуль enhanced-resolve тесно связан с inputFileSystem. Он использует FS для:

  • поиска модулей по node_modules
  • обработки alias
  • разрешения extensions
  • анализа symlink-структур

Каждый вызов резолвера опирается на:

  • stat
  • readlink
  • readdir

Таким образом, любая кастомизация FS напрямую влияет на процесс построения dependency graph.


Особенности совместимости API

Webpack не требует полного соответствия Node.js fs, но ожидает:

  • callback-based API (не Promises по умолчанию)
  • стандартные ошибки Node (ENOENT, EACCES)
  • поддержку путей в формате POSIX/Windows

При создании кастомной FS важно учитывать:

  • синхронизация путей
  • корректная обработка отсутствующих файлов
  • стабильность stat-метаданных (mtime, size)

Нарушение этих контрактов может привести к некорректному кешированию и “фантомным” пересборкам.


Практическое применение в тестировании

В тестовых сценариях Webpack часто запускается с виртуальной FS:

  • ускорение тестов
  • отсутствие побочных эффектов на диске
  • детерминированность сборки

Пример:

const { Volume } = require("memfs");

const fs = new Volume();

compiler.inputFileSystem = fs;
compiler.outputFileSystem = fs;

Это позволяет полностью изолировать сборку.


Особенности поведения outputFileSystem в разных режимах

В production-сборке outputFileSystem чаще всего:

  • указывает на реальную файловую систему (fs)
  • используется только для финальной записи bundle

В development:

  • заменяется на memory FS
  • используется совместно с middleware
  • не сохраняет состояние между перезапусками

Это разделение позволяет Webpack адаптироваться к разным стратегиям доставки кода без изменения core логики.


Взаимодействие с Compiler Hooks

Файловые системы тесно связаны с lifecycle hooks:

  • beforeRun
  • run
  • watchRun
  • emit
  • afterEmit

На этапе emit происходит финальная запись ассетов в outputFileSystem. Здесь важно, чтобы FS корректно реализовывала методы записи и создания директорий.

Ошибки на этом этапе часто связаны с:

  • отсутствием mkdir
  • неправильной обработкой вложенных путей
  • асинхронностью операций

Значение абстракции файловой системы в Webpack

Разделение input и output FS превращает Webpack в полностью абстрагированный компилятор, независимый от среды выполнения. Это даёт возможность:

  • запускать сборку в браузерных окружениях (через bundling самого Webpack)
  • использовать облачные файловые системы
  • строить промежуточные кэширующие слои
  • интегрировать сборку в нестандартные пайплайны

Фактически, файловая система становится не инфраструктурным ограничением, а частью конфигурации компиляции, влияющей на производительность, архитектуру и поведение сборочного процесса.