Если ты хоть раз пробовал упаковать приложение на Node.js в переносимый архив, запустить автотесты без создания временных папок на диске или скормить сгенерированный код напрямую в рантайм, то прекрасно знаешь эту давнюю проблему. Файловый ввод-вывод в Node.js всегда был намертво привязан к физическому диску. Чтобы сымитировать файловую структуру в оперативной памяти или прочитать запакованный архив, разработчикам приходилось идти на сомнительные уловки: подменять глобальный объект fs сторонними библиотеками вроде memfs или mock-fs.
Обезьяньи патчи (monkey-patching) платформенных модулей неизбежно ломаются, как только в проекте появляются нативные аддоны, потоки воркеров или строгий загрузчик ECMAScript-модулей (ESM). Мы уже наблюдали развитие серверной платформы, когда разбирали ключевые этапы эволюции версий Node.js.
Но костылям пришел конец. В линейке Node.js 26 (начиная с экспериментального релиза 26.4.0 и до стабилизации в версиях 26.9.0–26.10.0) появилась фундаментальная подсистема — нативный встроенный модуль node:vfs (Virtual Filesystem).
Его разработку возглавил сооснователь Platformatic и член Node.js TSC Маттео Коллина (Matteo Collina) совместно с Джеймсом Снеллом (James Snell, модуль загрузчика) и Филиппом Дункелем (Philipp Dunkel, ZIP-провайдер и флаги запуска). Давай разберем архитектуру этой подсистемы и выясним, как она устроена «под капотом».
Почему патчинг fs зашел в тупик
Старый подход с переопределением методов fs.readFile или fs.stat окончательно исчерпал себя по трем причинам:
- Многопоточность: каждый поток
worker_threadsживет в отдельном изоляте V8. Мутация объектаfsв одном потоке не видна в соседних без сложной синхронизации через общую память. - Загрузчик ESM: ядро Node.js обращается к файлам через системные вызовы libuv на уровне C++. Подмена методов
fsв JS-коде игнорировалась загрузчиком: вызовimportвсе равно шел на диск и завершался ошибкойERR_MODULE_NOT_FOUND. - Нативные аддоны
.node: для их подключения системный загрузчик (dlopenв Linux/macOS илиLoadLibraryв Windows) требует реальный дескриптор файла, чего виртуальные заглушки в памяти дать не могли.
Архитектура node:vfs: Резолвер и Провайдер
Чтобы устранить эти барьеры, архитектуру node:vfs разделили на два изолированных слоя: Резолвер путей (Resolver) и Провайдер хранилища (Storage Provider).
- Резолвер (Resolver) работает внутри ядра платформы. Он интегрирован с подсистемой путей, правилами поиска зависимостей в
package.json(поляexports,imports), а также хуками загрузчика CJS и ESM. Резолвер отвечает за логику путей, относительные переходы и правила разрешения импортов. - Провайдер хранилища (Storage Provider) — это чистая абстракция ввода-вывода. Провайдер ничего не знает об устройстве модулей Node.js. Его задача сводится к базовым бинарным операциям: прочитать массив байтов по ключу, записать блок данных, вернуть метаданные (размер, время) или отдать список файлов в папке.
Такое разделение позволяет подключать под единый интерфейс файловой системы любые бэкенды: оперативную память, сжатый ZIP-архив, локальную директорию на диске или облачное хранилище.
Изолированные пространства вместо глобальных точек монтирования
В привычных виртуальных ФС используется монтирование по пользовательскому пути вроде /virtual. Создатели node:vfs сознательно отказались от этой концепции. Глобальные пути приводили к коллизиям между независимыми библиотеками и вызывали опасный неявный фолбек на диск, если файл в виртуальной точке отсутствовал.
В node:vfs рантайм сам генерирует изолированное пространство имен для каждой смонтированной системы. На POSIX-системах создается путь вида /dev/null/vfs/0, на Windows — нуль-устройство. Виртуальное дерево полностью изолировано от физического диска, исключая случайные конфликты.
Встроенные провайдеры: память, диск и ZIP-архивы
Node.js 26 включает три штатных провайдера хранилищ:
MemoryProvider(по умолчанию): динамическое файловое дерево в оперативной памяти с поддержкой создания директорий, ссылок и событийfs.watch. Идеально для тестов и генерации кода на лету.RealFSProvider: мост к реальной папке на диске для проброса файлов внутрь виртуального контекста.ZipProvider: монтирование сжатых ZIP-архивов через модульnode:zlib(ZipFile,ZipBuffer) с прозрачной декомпрессией файлов при чтении.
Вот как выглядит создание виртуальной файловой системы в памяти и запуск сгенерированного модуля:
// demo-vfs-memory.mjs — Создание VFS в памяти и импорт модуля
import { createVFS, MemoryProvider } from 'node:vfs';
import fs from 'node:fs/promises';
// 1. Инициализируем VFS с провайдером памяти
const vfs = await createVFS({
provider: new MemoryProvider(),
});
// 2. Монтируем изолированное дерево
const mount = await vfs.mount();
console.log('Точка монтирования:', mount.path);
// 3. Записываем код модуля прямо в память
const moduleCode = `
export function computePayload(data) {
return data.map(item => item * 2);
}
`;
await fs.writeFile(`${mount.path}/processor.js`, moduleCode, 'utf8');
// 4. Импортируем модуль напрямую из памяти без обращения к диску
const { computePayload } = await import(`${mount.path}/processor.js`);
console.log('Результат:', computePayload([10, 20, 30]));
Запуск из архивов и Single Executable Applications (SEA)
Главное прикладное преимущество node:vfs — удобство дистрибуции. Микросервис со всеми зависимостями node_modules и ассетами можно собрать в один ZIP-архив и запускать без предварительной распаковки:

# Запуск приложения на Node.js 26 напрямую из упакованного ZIP-архива
node --experimental-vfs --vfs-load=application-bundle.zip
Кроме того, node:vfs встроен в технологию сборки бинарных файлов Single Executable Applications (SEA). Флаг "useVfs": true в конфиге сборки помещает ресурсы приложения в виртуальную файловую систему, позволяя коду читать файлы через привычный вызов fs.readFileSync('./assets/config.json').
Для нативных C++ аддонов (.node) разработчики задействовали вызов memfd_create в ядре Linux. Рантайм создает анонимный дескриптор в памяти с исполняемыми правами и передает его системному загрузчику dlopen.
Контекст кодинг-агентов и изоляция
Особое значение node:vfs приобретает для автономных AI-агентов. Когда агент экспериментирует с генерацией кода или собирает временные тестовые стенды, запись на физический диск создает задержки и засоряет проект. В виртуальной ФС агент разворачивает окружение за миллисекунды, прогоняет тесты и мгновенно освобождает память. Это напоминает уроки, которые мы извлекли из постмортема ИИ-агента Smith: надежность автоматизации напрямую зависит от управляемости окружения.
Модуль node:vfs является абстракцией хранения, а не изолирующей песочницей (sandbox). Код внутри виртуальной ФС имеет доступ к сетевым сокетам и системным процессам. При включенной модели разрешений Node.js (Permission Model) монтирование VFS требует явного флага --allow-fs-vfs.
Синхронное чтение сжатых файлов из ZipProvider блокирует Event Loop на время распаковки — для высоконагруженных задач используйте потоки. Также опция SEA useVfs пока не поддерживает совместную работу со снимками кучи useSnapshot.
Появление node:vfs дает экосистеме Node.js долгожданный стандарт виртуализации ввода-вывода, избавляя разработчиков от шатких monkey-patch библиотек.
