Skip to content

Commit 5fcc1d3

Browse files
joyeecheungmarco-ippolito
authored andcommitted
module: refactor ESM loader initialization and entry point handling
Split the `internal/process/esm_loader` file which contains the singleton cascaded loader: - The the singleton cascaded loader now directly resides in `internal/modules/esm/loader`, where the constructor also lives. This file is the root of most circular dependency of ESM code, (because components of the loader need the singleton itself), so this makes the dependency more obvious. Added comments about loading it lazily to avoid circular dependency. - The getter to the cascaded loader is also turned into a method to make the side effect explicit. - The sequence of `loadESM()` and `handleMainPromise` is now merged together into `runEntryPointWithESMLoader()` in `internal/modules/run_main` because this is intended to run entry points with the ESM loader and not just any module. - Documents how top-level await is handled. PR-URL: #51999 Fixes: #42868 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
1 parent 508e968 commit 5fcc1d3

17 files changed

+136
-140
lines changed

.github/CODEOWNERS

-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@
9696
/doc/api/packages.md @nodejs/loaders
9797
/lib/internal/bootstrap/realm.js @nodejs/loaders
9898
/lib/internal/modules/* @nodejs/loaders
99-
/lib/internal/process/esm_loader.js @nodejs/loaders
10099
/lib/internal/process/execution.js @nodejs/loaders
101100
/lib/module.js @nodejs/loaders
102101
/src/module_wrap* @nodejs/loaders @nodejs/vm

lib/internal/main/check_syntax.js

+2-5
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ function loadESMIfNeeded(cb) {
5050
const hasModulePreImport = getOptionValue('--import').length > 0;
5151

5252
if (hasModulePreImport) {
53-
const { loadESM } = require('internal/process/esm_loader');
54-
loadESM(cb);
53+
require('internal/modules/run_main').runEntryPointWithESMLoader(cb);
5554
return;
5655
}
5756
cb();
@@ -76,7 +75,5 @@ async function checkSyntax(source, filename) {
7675
return;
7776
}
7877

79-
const { loadESM } = require('internal/process/esm_loader');
80-
const { handleMainPromise } = require('internal/modules/run_main');
81-
handleMainPromise(loadESM((loader) => wrapSafe(filename, source)));
78+
wrapSafe(filename, source);
8279
}

lib/internal/main/eval_stdin.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const {
1010
const { getOptionValue } = require('internal/options');
1111

1212
const {
13-
evalModule,
13+
evalModuleEntryPoint,
1414
evalScript,
1515
readStdin,
1616
} = require('internal/process/execution');
@@ -24,15 +24,15 @@ readStdin((code) => {
2424
process._eval = code;
2525

2626
const print = getOptionValue('--print');
27-
const loadESM = getOptionValue('--import').length > 0;
27+
const shouldLoadESM = getOptionValue('--import').length > 0;
2828
if (getOptionValue('--input-type') === 'module' ||
2929
(getOptionValue('--experimental-default-type') === 'module' && getOptionValue('--input-type') !== 'commonjs')) {
30-
evalModule(code, print);
30+
evalModuleEntryPoint(code, print);
3131
} else {
3232
evalScript('[stdin]',
3333
code,
3434
getOptionValue('--inspect-brk'),
3535
print,
36-
loadESM);
36+
shouldLoadESM);
3737
}
3838
});

lib/internal/main/eval_string.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const {
1313
prepareMainThreadExecution,
1414
markBootstrapComplete,
1515
} = require('internal/process/pre_execution');
16-
const { evalModule, evalScript } = require('internal/process/execution');
16+
const { evalModuleEntryPoint, evalScript } = require('internal/process/execution');
1717
const { addBuiltinLibsToObject } = require('internal/modules/helpers');
1818

1919
const { getOptionValue } = require('internal/options');
@@ -24,10 +24,10 @@ markBootstrapComplete();
2424

2525
const source = getOptionValue('--eval');
2626
const print = getOptionValue('--print');
27-
const loadESM = getOptionValue('--import').length > 0 || getOptionValue('--experimental-loader').length > 0;
27+
const shouldLoadESM = getOptionValue('--import').length > 0 || getOptionValue('--experimental-loader').length > 0;
2828
if (getOptionValue('--input-type') === 'module' ||
2929
(getOptionValue('--experimental-default-type') === 'module' && getOptionValue('--input-type') !== 'commonjs')) {
30-
evalModule(source, print);
30+
evalModuleEntryPoint(source, print);
3131
} else {
3232
// For backward compatibility, we want the identifier crypto to be the
3333
// `node:crypto` module rather than WebCrypto.
@@ -54,5 +54,5 @@ if (getOptionValue('--input-type') === 'module' ||
5454
) : source,
5555
getOptionValue('--inspect-brk'),
5656
print,
57-
loadESM);
57+
shouldLoadESM);
5858
}

lib/internal/main/repl.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ if (process.env.NODE_REPL_EXTERNAL_MODULE) {
3535
process.exit(kInvalidCommandLineArgument);
3636
}
3737

38-
const esmLoader = require('internal/process/esm_loader');
39-
esmLoader.loadESM(() => {
38+
require('internal/modules/run_main').runEntryPointWithESMLoader(() => {
4039
console.log(`Welcome to Node.js ${process.version}.\n` +
4140
'Type ".help" for more information.');
4241

@@ -64,5 +63,7 @@ if (process.env.NODE_REPL_EXTERNAL_MODULE) {
6463
getOptionValue('--inspect-brk'),
6564
getOptionValue('--print'));
6665
}
66+
// The TLAs in the REPL are still run as scripts, just transformed as async
67+
// IIFEs for the REPL code itself to await on.
6768
});
6869
}

lib/internal/main/worker_thread.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ port.on('message', (message) => {
170170
}
171171

172172
case 'module': {
173-
const { evalModule } = require('internal/process/execution');
174-
PromisePrototypeThen(evalModule(filename), undefined, (e) => {
173+
const { evalModuleEntryPoint } = require('internal/process/execution');
174+
PromisePrototypeThen(evalModuleEntryPoint(filename), undefined, (e) => {
175175
workerOnGlobalUncaughtException(e, true);
176176
});
177177
break;

lib/internal/modules/esm/handle_process_exit.js

-16
This file was deleted.

lib/internal/modules/esm/hooks.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ class Hooks {
161161
* loader (user-land) to the worker.
162162
*/
163163
async register(urlOrSpecifier, parentURL, data) {
164-
const moduleLoader = require('internal/process/esm_loader').esmLoader;
165-
const keyedExports = await moduleLoader.import(
164+
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
165+
const keyedExports = await cascadedLoader.import(
166166
urlOrSpecifier,
167167
parentURL,
168168
kEmptyObject,

lib/internal/modules/esm/loader.js

+22-12
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const {
2020
ERR_UNKNOWN_MODULE_FORMAT,
2121
} = require('internal/errors').codes;
2222
const { getOptionValue } = require('internal/options');
23-
const { pathToFileURL, isURL } = require('internal/url');
23+
const { isURL } = require('internal/url');
2424
const { emitExperimentalWarning } = require('internal/util');
2525
const {
2626
getDefaultConditions,
@@ -85,11 +85,6 @@ class ModuleLoader {
8585
*/
8686
#defaultConditions = getDefaultConditions();
8787

88-
/**
89-
* The index for assigning unique URLs to anonymous module evaluation
90-
*/
91-
evalIndex = 0;
92-
9388
/**
9489
* Registry of resolved specifiers
9590
*/
@@ -187,10 +182,7 @@ class ModuleLoader {
187182
}
188183
}
189184

190-
async eval(
191-
source,
192-
url = pathToFileURL(`${process.cwd()}/[eval${++this.evalIndex}]`).href,
193-
) {
185+
async eval(source, url) {
194186
const evalInstance = (url) => {
195187
const { ModuleWrap } = internalBinding('module_wrap');
196188
const { registerModule } = require('internal/modules/esm/utils');
@@ -214,6 +206,7 @@ class ModuleLoader {
214206
return {
215207
__proto__: null,
216208
namespace: module.getNamespace(),
209+
module,
217210
};
218211
}
219212

@@ -568,6 +561,23 @@ function getHooksProxy() {
568561
return hooksProxy;
569562
}
570563

564+
let cascadedLoader;
565+
566+
/**
567+
* This is a singleton ESM loader that integrates the loader hooks, if any.
568+
* It it used by other internal built-ins when they need to load ESM code
569+
* while also respecting hooks.
570+
* When built-ins need access to this loader, they should do
571+
* require('internal/module/esm/loader').getOrInitializeCascadedLoader()
572+
* lazily only right before the loader is actually needed, and don't do it
573+
* in the top-level, to avoid circular dependencies.
574+
* @returns {ModuleLoader}
575+
*/
576+
function getOrInitializeCascadedLoader() {
577+
cascadedLoader ??= createModuleLoader();
578+
return cascadedLoader;
579+
}
580+
571581
/**
572582
* Register a single loader programmatically.
573583
* @param {string|import('url').URL} specifier
@@ -598,12 +608,11 @@ function getHooksProxy() {
598608
* ```
599609
*/
600610
function register(specifier, parentURL = undefined, options) {
601-
const moduleLoader = require('internal/process/esm_loader').esmLoader;
602611
if (parentURL != null && typeof parentURL === 'object' && !isURL(parentURL)) {
603612
options = parentURL;
604613
parentURL = options.parentURL;
605614
}
606-
moduleLoader.register(
615+
getOrInitializeCascadedLoader().register(
607616
specifier,
608617
parentURL ?? 'data:',
609618
options?.data,
@@ -614,5 +623,6 @@ function register(specifier, parentURL = undefined, options) {
614623
module.exports = {
615624
createModuleLoader,
616625
getHooksProxy,
626+
getOrInitializeCascadedLoader,
617627
register,
618628
};

lib/internal/modules/esm/translators.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ const {
5555
const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache');
5656
const moduleWrap = internalBinding('module_wrap');
5757
const { ModuleWrap } = moduleWrap;
58-
const asyncESM = require('internal/process/esm_loader');
5958
const { emitWarningSync } = require('internal/process/warning');
6059
const { internalCompileFunction } = require('internal/vm');
6160
const {
@@ -157,7 +156,8 @@ function errPath(url) {
157156
* @returns {Promise<import('internal/modules/esm/loader.js').ModuleExports>} The imported module.
158157
*/
159158
async function importModuleDynamically(specifier, { url }, attributes) {
160-
return asyncESM.esmLoader.import(specifier, url, attributes);
159+
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
160+
return cascadedLoader.import(specifier, url, attributes);
161161
}
162162

163163
// Strategy for loading a standard JavaScript module.
@@ -243,6 +243,7 @@ function loadCJSModule(module, source, url, filename) {
243243

244244
const compiledWrapper = compileResult.function;
245245

246+
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
246247
const __dirname = dirname(filename);
247248
// eslint-disable-next-line func-name-matching,func-style
248249
const requireFn = function require(specifier) {
@@ -261,7 +262,7 @@ function loadCJSModule(module, source, url, filename) {
261262
}
262263
specifier = `${pathToFileURL(path)}`;
263264
}
264-
const job = asyncESM.esmLoader.getModuleJobSync(specifier, url, importAttributes);
265+
const job = cascadedLoader.getModuleJobSync(specifier, url, importAttributes);
265266
job.runSync();
266267
return cjsCache.get(job.url).exports;
267268
};
@@ -272,7 +273,7 @@ function loadCJSModule(module, source, url, filename) {
272273
specifier = `${pathToFileURL(path)}`;
273274
}
274275
}
275-
const { url: resolvedURL } = asyncESM.esmLoader.resolveSync(specifier, url, kEmptyObject);
276+
const { url: resolvedURL } = cascadedLoader.resolveSync(specifier, url, kEmptyObject);
276277
return StringPrototypeStartsWith(resolvedURL, 'file://') ? fileURLToPath(resolvedURL) : resolvedURL;
277278
});
278279
setOwnProperty(requireFn, 'main', process.mainModule);

lib/internal/modules/esm/utils.js

+4-7
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ const {
3232
const {
3333
emitExperimentalWarning,
3434
getCWDURL,
35-
getLazy,
3635
} = require('internal/util');
3736
const {
3837
setImportModuleDynamicallyCallback,
@@ -181,9 +180,6 @@ function initializeImportMetaObject(symbol, meta) {
181180
}
182181
}
183182
}
184-
const getCascadedLoader = getLazy(
185-
() => require('internal/process/esm_loader').esmLoader,
186-
);
187183

188184
/**
189185
* Proxy the dynamic import to the default loader.
@@ -194,7 +190,8 @@ const getCascadedLoader = getLazy(
194190
*/
195191
function defaultImportModuleDynamically(specifier, attributes, referrerName) {
196192
const parentURL = normalizeReferrerURL(referrerName);
197-
return getCascadedLoader().import(specifier, parentURL, attributes);
193+
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
194+
return cascadedLoader.import(specifier, parentURL, attributes);
198195
}
199196

200197
/**
@@ -263,10 +260,10 @@ async function initializeHooks() {
263260
const customLoaderURLs = getOptionValue('--experimental-loader');
264261

265262
const { Hooks } = require('internal/modules/esm/hooks');
266-
const esmLoader = require('internal/process/esm_loader').esmLoader;
263+
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
267264

268265
const hooks = new Hooks();
269-
esmLoader.setCustomizations(hooks);
266+
cascadedLoader.setCustomizations(hooks);
270267

271268
// We need the loader customizations to be set _before_ we start invoking
272269
// `--require`, otherwise loops can happen because a `--require` script

0 commit comments

Comments
 (0)