Skip to content

Commit d181b76

Browse files
joyeecheungtargos
authored andcommitted
bootstrap: make CJS loader snapshotable
This patch makes the top-level access to runtime states in the CJS loader lazy, and move the side-effects into a initializeCJS() function that gets called during pre-execution. As a result the CJS loader can be included into the built-in snapshot. PR-URL: #45849 Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent 0b3512f commit d181b76

File tree

6 files changed

+140
-124
lines changed

6 files changed

+140
-124
lines changed

lib/internal/modules/cjs/loader.js

+105-86
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ const cjsParseCache = new SafeWeakMap();
7272
// Set first due to cycle with ESM loader functions.
7373
module.exports = {
7474
wrapSafe, Module, toRealPath, readPackageScope, cjsParseCache,
75-
get hasLoadedAnyUserCJSModule() { return hasLoadedAnyUserCJSModule; }
75+
get hasLoadedAnyUserCJSModule() { return hasLoadedAnyUserCJSModule; },
76+
initializeCJS,
7677
};
7778

7879
const { BuiltinModule } = require('internal/bootstrap/loaders');
@@ -86,8 +87,8 @@ const {
8687
kEmptyObject,
8788
filterOwnProperties,
8889
setOwnProperty,
90+
getLazy,
8991
} = require('internal/util');
90-
const { Script } = require('vm');
9192
const { internalCompileFunction } = require('internal/vm');
9293
const assert = require('internal/assert');
9394
const fs = require('fs');
@@ -97,21 +98,24 @@ const { sep } = path;
9798
const { internalModuleStat } = internalBinding('fs');
9899
const { safeGetenv } = internalBinding('credentials');
99100
const {
100-
cjsConditions,
101+
getCjsConditions,
102+
initializeCjsConditions,
101103
hasEsmSyntax,
102104
loadBuiltinModule,
103105
makeRequireFunction,
104106
normalizeReferrerURL,
105107
stripBOM,
106108
} = require('internal/modules/helpers');
107-
const { getOptionValue } = require('internal/options');
108-
const preserveSymlinks = getOptionValue('--preserve-symlinks');
109-
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
110-
const shouldReportRequiredModules = process.env.WATCH_REPORT_DEPENDENCIES;
111-
// Do not eagerly grab .manifest, it may be in TDZ
112-
const policy = getOptionValue('--experimental-policy') ?
113-
require('internal/process/policy') :
114-
null;
109+
const packageJsonReader = require('internal/modules/package_json_reader');
110+
const { getOptionValue, getEmbedderOptions } = require('internal/options');
111+
const policy = getLazy(
112+
() => (getOptionValue('--experimental-policy') ? require('internal/process/policy') : null)
113+
);
114+
const shouldReportRequiredModules = getLazy(() => process.env.WATCH_REPORT_DEPENDENCIES);
115+
116+
const getCascadedLoader = getLazy(
117+
() => require('internal/process/esm_loader').esmLoader
118+
);
115119

116120
// Whether any user-provided CJS modules had been loaded (executed).
117121
// Used for internal assertions.
@@ -127,7 +131,6 @@ const {
127131
setArrowMessage,
128132
} = require('internal/errors');
129133
const { validateString } = require('internal/validators');
130-
const pendingDeprecation = getOptionValue('--pending-deprecation');
131134

