Skip to content

Commit 0b661ec

Browse files
joyeecheungRafaelGSS
authored andcommitted
module: do less CJS module loader initialization at run time
This patch: - Builds the set of modules that can be required by users with/without the `node:` prefix at snapshot building time. We only modify it when `--expose-internals` but the default set is now in the snapshot. At run time the CJS module loader only creates a frozen array out of it. - `BuiltinModule.canBeRequiredWithoutScheme()` is now enough to determine if an id can be required without `node:` without an additional call to `BuiltinModule.canBeRequiredByUsers()` - Replace the pending-to-deprecate methods on `Module` with an internal implementation that only queries the CLI flags when being invoked. So we can install these methods in the snapshot. PR-URL: #47194 Reviewed-By: Geoffrey Booth <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]>
1 parent 21e1e1b commit 0b661ec

File tree

6 files changed

+137
-89
lines changed

6 files changed

+137
-89
lines changed

lib/internal/bootstrap/realm.js

+48-12
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const {
6363
SafeSet,
6464
String,
6565
StringPrototypeStartsWith,
66+
StringPrototypeSlice,
6667
TypeError,
6768
} = primordials;
6869

@@ -126,10 +127,14 @@ const legacyWrapperList = new SafeSet([
126127
'util',
127128
]);
128129

130+
// The code bellow assumes that the two lists must not contain any modules
131+
// beginning with "internal/".
129132
// Modules that can only be imported via the node: scheme.
130133
const schemelessBlockList = new SafeSet([
131134
'test',
132135
]);
136+
// Modules that will only be enabled at run time.
137+
const experimentalModuleList = new SafeSet();
133138

