El caso de un sistema de archivos virtual en Node.js
Node.js está muy ligado al sistema de archivos real. Una capa VFS desbloquearía mejores pruebas, sandboxing y despliegue en edge.

Intenta ejecutar una aplicación Node.js sin un sistema de archivos real y verás lo rápido que se desmorona. require() lee archivos. fs.readFile() lee archivos. Los motores de plantillas leen archivos. Los cargadores de configuración leen archivos. Las librerías de logging escriben archivos. Todo el ecosistema de Node.js asume que existe un sistema de archivos tipo POSIX, que es escribible y que es la forma principal de acceder al código y a los datos.
Esto estaba bien en 2010, cuando Node.js corría en servidores con discos locales. Ahora es cada vez más problemático, porque queremos ejecutar JavaScript en lugares donde el sistema de archivos no existe, es de solo lectura o no debería ser confiable: runtimes edge, sandboxes de WebAssembly, funciones serverless y entornos de desarrollo en el navegador.
Dónde se rompe la suposición del sistema de archivos
Runtimes edge. Cloudflare Workers, Deno Deploy y Vercel Edge Functions ejecutan JavaScript en el edge, en máquinas cercanas al usuario, con infraestructura mínima. Estos entornos a menudo no ofrecen un sistema de archivos escribible, o solo proporcionan un sistema en memoria limitado que no persiste entre peticiones. Los módulos de Node.js que llaman a fs.writeFileSync fallan. Los que usan fs.existsSync para comprobar archivos de configuración se comportan de forma impredecible.
WebAssembly. Ejecutar Node.js dentro de un sandbox de WebAssembly exige mapear las operaciones de archivos al sistema de archivos virtual del runtime de Wasm (normalmente WASI). Este mapeo es imperfecto: los permisos de archivos, los enlaces simbólicos y las rutas específicas de cada plataforma no se traducen bien. Una abstracción VFS haría ese mapeo explícito en lugar de ad hoc.
Pruebas. Hacer pruebas unitarias de código que lee archivos de configuración, carga plantillas o escribe logs obliga a elegir entre mockear el módulo fs (frágil, porque cada librería usa APIs distintas de fs) o crear directorios temporales con fixtures (lento, inestable y deja basura en el disco). Un sistema de archivos virtual permitiría ejecutar las pruebas completamente en memoria, de forma determinista y sin tocar el disco real.
Sandboxing de seguridad. Cuando ejecutas código no confiable (plugins, scripts de usuarios, pasos de build de CI/CD), quieres controlar el acceso al sistema de archivos. Hoy eso requiere sandboxing a nivel de sistema operativo (contenedores, namespaces) o hacer monkey-patching del módulo fs. Un VFS permitiría políticas de archivos granulares: este código puede leer /app pero no /etc, puede escribir en /tmp pero no en /app.
Cómo sería un VFS
Un sistema de archivos virtual no es un concepto nuevo. Los sistemas operativos usan capas VFS desde los años ochenta: el VFS de Linux permite que ext4, NFS y procfs convivan detrás de la misma API. La idea para Node.js es la misma: desacoplar la interfaz del sistema de archivos (fs.readFile, require(), etc.) de su implementación.
// Hypothetical VFS API
import { createVFS, MemoryFS, ReadOnlyFS, OverlayFS } from 'node:vfs';
// In-memory filesystem for testing
const testFS = new MemoryFS({
'/app/config.json': '{"port": 3000}',
'/app/templates/index.html': '<h1>Hello</h1>',
});
// Read-only view of the real filesystem
const readOnly = new ReadOnlyFS('/');
// Overlay: reads from real FS, writes go to memory
const sandbox = new OverlayFS(readOnly, new MemoryFS());
// Run code with a specific filesystem
const vfs = createVFS(sandbox);
vfs.run(() => {
// Inside this context:
// fs.readFileSync('/etc/hosts') → reads real file
// fs.writeFileSync('/tmp/log.txt') → writes to memory overlay
// require('./module') → resolves against the VFS
const app = require('./app');
app.start();
});
La clave está en que el VFS intercepta todos los accesos al sistema de archivos, no solo las llamadas explícitas a fs, sino también require(), import(), __dirname y process.cwd(). Eso es lo que lo hace útil: puedes redirigir todo el acceso al sistema de archivos de una aplicación sin modificarla.
El problema de la resolución de módulos
La relación más profunda entre Node.js y el sistema de archivos es la resolución de módulos. Cuando escribes require('express'), Node.js sube por el árbol de directorios buscando directorios node_modules/express, lee archivos package.json, resuelve enlaces simbólicos y sigue el campo main o exports. Este proceso hace decenas de llamadas al sistema de archivos por cada require.
Yarn PnP (Plug'n'Play) resolvió esto en parte reemplazando node_modules por un archivo manifiesto que mapea nombres de módulos a archivos zip. Es más rápido (sin recorrer directorios) y más determinista (sin sorpresas por hoisting), pero requirió hacer monkey-patching de la resolución de módulos de Node, lo que rompió herramientas que asumían que node_modules existía.
Un VFS bien diseñado convertiría el enfoque de Yarn PnP en una funcionalidad de primera clase. La resolución de módulos pasaría por el VFS, que podría implementar cualquier mapeo: archivos zip, módulos en memoria, URLs remotas o el recorrido tradicional del directorio node_modules. Distintas estrategias para distintos entornos, con la misma API para el código de la aplicación.
Antecedentes
Otros runtimes ya han resuelto variantes de este problema.
- Deno carga los módulos desde URLs por defecto y los guarda en caché en un directorio gestionado. No hay
node_modulesni resolución de módulos basada en el sistema de archivos. El runtime controla dónde y cómo se almacenan los módulos. - Bun usa una caché global de módulos con hardlinks, lo que reduce la carga sobre el sistema de archivos. Su resolución de módulos está muy optimizada en comparación con la de Node.js.
- La interfaz
io/fsde Go (añadida en Go 1.16) define una interfaz de sistema de archivos que implementan el sistema de archivos del SO, los archivos embebidos (embed.FS), los archivos zip y los sistemas en memoria. Las funciones de la librería estándar aceptan la interfazfs.FS, así que se pueden probar sin archivos reales. - La abstracción NIO
FileSystemde Java admite proveedores de sistemas de archivos intercambiables, incluidas implementaciones en memoria para pruebas y sistemas de archivos basados en zip. - El patrón
IFileSystemde .NET se usa mucho en aplicaciones .NET para facilitar las pruebas, aunque es una convención de la comunidad y no una funcionalidad del runtime.
El enfoque de Go es especialmente instructivo. Al definir una interfaz pequeña (Open, Read, Stat) y usarla en toda la librería estándar, Go hizo que la abstracción del sistema de archivos fuera trivial sin romper el código existente. Node.js podría hacer lo mismo: definir una interfaz VFS y migrar gradualmente la librería estándar y el cargador de módulos para usarla.
El reto de la compatibilidad
El mayor obstáculo para un VFS en Node.js no es la implementación, sino el ecosistema. npm tiene más de dos millones de paquetes, y una parte significativa accede al sistema de archivos asumiendo semántica POSIX, rutas reales y directorios escribibles.
Un VFS que rompa los paquetes existentes nacería muerto. Tiene que ser opt-in y retrocompatible: si no creas explícitamente un VFS, todo funciona exactamente igual que hoy. Cuando lo creas, debería interceptar las llamadas al sistema de archivos de forma transparente para que los paquetes bien comportados funcionen sin modificaciones.
"Bien comportados" es el matiz clave. Los paquetes que ejecutan cp o rm, usan addons nativos que llaman directamente a open() de libc, o dependen de /proc u otros sistemas de archivos específicos del SO no funcionarán con un VFS. Es un límite firme: cualquier abstracción pierde detalles cuando el código se salta la capa y accede al sistema subyacente.
Qué se propone realmente
La discusión sobre un VFS en Node.js no es teórica: hay propuestas concretas en el issue tracker de Node.js. La dirección actual gira en torno a algunas ideas clave.
Primero, una interfaz FileSystemProvider a la que el módulo fs delega. El proveedor por defecto es el sistema de archivos real. Los proveedores personalizados implementan la misma interfaz para sistemas en memoria, de solo lectura, overlay o remotos.
Segundo, la integración con el cargador de módulos. require() e import() resolverían los módulos a través del VFS, lo que habilitaría estrategias de resolución que no dependen de directorios node_modules.
Tercero, una capa de políticas. El VFS podría aplicar controles de acceso, impidiendo que el código lea o escriba rutas fuera de un ámbito definido. Eso daría a Node.js una capacidad de sandboxing integrada similar a los flags --allow-read y --allow-write de Deno.
No sabemos si esto llegará en Node.js 24, en la 26 o nunca. Depende de la capacidad del equipo de Node.js y de las ganas de la comunidad de aceptar un cambio que rompe la forma en que funciona el acceso al sistema de archivos. Pero la presión es real: los runtimes edge crecen, se multiplican los objetivos de despliegue WebAssembly y la brecha entre el 'JavaScript en todas partes' y 'Node.js específicamente en servidores con sistemas de archivos reales' se está convirtiendo en una desventaja competitiva.
Qué puedes hacer hoy
No necesitas esperar a un VFS en el runtime para beneficiarte de la abstracción del sistema de archivos. En código nuevo, envuelve el acceso a archivos detrás de una interfaz. En lugar de llamar directamente a fs.readFile, crea una interfaz FileStore de la que dependa tu código. Impleméntala con el sistema de archivos real para producción y con un mapa en memoria para las pruebas. Es inversión de dependencias estándar: poco vistosa, eficaz y compatible con cualquier runtime.
Para código existente, librerías como memfs y unionfs ofrecen implementaciones en memoria y overlay que parchean el módulo fs de Node. No son perfectas (los addons nativos y los procesos hijos las saltan), pero funcionan para código JavaScript puro y hacen las pruebas mucho más fáciles.
El sistema de archivos es la última gran pieza de la infraestructura de Node.js que no tiene un límite de abstracción limpio. Los streams tienen interfaces. HTTP tiene interfaces. Incluso el cargador de módulos tiene hooks. Cuando el sistema de archivos reciba el mismo tratamiento, Node.js será un runtime realmente portable y no uno pensado solo para servidores. Vale la pena empujar en esa dirección.


