Skip to content

Commit dd13aff

Browse files
committed
vfs: replace fs patching with toggleable toRealPath for module loading
ESM resolve.js captures `realpathSync` via destructuring at import time, so patching `fs.realpathSync` later has no effect on ESM resolution. Replace the direct `realpathSync` call in `finalizeResolution()` with the shared `toRealPath()` from helpers, which dispatches to a VFS-aware override at runtime. Split `installHooks()` into `installModuleHooks()` (Module._stat, toRealPath override, ESM hooks) and `installFsPatches()` (fs.* patches for user code transparency) for clearer separation of concerns.
1 parent 4f8e6de commit dd13aff

File tree

3 files changed

+87
-30
lines changed

3 files changed

+87
-30
lines changed

lib/internal/modules/esm/resolve.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ const {
99
ObjectPrototypeHasOwnProperty,
1010
RegExpPrototypeExec,
1111
RegExpPrototypeSymbolReplace,
12-
SafeMap,
1312
SafeSet,
1413
String,
1514
StringPrototypeEndsWith,
@@ -23,9 +22,8 @@ const {
2322
encodeURIComponent,
2423
} = primordials;
2524
const assert = require('internal/assert');
26-
const internalFS = require('internal/fs/utils');
2725
const { BuiltinModule } = require('internal/bootstrap/realm');
28-
const { realpathSync } = require('fs');
26+
const { toRealPath } = require('internal/modules/helpers');
2927
const { getOptionValue } = require('internal/options');
3028
// Do not eagerly grab .manifest, it may be in TDZ
3129
const { sep, posix: { relative: relativePosixPath }, resolve } = require('path');
@@ -149,8 +147,6 @@ function emitLegacyIndexDeprecation(url, path, pkgPath, base, main) {
149147
}
150148
}
151149

