From 8c31f00f304d4e2a9ebdce7bf5aa785a14033138 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 30 Sep 2026 16:12:27 +0200 Subject: [PATCH] vfs: allow imports without the node: prefix The experimental VFS module previously required the node: prefix. Allow both vfs and node:vfs in CommonJS and ESM while keeping --experimental-vfs gating unchanged. Assisted-by: Pi Signed-off-by: Matteo Collina --- lib/internal/bootstrap/realm.js | 1 - .../test-module-builtin-experimental.js | 2 +- test/parallel/test-vfs-flag.js | 70 ++++++++++++------- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/lib/internal/bootstrap/realm.js b/lib/internal/bootstrap/realm.js index 92314a873d3e..c5eb97d6b18e 100644 --- a/lib/internal/bootstrap/realm.js +++ b/lib/internal/bootstrap/realm.js @@ -133,7 +133,6 @@ const schemelessBlockList = new SafeSet([ 'quic', 'test', 'test/reporters', - 'vfs', ]); // Modules that will only be enabled at run time. const experimentalModuleList = new SafeSet([ diff --git a/test/parallel/test-module-builtin-experimental.js b/test/parallel/test-module-builtin-experimental.js index 2a3d75e23c1b..9f6ff35080d9 100644 --- a/test/parallel/test-module-builtin-experimental.js +++ b/test/parallel/test-module-builtin-experimental.js @@ -8,7 +8,6 @@ const experimentalBuiltins = [ ['bench/reporters', '--experimental-bench', true], ['dtls', '--experimental-dtls', common.hasDtls], ['quic', '--experimental-quic', common.hasQuic], - ['vfs', '--experimental-vfs', true], ].filter(([, , available]) => available); for (const [id, flag] of experimentalBuiltins) { @@ -25,6 +24,7 @@ for (const [id, flag] of experimentalBuiltins) { } const schemelessExperimentalBuiltins = [ + ['vfs', '--experimental-vfs'], ['stream/iter', '--experimental-stream-iter'], ['zlib/iter', '--experimental-stream-iter'], ]; diff --git a/test/parallel/test-vfs-flag.js b/test/parallel/test-vfs-flag.js index 14e34c9e8be9..9ca1050a9509 100644 --- a/test/parallel/test-vfs-flag.js +++ b/test/parallel/test-vfs-flag.js @@ -1,34 +1,45 @@ 'use strict'; -// node:vfs is gated behind --experimental-vfs. Without the flag the -// module is not exposed; bare `vfs` (without the node: scheme) is also -// blocked. +// Both vfs and node:vfs are gated behind --experimental-vfs. require('../common'); const { spawnSyncAndAssert } = require('../common/child_process'); -// Without the flag, requiring node:vfs throws ERR_UNKNOWN_BUILTIN_MODULE. -{ - spawnSyncAndAssert(process.execPath, [ - '-e', 'require("node:vfs")', - ], { status: 1, stderr: /ERR_UNKNOWN_BUILTIN_MODULE/ }); -} +// Without the flag, or when explicitly disabled, neither specifier is exposed. +for (const flags of [[], ['--no-experimental-vfs']]) { + for (const [id, requireError, importError] of [ + ['vfs', 'MODULE_NOT_FOUND', 'ERR_MODULE_NOT_FOUND'], + ['node:vfs', 'ERR_UNKNOWN_BUILTIN_MODULE', 'ERR_UNKNOWN_BUILTIN_MODULE'], + ]) { + spawnSyncAndAssert(process.execPath, [ + ...flags, + '-e', + `const assert = require('node:assert'); + const { isBuiltin } = require('node:module'); + assert.strictEqual(isBuiltin('${id}'), false); + assert.strictEqual(process.getBuiltinModule('${id}'), undefined); + assert.throws(() => require('${id}'), { code: '${requireError}' });`, + ], { status: 0 }); -// Without the flag, importing node:vfs throws ERR_UNKNOWN_BUILTIN_MODULE. -{ - spawnSyncAndAssert(process.execPath, [ - '--input-type=module', - '-e', 'import("node:vfs").catch((e) => { console.error(e.code); process.exit(1); });', - ], { - status: 1, - stderr: /ERR_UNKNOWN_BUILTIN_MODULE/, - }); + spawnSyncAndAssert(process.execPath, [ + ...flags, + '--input-type=module', + '-e', + `import assert from 'node:assert'; + await assert.rejects(import('${id}'), { code: '${importError}' });`, + ], { status: 0 }); + } } -// With the flag, node:vfs loads and works. -{ +// With the flag, both specifiers resolve to the same CommonJS exports. +for (const id of ['vfs', 'node:vfs']) { const script = - 'const v = require("node:vfs");' + + 'const assert = require("node:assert");' + + `const v = require('${id}');` + + 'assert.strictEqual(v, require("node:vfs"));' + + `assert.strictEqual(require.resolve('${id}'), '${id}');` + + `assert.strictEqual(require('node:module').isBuiltin('${id}'), true);` + + `assert.strictEqual(process.getBuiltinModule('${id}'), v);` + 'const x = v.create();' + 'x.writeFileSync("/x", "hi");' + 'console.log(x.readFileSync("/x", "utf8"));'; @@ -38,15 +49,22 @@ const { spawnSyncAndAssert } = require('../common/child_process'); }); } -// Bare `vfs` (no node: scheme) is always blocked. +// Static and dynamic ESM imports share the same module and named exports. { spawnSyncAndAssert(process.execPath, [ '--experimental-vfs', - '-e', "require('vfs')", - ], { status: 1, stderr: /Cannot find module 'vfs'/ }); + '--input-type=module', + '-e', + `import assert from 'node:assert'; + import vfs, { create } from 'vfs'; + import nodeVfs, { create as nodeCreate } from 'node:vfs'; + assert.strictEqual(vfs, nodeVfs); + assert.strictEqual(create, nodeCreate); + assert.strictEqual(await import('vfs'), await import('node:vfs'));`, + ], { status: 0 }); } -// Module.builtinModules reflects whether --experimental-vfs is active. +// Module.builtinModules lists the bare name when --experimental-vfs is active. for (const [flag, expected] of [ ['--experimental-vfs', 'true\n'], ['--no-experimental-vfs', 'false\n'], @@ -54,6 +72,6 @@ for (const [flag, expected] of [ spawnSyncAndAssert(process.execPath, [ flag, '-p', - 'require("node:module").builtinModules.includes("node:vfs")', + 'require("node:module").builtinModules.includes("vfs")', ], { stdout: expected, stderr: '' }); }