132135
const {
133136
CHAR_BACKWARD_SLASH,
@@ -140,15 +143,7 @@ const {
140143
isProxy
141144
} = require('internal/util/types');
142145

143-
const asyncESM = require('internal/process/esm_loader');
144-
const { enrichCJSError } = require('internal/modules/esm/translators');
145146
const { kEvaluated } = internalBinding('module_wrap');
146-
const {
147-
encodedSepRegEx,
148-
packageExportsResolve,
149-
packageImportsResolve
150-
} = require('internal/modules/esm/resolve');
151-
152147
const isWindows = process.platform === 'win32';
153148

154149
const relativeResolveCache = ObjectCreate(null);
@@ -190,13 +185,13 @@ function updateChildren(parent, child, scan) {
190185
}
191186

192187
function reportModuleToWatchMode(filename) {
193-
if (shouldReportRequiredModules && process.send) {
188+
if (shouldReportRequiredModules() && process.send) {
194189
process.send({ 'watch:require': [filename] });
195190
}
196191
}
197192

198193
function reportModuleNotFoundToWatchMode(basePath, extensions) {
199-
if (shouldReportRequiredModules && process.send) {
194+
if (shouldReportRequiredModules() && process.send) {
200195
process.send({ 'watch:require': ArrayPrototypeMap(extensions, (ext) => path.resolve(`${basePath}${ext}`)) });
201196
}
202197
}
@@ -213,22 +208,6 @@ function Module(id = '', parent) {
213208
this.children = [];
214209
}
215210

216-
const builtinModules = [];
217-
for (const { 0: id, 1: mod } of BuiltinModule.map) {
218-
if (mod.canBeRequiredByUsers &&
219-
BuiltinModule.canBeRequiredWithoutScheme(id)) {
220-
ArrayPrototypePush(builtinModules, id);
221-
}
222-
}
223-
224-
const allBuiltins = new SafeSet(
225-
ArrayPrototypeFlatMap(builtinModules, (bm) => [bm, `node:${bm}`])
226-
);
227-
BuiltinModule.getSchemeOnlyModuleNames().forEach((builtin) => allBuiltins.add(`node:${builtin}`));
228-
229-
ObjectFreeze(builtinModules);
230-
Module.builtinModules = builtinModules;
231-
232211
Module._cache = ObjectCreate(null);
233212
Module._pathCache = ObjectCreate(null);
234213
Module._extensions = ObjectCreate(null);
@@ -297,26 +276,59 @@ function setModuleParent(value) {
297276
moduleParentCache.set(this, value);
298277
}
299278

300-
ObjectDefineProperty(Module.prototype, 'parent', {
301-
__proto__: null,
302-
get: pendingDeprecation ? deprecate(
303-
getModuleParent,
304-
'module.parent is deprecated due to accuracy issues. Please use ' +
305-
'require.main to find program entry point instead.',
306-
'DEP0144'
307-
) : getModuleParent,
308-
set: pendingDeprecation ? deprecate(
309-
setModuleParent,
310-
'module.parent is deprecated due to accuracy issues. Please use ' +
311-
'require.main to find program entry point instead.',
312-
'DEP0144'
313-
) : setModuleParent,
314-
});
315-
316279
let debug = require('internal/util/debuglog').debuglog('module', (fn) => {
317280
debug = fn;
318281
});
319-
Module._debug = deprecate(debug, 'Module._debug is deprecated.', 'DEP0077');
282+
283+
const builtinModules = [];
284+
// This function is called during pre-execution, before any user code is run.
285+
function initializeCJS() {
286+
const pendingDeprecation = getOptionValue('--pending-deprecation');
287+
ObjectDefineProperty(Module.prototype, 'parent', {
288+
__proto__: null,
289+
get: pendingDeprecation ? deprecate(
290+
getModuleParent,
291+
'module.parent is deprecated due to accuracy issues. Please use ' +
292+
'require.main to find program entry point instead.',
293+
'DEP0144'
294+
) : getModuleParent,
295+
set: pendingDeprecation ? deprecate(
296+
setModuleParent,
297+
'module.parent is deprecated due to accuracy issues. Please use ' +
298+
'require.main to find program entry point instead.',
299+
'DEP0144'
300+
) : setModuleParent,
301+
});
302+
Module._debug = deprecate(debug, 'Module._debug is deprecated.', 'DEP0077');
303+
304+
for (const { 0: id, 1: mod } of BuiltinModule.map) {
305+
if (mod.canBeRequiredByUsers &&
306+
BuiltinModule.canBeRequiredWithoutScheme(id)) {
307+
ArrayPrototypePush(builtinModules, id);
308+
}
309+
}
310+
311+
const allBuiltins = new SafeSet(
312+
ArrayPrototypeFlatMap(builtinModules, (bm) => [bm, `node:${bm}`])
313+
);
314+
BuiltinModule.getSchemeOnlyModuleNames().forEach((builtin) => allBuiltins.add(`node:${builtin}`));
315+
ObjectFreeze(builtinModules);
316+
Module.builtinModules = builtinModules;
317+
318+
Module.isBuiltin = function isBuiltin(moduleName) {
319+
return allBuiltins.has(moduleName);
320+
};
321+
322+
initializeCjsConditions();
323+
324+
if (!getEmbedderOptions().noGlobalSearchPaths) {
325+
Module._initPaths();
326+
}
327+
328+
// TODO(joyeecheung): deprecate this in favor of a proper hook?
329+
Module.runMain =
330+
require('internal/modules/run_main').executeUserEntryPoint;
331+
}
320332

321333
// Given a module name, and a list of paths to test, returns the first
322334
// matching file in the following precedence.
@@ -337,7 +349,6 @@ function readPackage(requestPath) {
337349
const existing = packageJsonCache.get(jsonPath);
338350
if (existing !== undefined) return existing;
339351

340-
const packageJsonReader = require('internal/modules/package_json_reader');
341352
const result = packageJsonReader.read(jsonPath);
342353
const json = result.containsKeys === false ? '{}' : result.string;
343354
if (json === undefined) {
@@ -440,7 +451,7 @@ const realpathCache = new SafeMap();
440451
function tryFile(requestPath, isMain) {
441452
const rc = _stat(requestPath);
442453
if (rc !== 0) return;
443-
if (preserveSymlinks && !isMain) {
454+
if (getOptionValue('--preserve-symlinks') && !isMain) {
444455
return path.resolve(requestPath);
445456
}
446457
return toRealPath(requestPath);
@@ -511,9 +522,10 @@ function trySelf(parentPath, request) {
511522
}
512523

513524
try {
525+
const { packageExportsResolve } = require('internal/modules/esm/resolve');
514526
return finalizeEsmResolution(packageExportsResolve(
515527
pathToFileURL(pkgPath + '/package.json'), expansion, pkg,
516-
pathToFileURL(parentPath), cjsConditions), parentPath, pkgPath);
528+
pathToFileURL(parentPath), getCjsConditions()), parentPath, pkgPath);
517529
} catch (e) {
518530
if (e.code === 'ERR_MODULE_NOT_FOUND')
519531
throw createEsmNotFoundErr(request, pkgPath + '/package.json');
@@ -535,9 +547,10 @@ function resolveExports(nmPath, request) {
535547
const pkg = _readPackage(pkgPath);
536548
if (pkg?.exports != null) {
537549
try {
550+
const { packageExportsResolve } = require('internal/modules/esm/resolve');
538551
return finalizeEsmResolution(packageExportsResolve(
539552
pathToFileURL(pkgPath + '/package.json'), '.' + expansion, pkg, null,
540-
cjsConditions), null, pkgPath);
553+
getCjsConditions()), null, pkgPath);
541554
} catch (e) {
542555
if (e.code === 'ERR_MODULE_NOT_FOUND')
543556
throw createEsmNotFoundErr(request, pkgPath + '/package.json');
@@ -616,19 +629,19 @@ Module._findPath = function(request, paths, isMain) {
616629
if (!trailingSlash) {
617630
if (rc === 0) { // File.
618631
if (!isMain) {
619-
if (preserveSymlinks) {
632+
if (getOptionValue('--preserve-symlinks')) {
620633
filename = path.resolve(basePath);
621634
} else {
622635
filename = toRealPath(basePath);
623636
}
624-
} else if (preserveSymlinksMain) {
625-
// For the main module, we use the preserveSymlinksMain flag instead
637+
} else if (getOptionValue('--preserve-symlinks-main')) {
638+
// For the main module, we use the --preserve-symlinks-main flag instead
626639
// mainly for backward compatibility, as the preserveSymlinks flag
627640
// historically has not applied to the main module. Most likely this
628641
// was intended to keep .bin/ binaries working, as following those
629642
// symlinks is usually required for the imports in the corresponding
630643
// files to resolve; that said, in some use cases following symlinks
631-
// causes bigger problems which is why the preserveSymlinksMain option
644+
// causes bigger problems which is why the --preserve-symlinks-main option
632645
// is needed.
633646
filename = path.resolve(basePath);
634647
} else {
@@ -999,9 +1012,10 @@ Module._resolveFilename = function(request, parent, isMain, options) {
9991012
const pkg = readPackageScope(parentPath) || {};
10001013
if (pkg.data?.imports != null) {
10011014
try {
1015+
const { packageImportsResolve } = require('internal/modules/esm/resolve');
10021016
return finalizeEsmResolution(
10031017
packageImportsResolve(request, pathToFileURL(parentPath),
1004-
cjsConditions), parentPath,
1018+
getCjsConditions()), parentPath,
10051019
pkg.path);
10061020
} catch (e) {
10071021
if (e.code === 'ERR_MODULE_NOT_FOUND')
@@ -1043,6 +1057,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {
10431057
};
10441058

10451059
function finalizeEsmResolution(resolved, parentPath, pkgPath) {
1060+
const { encodedSepRegEx } = require('internal/modules/esm/resolve');
10461061
if (RegExpPrototypeExec(encodedSepRegEx, resolved) !== null)
10471062
throw new ERR_INVALID_MODULE_SPECIFIER(
10481063
resolved, 'must not include encoded "/" or "\\" characters', parentPath);
@@ -1081,14 +1096,14 @@ Module.prototype.load = function(filename) {
10811096
Module._extensions[extension](this, filename);
10821097
this.loaded = true;
10831098

1084-
const esmLoader = asyncESM.esmLoader;
1099+
const cascadedLoader = getCascadedLoader();
10851100
// Create module entry at load time to snapshot exports correctly
10861101
const exports = this.exports;
10871102
// Preemptively cache
10881103
if ((module?.module === undefined ||
10891104
module.module.getStatus() < kEvaluated) &&
1090-
!esmLoader.cjsCache.has(this))
1091-
esmLoader.cjsCache.set(this, exports);
1105+
!cascadedLoader.cjsCache.has(this))
1106+
cascadedLoader.cjsCache.set(this, exports);
10921107
};
10931108

10941109

@@ -1113,17 +1128,20 @@ Module.prototype.require = function(id) {
11131128
// (needed for setting breakpoint when called with --inspect-brk)
11141129
let resolvedArgv;
11151130
let hasPausedEntry = false;
1116-
1131+
let Script;
11171132
function wrapSafe(filename, content, cjsModuleInstance) {
11181133
if (patched) {
11191134
const wrapper = Module.wrap(content);
1135+
if (Script === undefined) {
1136+
({ Script } = require('vm'));
1137+
}
11201138
const script = new Script(wrapper, {
11211139
filename,
11221140
lineOffset: 0,
11231141
importModuleDynamically: async (specifier, _, importAssertions) => {
1124-
const loader = asyncESM.esmLoader;
1125-
return loader.import(specifier, normalizeReferrerURL(filename),
1126-
importAssertions);
1142+
const cascadedLoader = getCascadedLoader();
1143+
return cascadedLoader.import(specifier, normalizeReferrerURL(filename),
1144+
importAssertions);
11271145
},
11281146
});
11291147

@@ -1147,9 +1165,9 @@ function wrapSafe(filename, content, cjsModuleInstance) {
11471165
], {
11481166
filename,
11491167
importModuleDynamically(specifier, _, importAssertions) {
1150-
const loader = asyncESM.esmLoader;
1151-
return loader.import(specifier, normalizeReferrerURL(filename),
1152-
importAssertions);
1168+
const cascadedLoader = getCascadedLoader();
1169+
return cascadedLoader.import(specifier, normalizeReferrerURL(filename),
1170+
importAssertions);
11531171
},
11541172
});
11551173

@@ -1160,8 +1178,10 @@ function wrapSafe(filename, content, cjsModuleInstance) {
11601178

11611179
return result.function;
11621180
} catch (err) {
1163-
if (process.mainModule === cjsModuleInstance)
1181+
if (process.mainModule === cjsModuleInstance) {
1182+
const { enrichCJSError } = require('internal/modules/esm/translators');
11641183
enrichCJSError(err, content);
1184+
}
11651185
throw err;
11661186
}
11671187
}
@@ -1173,10 +1193,11 @@ function wrapSafe(filename, content, cjsModuleInstance) {
11731193
Module.prototype._compile = function(content, filename) {
11741194
let moduleURL;
11751195
let redirects;
1176-
if (policy?.manifest) {
1196+
const manifest = policy()?.manifest;
1197+
if (manifest) {
11771198
moduleURL = pathToFileURL(filename);
1178-
redirects = policy.manifest.getDependencyMapper(moduleURL);
1179-
policy.manifest.assertIntegrity(moduleURL, content);
1199+
redirects = manifest.getDependencyMapper(moduleURL);
1200+
manifest.assertIntegrity(moduleURL, content);
11801201
}
11811202

11821203
const compiledWrapper = wrapSafe(filename, content, this);
@@ -1277,9 +1298,10 @@ Module._extensions['.js'] = function(module, filename) {
12771298
Module._extensions['.json'] = function(module, filename) {
12781299
const content = fs.readFileSync(filename, 'utf8');
12791300

1280-
if (policy?.manifest) {
1301+
const manifest = policy()?.manifest;
1302+
if (manifest) {
12811303
const moduleURL = pathToFileURL(filename);
1282-
policy.manifest.assertIntegrity(moduleURL, content);
1304+
manifest.assertIntegrity(moduleURL, content);
12831305
}
12841306

12851307
try {
@@ -1293,10 +1315,11 @@ Module._extensions['.json'] = function(module, filename) {
12931315

12941316
// Native extension for .node
12951317
Module._extensions['.node'] = function(module, filename) {
1296-
if (policy?.manifest) {
1318+
const manifest = policy()?.manifest;
1319+
if (manifest) {
12971320
const content = fs.readFileSync(filename);
12981321
const moduleURL = pathToFileURL(filename);
1299-
policy.manifest.assertIntegrity(moduleURL, content);
1322+
manifest.assertIntegrity(moduleURL, content);
13001323
}
13011324
// Be aware this doesn't use `content`
13021325
return process.dlopen(module, path.toNamespacedPath(filename));
@@ -1405,9 +1428,5 @@ Module.syncBuiltinESMExports = function syncBuiltinESMExports() {
14051428
}
14061429
};
14071430

1408-
Module.isBuiltin = function isBuiltin(moduleName) {
1409-
return allBuiltins.has(moduleName);
1410-
};
1411-
14121431
// Backwards compatibility
14131432
Module.Module = Module;

0 commit comments

Comments
 (0)