134139
// Set up process.binding() and process._linkedBinding().
135140
{
@@ -196,6 +201,20 @@ const getOwn = (target, property, receiver) => {
196201
undefined;
197202
};
198203

204+
const publicBuiltinIds = builtinIds
205+
.filter((id) =>
206+
!StringPrototypeStartsWith(id, 'internal/') &&
207+
!experimentalModuleList.has(id),
208+
);
209+
// Do not expose the loaders to user land even with --expose-internals.
210+
const internalBuiltinIds = builtinIds
211+
.filter((id) => StringPrototypeStartsWith(id, 'internal/') && id !== selfId);
212+
213+
// When --expose-internals is on we'll add the internal builtin ids to these.
214+
const canBeRequiredByUsersList = new SafeSet(publicBuiltinIds);
215+
const canBeRequiredByUsersWithoutSchemeList =
216+
new SafeSet(publicBuiltinIds.filter((id) => !schemelessBlockList.has(id)));
217+
199218
/**
200219
* An internal abstraction for the built-in JavaScript modules of Node.js.
201220
* Be careful not to expose this to user land unless --expose-internals is
@@ -213,7 +232,6 @@ class BuiltinModule {
213232
constructor(id) {
214233
this.filename = `${id}.js`;
215234
this.id = id;
216-
this.canBeRequiredByUsers = !StringPrototypeStartsWith(id, 'internal/');
217235

218236
// The CJS exports object of the module.
219237
this.exports = {};
@@ -235,14 +253,23 @@ class BuiltinModule {
235253
this.exportKeys = undefined;
236254
}
237255

256+
static allowRequireByUsers(id) {
257+
if (id === selfId) {
258+
// No code because this is an assertion against bugs.
259+
// eslint-disable-next-line no-restricted-syntax
260+
throw new Error(`Should not allow ${id}`);
261+
}
262+
canBeRequiredByUsersList.add(id);
263+
if (!schemelessBlockList.has(id)) {
264+
canBeRequiredByUsersWithoutSchemeList.add(id);
265+
}
266+
}
267+
238268
// To be called during pre-execution when --expose-internals is on.
239269
// Enables the user-land module loader to access internal modules.
240270
static exposeInternals() {
241-
for (const { 0: id, 1: mod } of BuiltinModule.map) {
242-
// Do not expose this to user land even with --expose-internals.
243-
if (id !== selfId) {
244-
mod.canBeRequiredByUsers = true;
245-
}
271+
for (let i = 0; i < internalBuiltinIds.length; ++i) {
272+
BuiltinModule.allowRequireByUsers(internalBuiltinIds[i]);
246273
}
247274
}
248275

@@ -251,14 +278,23 @@ class BuiltinModule {
251278
}
252279

253280
static canBeRequiredByUsers(id) {
254-
const mod = BuiltinModule.map.get(id);
255-
return mod && mod.canBeRequiredByUsers;
281+
return canBeRequiredByUsersList.has(id);
256282
}
257283

258-
// Determine if a core module can be loaded without the node: prefix. This
259-
// function does not validate if the module actually exists.
260284
static canBeRequiredWithoutScheme(id) {
261-
return !schemelessBlockList.has(id);
285+
return canBeRequiredByUsersWithoutSchemeList.has(id);
286+
}
287+
288+
static isBuiltin(id) {
289+
return BuiltinModule.canBeRequiredWithoutScheme(id) || (
290+
typeof id === 'string' &&
291+
StringPrototypeStartsWith(id, 'node:') &&
292+
BuiltinModule.canBeRequiredByUsers(StringPrototypeSlice(id, 5))
293+
);
294+
}
295+
296+
static getCanBeRequiredByUsersWithoutSchemeList() {
297+
return ArrayFrom(canBeRequiredByUsersWithoutSchemeList);
262298
}
263299

264300
static getSchemeOnlyModuleNames() {
@@ -267,7 +303,7 @@ class BuiltinModule {
267303

268304
// Used by user-land module loaders to compile and load builtins.
269305
compileForPublicLoader() {
270-
if (!this.canBeRequiredByUsers) {
306+
if (!BuiltinModule.canBeRequiredByUsers(this.id)) {
271307
// No code because this is an assertion against bugs
272308
// eslint-disable-next-line no-restricted-syntax
273309
throw new Error(`Should not compile ${this.id} for public use`);

lib/internal/modules/cjs/loader.js

+27-48
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ const {
3434
ArrayPrototypeSplice,
3535
ArrayPrototypeUnshift,
3636
ArrayPrototypeUnshiftApply,
37-
ArrayPrototypeFlatMap,
3837
Boolean,
3938
Error,
4039
JSONParse,
@@ -51,7 +50,6 @@ const {
5150
ReflectSet,
5251
RegExpPrototypeExec,
5352
SafeMap,
54-
SafeSet,
5553
SafeWeakMap,
5654
String,
5755
StringPrototypeCharAt,
@@ -81,7 +79,7 @@ const {
8179
} = require('internal/source_map/source_map_cache');
8280
const { pathToFileURL, fileURLToPath, isURLInstance } = require('internal/url');
8381
const {
84-
deprecate,
82+
pendingDeprecate,
8583
emitExperimentalWarning,
8684
kEmptyObject,
8785
filterOwnProperties,
@@ -308,44 +306,29 @@ let debug = require('internal/util/debuglog').debuglog('module', (fn) => {
308306
debug = fn;
309307
});
310308

311-
const builtinModules = [];
309+
ObjectDefineProperty(Module.prototype, 'parent', {
310+
__proto__: null,
311+
get: pendingDeprecate(
312+
getModuleParent,
313+
'module.parent is deprecated due to accuracy issues. Please use ' +
314+
'require.main to find program entry point instead.',
315+
'DEP0144',
316+
),
317+
set: pendingDeprecate(
318+
setModuleParent,
319+
'module.parent is deprecated due to accuracy issues. Please use ' +
320+
'require.main to find program entry point instead.',
321+
'DEP0144',
322+
),
323+
});
324+
Module._debug = pendingDeprecate(debug, 'Module._debug is deprecated.', 'DEP0077');
325+
Module.isBuiltin = BuiltinModule.isBuiltin;
326+
312327
// This function is called during pre-execution, before any user code is run.
313328
function initializeCJS() {
314-
const pendingDeprecation = getOptionValue('--pending-deprecation');
315-
ObjectDefineProperty(Module.prototype, 'parent', {
316-
__proto__: null,
317-
get: pendingDeprecation ? deprecate(
318-
getModuleParent,
319-
'module.parent is deprecated due to accuracy issues. Please use ' +
320-
'require.main to find program entry point instead.',
321-
'DEP0144',
322-
) : getModuleParent,
323-
set: pendingDeprecation ? deprecate(
324-
setModuleParent,
325-
'module.parent is deprecated due to accuracy issues. Please use ' +
326-
'require.main to find program entry point instead.',
327-
'DEP0144',
328-
) : setModuleParent,
329-
});
330-
Module._debug = deprecate(debug, 'Module._debug is deprecated.', 'DEP0077');
331-
332-
for (const { 0: id, 1: mod } of BuiltinModule.map) {
333-
if (mod.canBeRequiredByUsers &&
334-
BuiltinModule.canBeRequiredWithoutScheme(id)) {
335-
ArrayPrototypePush(builtinModules, id);
336-
}
337-
}
338-
339-
const allBuiltins = new SafeSet(
340-
ArrayPrototypeFlatMap(builtinModules, (bm) => [bm, `node:${bm}`]),
341-
);
342-
BuiltinModule.getSchemeOnlyModuleNames().forEach((builtin) => allBuiltins.add(`node:${builtin}`));
343-
ObjectFreeze(builtinModules);
344-
Module.builtinModules = builtinModules;
345-
346-
Module.isBuiltin = function isBuiltin(moduleName) {
347-
return allBuiltins.has(moduleName);
348-
};
329+
// This need to be done at runtime in case --expose-internals is set.
330+
const builtinModules = BuiltinModule.getCanBeRequiredByUsersWithoutSchemeList();
331+
Module.builtinModules = ObjectFreeze(builtinModules);
349332

350333
initializeCjsConditions();
351334

@@ -801,7 +784,6 @@ Module._resolveLookupPaths = function(request, parent) {
801784
StringPrototypeStartsWith(request, 'node:') &&
802785
BuiltinModule.canBeRequiredByUsers(StringPrototypeSlice(request, 5))
803786
) || (
804-
BuiltinModule.canBeRequiredByUsers(request) &&
805787
BuiltinModule.canBeRequiredWithoutScheme(request)
806788
)) {
807789
debug('looking for %j in []', request);
@@ -923,11 +905,11 @@ Module._load = function(request, parent, isMain) {
923905
// Slice 'node:' prefix
924906
const id = StringPrototypeSlice(request, 5);
925907

926-
const module = loadBuiltinModule(id, request);
927-
if (!module?.canBeRequiredByUsers) {
908+
if (!BuiltinModule.canBeRequiredByUsers(id)) {
928909
throw new ERR_UNKNOWN_BUILTIN_MODULE(request);
929910
}
930911

912+
const module = loadBuiltinModule(id, request);
931913
return module.exports;
932914
}
933915

@@ -945,9 +927,8 @@ Module._load = function(request, parent, isMain) {
945927
}
946928
}
947929

948-
const mod = loadBuiltinModule(filename, request);
949-
if (mod?.canBeRequiredByUsers &&
950-
BuiltinModule.canBeRequiredWithoutScheme(filename)) {
930+
if (BuiltinModule.canBeRequiredWithoutScheme(filename)) {
931+
const mod = loadBuiltinModule(filename, request);
951932
return mod.exports;
952933
}
953934

@@ -1001,7 +982,6 @@ Module._resolveFilename = function(request, parent, isMain, options) {
1001982
StringPrototypeStartsWith(request, 'node:') &&
1002983
BuiltinModule.canBeRequiredByUsers(StringPrototypeSlice(request, 5))
1003984
) || (
1004-
BuiltinModule.canBeRequiredByUsers(request) &&
1005985
BuiltinModule.canBeRequiredWithoutScheme(request)
1006986
)
1007987
) {
@@ -1457,8 +1437,7 @@ Module._preloadModules = function(requests) {
14571437

14581438
Module.syncBuiltinESMExports = function syncBuiltinESMExports() {
14591439
for (const mod of BuiltinModule.map.values()) {
1460-
if (mod.canBeRequiredByUsers &&
1461-
BuiltinModule.canBeRequiredWithoutScheme(mod.id)) {
1440+
if (BuiltinModule.canBeRequiredWithoutScheme(mod.id)) {
14621441
mod.syncExports();
14631442
}
14641443
}

lib/internal/modules/esm/hooks.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,7 @@ class Hooks {
207207
globalThis,
208208
// Param getBuiltin
209209
(builtinName) => {
210-
if (BuiltinModule.canBeRequiredByUsers(builtinName) &&
211-
BuiltinModule.canBeRequiredWithoutScheme(builtinName)) {
210+
if (BuiltinModule.canBeRequiredWithoutScheme(builtinName)) {
212211
return require(builtinName);
213212
}
214213
throw new ERR_INVALID_ARG_VALUE('builtinName', builtinName);

lib/internal/modules/esm/resolve.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -736,8 +736,7 @@ function parsePackageName(specifier, base) {
736736
* @returns {resolved: URL, format? : string}
737737
*/
738738
function packageResolve(specifier, base, conditions) {
739-
if (BuiltinModule.canBeRequiredByUsers(specifier) &&
740-
BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
739+
if (BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
741740
return new URL('node:' + specifier);
742741
}
743742

@@ -919,8 +918,7 @@ function checkIfDisallowedImport(specifier, parsed, parsedParentURL) {
919918

920919
return { url: parsed.href };
921920
}
922-
if (BuiltinModule.canBeRequiredByUsers(specifier) &&
923-
BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
921+
if (BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
924922
throw new ERR_NETWORK_IMPORT_DISALLOWED(
925923
specifier,
926924
parsedParentURL,

lib/internal/modules/helpers.js

+10-8
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,14 @@ function getCjsConditions() {
5858
}
5959

6060
function loadBuiltinModule(filename, request) {
61-
const mod = BuiltinModule.map.get(filename);
62-
if (mod?.canBeRequiredByUsers) {
63-
debug('load built-in module %s', request);
64-
// compileForPublicLoader() throws if mod.canBeRequiredByUsers is false:
65-
mod.compileForPublicLoader();
66-
return mod;
61+
if (!BuiltinModule.canBeRequiredByUsers(filename)) {
62+
return;
6763
}
64+
const mod = BuiltinModule.map.get(filename);
65+
debug('load built-in module %s', request);
66+
// compileForPublicLoader() throws if canBeRequiredByUsers is false:
67+
mod.compileForPublicLoader();
68+
return mod;
6869
}
6970

7071
// Invoke with makeRequireFunction(module) where |module| is the Module object
@@ -88,8 +89,9 @@ function makeRequireFunction(mod, redirects) {
8889
const href = destination.href;
8990
if (destination.protocol === 'node:') {
9091
const specifier = destination.pathname;
91-
const mod = loadBuiltinModule(specifier, href);
92-
if (mod && mod.canBeRequiredByUsers) {
92+
93+
if (BuiltinModule.canBeRequiredByUsers(specifier)) {
94+
const mod = loadBuiltinModule(specifier, href);
9395
return mod.exports;
9496
}
9597
throw new ERR_UNKNOWN_BUILTIN_MODULE(specifier);

0 commit comments

Comments
 (0)