152-
const realpathCache = new SafeMap();
153-
154150
const legacyMainResolveExtensions = [
155151
'',
156152
'.js',
@@ -273,9 +269,7 @@ function finalizeResolution(resolved, base, preserveSymlinks) {
273269
}
274270

275271
if (!preserveSymlinks) {
276-
const real = realpathSync(path, {
277-
[internalFS.realpathCacheKey]: realpathCache,
278-
});
272+
const real = toRealPath(path);
279273
const { search, hash } = resolved;
280274
resolved =
281275
pathToFileURL(real + (StringPrototypeEndsWith(path, sep) ? '/' : ''));

lib/internal/modules/helpers.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,43 @@ let debug = require('internal/util/debuglog').debuglog('module', (fn) => {
5555
* @type {Map<string, string>}
5656
*/
5757
const realpathCache = new SafeMap();
58+
59+
let customToRealPath = null;
60+
5861
/**
59-
* Resolves the path of a given `require` specifier, following symlinks.
62+
* Default implementation: resolves the path following symlinks.
6063
* @param {string} requestPath The `require` specifier
6164
* @returns {string}
6265
*/
63-
function toRealPath(requestPath) {
66+
function defaultToRealPath(requestPath) {
6467
return fs.realpathSync(requestPath, {
6568
[internalFS.realpathCacheKey]: realpathCache,
6669
});
6770
}
6871

72+
/**
73+
* Resolves the path of a given `require` specifier, following symlinks.
74+
* When a custom override is set (e.g. for VFS), it is called first.
75+
* @param {string} requestPath The `require` specifier
76+
* @returns {string}
77+
*/
78+
function toRealPath(requestPath) {
79+
if (customToRealPath !== null) {
80+
return customToRealPath(requestPath, defaultToRealPath);
81+
}
82+
return defaultToRealPath(requestPath);
83+
}
84+
85+
/**
86+
* Set a custom toRealPath override (e.g. for VFS-aware resolution).
87+
* The function receives (requestPath, defaultToRealPath) and should
88+
* return the resolved real path.
89+
* @param {Function|null} fn The custom function, or null to reset
90+
*/
91+
function setCustomToRealPath(fn) {
92+
customToRealPath = fn;
93+
}
94+
6995
/** @type {Set<string>} */
7096
let cjsConditions;
7197
/** @type {string[]} */
@@ -512,6 +538,7 @@ module.exports = {
512538
makeRequireFunction,
513539
normalizeReferrerURL,
514540
stringify,
541+
setCustomToRealPath,
515542
stripBOM,
516543
toRealPath,
517544
hasStartedUserCJSExecution() {

lib/internal/vfs/module_hooks.js

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ const {
1111
const path = require('path');
1212
const { dirname, extname, isAbsolute, resolve } = path;
1313
const pathPosix = path.posix;
14-
const { extensionFormatMap } = require('internal/modules/esm/formats');
14+
// Inline format map to avoid dependency on internal/modules/esm/formats,
15+
// which may not be available during early bootstrap (e.g. SEA).
16+
// VFS defaults .js to 'commonjs' (see getFormatFromExtension below).
17+
const extensionFormatMap = {
18+
'__proto__': null, '.cjs': 'commonjs', '.mjs': 'module',
19+
'.json': 'json', '.wasm': 'wasm',
20+
};
1521

1622
/**
1723
* Normalizes a VFS path. Uses POSIX normalization for Unix-style paths (starting with /)
@@ -478,24 +484,15 @@ function vfsLoadHook(url, context, nextLoad) {
478484
}
479485

480486
/**
481-
* Install hooks into Module._stat and various fs functions.
482-
* Note: fs and internal modules are required here (not at top level) to avoid
483-
* circular dependencies during bootstrap. This module may be loaded early.
487+
* Install module loading hooks (Module._stat, toRealPath override, ESM hooks).
488+
* These make CJS/ESM module resolution VFS-aware without patching fs globally.
484489
*/
485-
function installHooks() {
486-
if (hooksInstalled) {
487-
return;
488-
}
489-
490+
function installModuleHooks() {
490491
const Module = require('internal/modules/cjs/loader').Module;
491-
const fs = require('fs');
492+
const { setCustomToRealPath } = require('internal/modules/helpers');
492493

493-
// Save originals
494+
// Save original Module._stat
494495
originalStat = Module._stat;
495-
originalReadFileSync = fs.readFileSync;
496-
originalRealpathSync = fs.realpathSync;
497-
originalLstatSync = fs.lstatSync;
498-
originalStatSync = fs.statSync;
499496

500497
// Override Module._stat
501498
// This uses the setter which emits an experimental warning, but that's acceptable
@@ -508,6 +505,37 @@ function installHooks() {
508505
return originalStat(filename);
509506
};
510507

508+
// Set VFS-aware toRealPath override for internal module resolution.
509+
// This is called at runtime (not captured at import time), so it works
510+
// correctly with both CJS toRealPath and ESM finalizeResolution.
511+
setCustomToRealPath(function vfsAwareToRealPath(requestPath, defaultFn) {
512+
const vfsResult = findVFSForRealpath(requestPath);
513+
if (vfsResult !== null) {
514+
return vfsResult.realpath;
515+
}
516+
return defaultFn(requestPath);
517+
});
518+
519+
// Register ESM hooks using Module.registerHooks
520+
Module.registerHooks({
521+
resolve: vfsResolveHook,
522+
load: vfsLoadHook,
523+
});
524+
}
525+
526+
/**
527+
* Install fs patches for user code transparency.
528+
* These make fs.readFileSync('/vfs/path'), fs.statSync, etc. work for user code.
529+
*/
530+
function installFsPatches() {
531+
const fs = require('fs');
532+
533+
// Save originals
534+
originalReadFileSync = fs.readFileSync;
535+
originalRealpathSync = fs.realpathSync;
536+
originalLstatSync = fs.lstatSync;
537+
originalStatSync = fs.statSync;
538+
511539
// Override fs.readFileSync
512540
// We need to be careful to only intercept when VFS should handle the path
513541
fs.readFileSync = function readFileSync(path, options) {
@@ -687,12 +715,20 @@ function installHooks() {
687715
}
688716
return FunctionPrototypeCall(originalPromisesWatch, fsPromises, filename, options);
689717
};
718+
}
690719

691-
// Register ESM hooks using Module.registerHooks
692-
Module.registerHooks({
693-
resolve: vfsResolveHook,
694-
load: vfsLoadHook,
695-
});
720+
/**
721+
* Install all VFS hooks: module loading hooks and fs patches.
722+
* Note: fs and internal modules are required here (not at top level) to avoid
723+
* circular dependencies during bootstrap. This module may be loaded early.
724+
*/
725+
function installHooks() {
726+
if (hooksInstalled) {
727+
return;
728+
}
729+
730+
installModuleHooks();
731+
installFsPatches();
696732

697733
hooksInstalled = true;
698734
}

0 commit comments

Comments
 (0)