@@ -46,6 +46,11 @@ callback-based, and promise-based file system methods that mirror the
4646shape of the [ ` node:fs ` ] [ ] API. All paths are POSIX-style and absolute
4747(starting with ` / ` ).
4848
49+ By default, the file tree is private to the VFS instance. To expose
50+ it through the global ` node:fs ` module, ` require() ` , and ` import ` ,
51+ call [ ` vfs.mount(prefix) ` ] [ ] ; call [ ` vfs.unmount() ` ] [ ] (or rely on a
52+ ` using ` declaration) to detach again.
53+
4954## ` vfs.create([provider][, options]) `
5055
5156<!-- YAML
@@ -92,6 +97,126 @@ added: REPLACEME
9297 * ` emitExperimentalWarning ` {boolean} Whether to emit the experimental
9398 warning. ** Default:** ` true ` .
9499
100+ ### ` vfs.mount(prefix) `
101+
102+ <!-- YAML
103+ added: REPLACEME
104+ -->
105+
106+ * ` prefix ` {string} The path prefix where the VFS will be mounted.
107+ * Returns: {VirtualFileSystem} The VFS instance, for chaining or ` using ` .
108+
109+ Mounts the virtual file system at the specified path prefix. After
110+ mounting, files in the VFS can be accessed through the ` node:fs `
111+ module — and resolved through ` require() ` and ` import ` — using paths
112+ that start with the prefix.
113+
114+ If a real file-system path already exists at the mount prefix, the
115+ VFS ** shadows** that path: every operation against a path under the
116+ mount point is directed to the VFS until the VFS is unmounted.
117+
118+ ``` cjs
119+ const vfs = require (' node:vfs' );
120+ const fs = require (' node:fs' );
121+
122+ const myVfs = vfs .create ();
123+ myVfs .writeFileSync (' /data.txt' , ' Hello' );
124+ myVfs .mount (' /virtual' );
125+
126+ fs .readFileSync (' /virtual/data.txt' , ' utf8' ); // 'Hello'
127+ ```
128+
129+ Each ` VirtualFileSystem ` instance may be mounted at most once at a
130+ time. Attempting to mount an already-mounted instance throws
131+ ` ERR_INVALID_STATE ` . Mounting two instances at overlapping prefixes
132+ (e.g., ` /virtual ` and ` /virtual/sub ` ) also throws ` ERR_INVALID_STATE ` .
133+
134+ The VFS supports the [ Explicit Resource Management] [ ] proposal. Use
135+ a ` using ` declaration to unmount automatically when leaving scope:
136+
137+ ``` cjs
138+ const vfs = require (' node:vfs' );
139+ const fs = require (' node:fs' );
140+
141+ {
142+ using myVfs = vfs .create ();
143+ myVfs .writeFileSync (' /data.txt' , ' Hello' );
144+ myVfs .mount (' /virtual' );
145+
146+ fs .readFileSync (' /virtual/data.txt' , ' utf8' ); // 'Hello'
147+ } // VFS is automatically unmounted here
148+
149+ fs .existsSync (' /virtual/data.txt' ); // false
150+ ```
151+
152+ ### ` vfs.unmount() `
153+
154+ <!-- YAML
155+ added: REPLACEME
156+ -->
157+
158+ Unmounts the virtual file system. After unmounting, virtual files
159+ are no longer reachable through ` node:fs ` , ` require() ` , or ` import ` .
160+ The same instance may be mounted again, at the same or a different
161+ prefix, by calling ` mount() ` .
162+
163+ This method is idempotent: calling ` unmount() ` on a VFS that is not
164+ currently mounted has no effect.
165+
166+ ### ` vfs.mounted `
167+
168+ <!-- YAML
169+ added: REPLACEME
170+ -->
171+
172+ * {boolean}
173+
174+ ` true ` while the VFS is mounted; ` false ` otherwise.
175+
176+ ### ` vfs.mountPoint `
177+
178+ <!-- YAML
179+ added: REPLACEME
180+ -->
181+
182+ * {string | null}
183+
184+ The current mount-point path as an absolute string, or ` null ` when
185+ the VFS is not mounted.
186+
187+ ### ` vfs.layerId `
188+
189+ <!-- YAML
190+ added: REPLACEME
191+ -->
192+
193+ * {number}
194+
195+ A per-process monotonically increasing identifier assigned at
196+ construction. The id is stable across ` mount() ` / ` unmount() ` cycles
197+ for the lifetime of the instance, and is independent of the order in
198+ which VFS layers are mounted.
199+
200+ The layer id is the building block for cache scoping (see
201+ [ Module loader integration] [ ] ):
202+
203+ * it surfaces in ` import.meta.url ` for ES modules loaded from this
204+ VFS, as a ` ?vfs-layer=<id> ` search parameter, so that the cascaded
205+ loader's caches can be scoped per VFS;
206+ * it appears in the ` NODE_DEBUG=vfs ` output for ` register ` and
207+ ` deregister ` events;
208+ * it appears in the ` ERR_INVALID_STATE ` error message thrown when two
209+ VFS instances try to mount at overlapping prefixes.
210+
211+ ``` cjs
212+ const vfs = require (' node:vfs' );
213+
214+ const a = vfs .create ();
215+ const b = vfs .create ();
216+ console .log (a .layerId ); // e.g. 0
217+ console .log (b .layerId ); // a.layerId + 1
218+ ```
219+
95220### ` vfs.provider `
96221
97222<!-- YAML
@@ -180,6 +305,69 @@ The promise namespace mirrors `fs.promises` and includes `readFile`,
180305` access ` , ` rm ` , ` truncate ` , ` link ` , ` mkdtemp ` , ` chmod ` , ` chown ` , ` lchown ` ,
181306` utimes ` , ` lutimes ` , ` open ` , ` lchmod ` , and ` watch ` .
182307
308+ ## Module loader integration
309+
310+ Once a ` VirtualFileSystem ` is mounted, paths under the mount prefix
311+ participate in module resolution and loading. Both
312+ ` require() ` / ` require.resolve() ` (CommonJS) and ` import ` /
313+ ` import.meta.resolve() ` (ECMAScript modules) consult the VFS through
314+ the same toggleable hooks that ` node:fs ` uses, so files served from
315+ the VFS are first-class modules: ` package.json ` is honoured,
316+ extensionless files are sniffed for Wasm vs. JavaScript, conditional
317+ ` exports ` / ` imports ` work, and so on.
318+
319+ ``` cjs
320+ const vfs = require (' node:vfs' );
321+
322+ const myVfs = vfs .create ();
323+ myVfs .mkdirSync (' /lib' );
324+ myVfs .writeFileSync (' /lib/greet.js' , ' module.exports = () => "hi";' );
325+ myVfs .writeFileSync (
326+ ' /lib/package.json' , ' {"main": "./greet.js"}' );
327+ myVfs .mount (' /virtual' );
328+
329+ const greet = require (' /virtual/lib' );
330+ console .log (greet ()); // 'hi'
331+
332+ myVfs .unmount ();
333+ ```
334+
335+ ### Cache scoping and ` import.meta.url `
336+
337+ Module loaders maintain caches that survive the lifetime of any
338+ single VFS. To keep entries from leaking once a VFS is unmounted
339+ without invalidating unrelated real-fs imports, two mechanisms are
340+ combined:
341+
342+ * ** CommonJS caches** (` require.cache ` , the internal stat and
343+ realpath caches, and the ` package.json ` caches) are filtered on
344+ ` unmount() ` : entries whose absolute filename would be claimed by
345+ the VFS going away are deleted. ` __filename ` and ` module.filename `
346+ are unchanged - they remain plain absolute paths.
347+
348+ * ** ECMAScript module URLs** are tagged at resolve time. When the
349+ resolver determines that a path belongs to a mounted VFS, it
350+ appends ` ?vfs-layer=<id> ` (where ` <id> ` is the owning instance's
351+ [ ` vfs.layerId ` ] [ ] ) to the resolved URL. The tag therefore appears
352+ in ` import.meta.url ` and in cache keys, and on ` unmount() ` the
353+ cascaded loader's caches drop just the entries that carry the tag
354+ for the unmounting layer.
355+
356+ ``` mjs
357+ // inside /virtual/lib/greet.mjs after the VFS above is mounted
358+ console .log (import .meta.url);
359+ // e.g. 'file:///virtual/lib/greet.mjs?vfs-layer=0'
360+ ` ` `
361+
362+ User code that compares ` import .meta.url` literally should account
363+ for the search parameter; use ` new URL(import.meta.url).pathname` or
364+ ` fileURLToPath()` to obtain the underlying path.
365+
366+ Mounting and unmounting do not invalidate ESM modules that are
367+ already executing. As with any other module-system teardown,
368+ unmounting a VFS while the import graph below it is still loading is
369+ the caller's responsibility to avoid.
370+
183371## Class: ` VirtualProvider`
184372
185373<!-- YAML
@@ -302,9 +490,14 @@ fields use synthetic but stable values:
302490* ` blocks` is ` Math .ceil (size / 512 )` .
303491* Times default to the moment the entry was created/last modified.
304492
493+ [Explicit Resource Management]: https://github.com/tc39/proposal-explicit-resource-management
494+ [Module loader integration]: #module-loader-integration
305495[` MemoryProvider` ]: #class-memoryprovider
306496[` VirtualFileSystem` ]: #class-virtualfilesystem
307497[` VirtualProvider` ]: #class-virtualprovider
308498[` fs .BigIntStats ` ]: fs.md#class-fsbigintstats
309499[` fs .Stats ` ]: fs.md#class-fsstats
310500[` node: fs` ]: fs.md
501+ [` vfs .layerId ` ]: #vfslayerid
502+ [` vfs .mount (prefix)` ]: #vfsmountprefix
503+ [` vfs .unmount ()` ]: #vfsunmount
0 commit comments