Skip to content

Commit 8839dcc

Browse files
bfarias-godaddyaduh95
authored andcommitted
module: refactor modules bootstrap
PR-URL: nodejs#29937 Reviewed-By: Myles Borins <[email protected]> Reviewed-By: Joyee Cheung <[email protected]>
1 parent a6a273c commit 8839dcc

17 files changed

+176
-102
lines changed

doc/api/esm.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -885,13 +885,13 @@ _isMain_ is **true** when resolving the Node.js application entry point.
885885
> 1. Throw a _Module Not Found_ error.
886886
> 1. If _pjson.exports_ is not **null** or **undefined**, then
887887
> 1. If _pjson.exports_ is a String or Array, then
888-
> 1. Return _PACKAGE_EXPORTS_TARGET_RESOLVE(packageURL, pjson.exports,
889-
> "")_.
888+
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_,
889+
> _pjson.exports_, "")_.
890890
> 1. If _pjson.exports is an Object, then
891891
> 1. If _pjson.exports_ contains a _"."_ property, then
892892
> 1. Let _mainExport_ be the _"."_ property in _pjson.exports_.
893-
> 1. Return _PACKAGE_EXPORTS_TARGET_RESOLVE(packageURL, mainExport,
894-
> "")_.
893+
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_,
894+
> _mainExport_, "")_.
895895
> 1. If _pjson.main_ is a String, then
896896
> 1. Let _resolvedMain_ be the URL resolution of _packageURL_, "/", and
897897
> _pjson.main_.

lib/internal/bootstrap/loaders.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,10 @@ NativeModule.prototype.compileForPublicLoader = function(needToSyncExports) {
220220
this.compile();
221221
if (needToSyncExports) {
222222
if (!this.exportKeys) {
223-
this.exportKeys = Object.keys(this.exports);
223+
// When using --expose-internals, we do not want to reflect the named
224+
// exports from core modules as this can trigger unnecessary getters.
225+
const internal = this.id.startsWith('internal/');
226+
this.exportKeys = internal ? [] : Object.keys(this.exports);
224227
}
225228
this.getESMFacade();
226229
this.syncExports();

lib/internal/bootstrap/pre_execution.js

+60-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { Object, SafeWeakMap } = primordials;
55
const { getOptionValue } = require('internal/options');
66
const { Buffer } = require('buffer');
77
const { ERR_MANIFEST_ASSERT_INTEGRITY } = require('internal/errors').codes;
8+
const path = require('path');
89

910
function prepareMainThreadExecution(expandArgv1 = false) {
1011
// Patch the process object with legacy properties and normalizations
@@ -404,7 +405,6 @@ function initializeESMLoader() {
404405
'The ESM module loader is experimental.',
405406
'ExperimentalWarning', undefined);
406407
}
407-
408408
const {
409409
setImportModuleDynamicallyCallback,
410410
setInitializeImportMetaObjectCallback
@@ -414,14 +414,6 @@ function initializeESMLoader() {
414414
// track of for different ESM modules.
415415
setInitializeImportMetaObjectCallback(esm.initializeImportMetaObject);
416416
setImportModuleDynamicallyCallback(esm.importModuleDynamicallyCallback);
417-
const userLoader = getOptionValue('--experimental-loader');
418-
// If --experimental-loader is specified, create a loader with user hooks.
419-
// Otherwise create the default loader.
420-
if (userLoader) {
421-
const { emitExperimentalWarning } = require('internal/util');
422-
emitExperimentalWarning('--experimental-loader');
423-
}
424-
esm.initializeLoader(process.cwd(), userLoader);
425417
}
426418
}
427419

@@ -446,11 +438,70 @@ function loadPreloadModules() {
446438
}
447439
}
448440

441+
function resolveMainPath(main) {
442+
const { toRealPath, Module: CJSModule } =
443+
require('internal/modules/cjs/loader');
444+
445+
// Note extension resolution for the main entry point can be deprecated in a
446+
// future major.
447+
let mainPath = CJSModule._findPath(path.resolve(main), null, true);
448+
if (!mainPath)
449+
return;
450+
451+
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
452+
if (!preserveSymlinksMain)
453+
mainPath = toRealPath(mainPath);
454+
455+
return mainPath;
456+
}
457+
458+
function shouldUseESMLoader(mainPath) {
459+
const experimentalModules = getOptionValue('--experimental-modules');
460+
if (!experimentalModules)
461+
return false;
462+
const userLoader = getOptionValue('--experimental-loader');
463+
if (userLoader)
464+
return true;
465+
// Determine the module format of the main
466+
if (mainPath && mainPath.endsWith('.mjs'))
467+
return true;
468+
if (!mainPath || mainPath.endsWith('.cjs'))
469+
return false;
470+
const { readPackageScope } = require('internal/modules/cjs/loader');
471+
const pkg = readPackageScope(mainPath);
472+
return pkg && pkg.data.type === 'module';
473+
}
474+
475+
function runMainESM(mainPath) {
476+
const esmLoader = require('internal/process/esm_loader');
477+
const { pathToFileURL } = require('internal/url');
478+
const { hasUncaughtExceptionCaptureCallback } =
479+
require('internal/process/execution');
480+
return esmLoader.initializeLoader().then(() => {
481+
const main = path.isAbsolute(mainPath) ?
482+
pathToFileURL(mainPath).href : mainPath;
483+
return esmLoader.ESMLoader.import(main).catch((e) => {
484+
if (hasUncaughtExceptionCaptureCallback()) {
485+
process._fatalException(e);
486+
return;
487+
}
488+
internalBinding('errors').triggerUncaughtException(
489+
e,
490+
true /* fromPromise */
491+
);
492+
});
493+
});
494+
}
495+
496+
449497
module.exports = {
450498
patchProcessObject,
499+
resolveMainPath,
500+
runMainESM,
451501
setupCoverageHooks,
452502
setupWarningHandler,
453503
setupDebugEnv,
504+
shouldUseESMLoader,
454505
prepareMainThreadExecution,
455506
initializeDeprecations,
456507
initializeESMLoader,

lib/internal/main/run_main_module.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ const CJSModule = require('internal/modules/cjs/loader').Module;
1010

1111
markBootstrapComplete();
1212

13-
// Note: this actually tries to run the module as a ESM first if
14-
// --experimental-modules is on.
15-
// TODO(joyeecheung): can we move that logic to here? Note that this
16-
// is an undocumented method available via `require('module').runMain`
17-
CJSModule.runMain();
13+
// Note: this loads the module through the ESM loader if
14+
// --experimental-loader is provided or --experimental-modules is on
15+
// and the module is determined to be an ES module
16+
CJSModule.runMain(process.argv[1]);

lib/internal/main/worker_thread.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,9 @@ port.on('message', (message) => {
135135
const { evalScript } = require('internal/process/execution');
136136
evalScript('[worker eval]', filename);
137137
} else {
138-
process.argv[1] = filename; // script filename
139-
require('module').runMain();
138+
// script filename
139+
const CJSModule = require('internal/modules/cjs/loader').Module;
140+
CJSModule.runMain(process.argv[1] = filename);
140141
}
141142
} else if (message.type === STDIO_PAYLOAD) {
142143
const { stream, chunk, encoding } = message;

lib/internal/modules/cjs/loader.js

+36-31
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,15 @@ const {
6969
ERR_REQUIRE_ESM
7070
} = require('internal/errors').codes;
7171
const { validateString } = require('internal/validators');
72+
const {
73+
resolveMainPath,
74+
shouldUseESMLoader,
75+
runMainESM
76+
} = require('internal/bootstrap/pre_execution');
7277
const pendingDeprecation = getOptionValue('--pending-deprecation');
7378
const experimentalExports = getOptionValue('--experimental-exports');
7479

75-
module.exports = { wrapSafe, Module };
80+
module.exports = { wrapSafe, Module, toRealPath, readPackageScope };
7681

7782
let asyncESM, ModuleJob, ModuleWrap, kInstantiated;
7883

@@ -807,6 +812,10 @@ Module.prototype.load = function(filename) {
807812
this.paths = Module._nodeModulePaths(path.dirname(filename));
808813

809814
const extension = findLongestRegisteredExtension(filename);
815+
// allow .mjs to be overridden
816+
if (filename.endsWith('.mjs') && !Module._extensions['.mjs']) {
817+
throw new ERR_REQUIRE_ESM(filename);
818+
}
810819
Module._extensions[extension](this, filename);
811820
this.loaded = true;
812821

@@ -820,14 +829,19 @@ Module.prototype.load = function(filename) {
820829
if (module !== undefined && module.module !== undefined) {
821830
if (module.module.getStatus() >= kInstantiated)
822831
module.module.setExport('default', exports);
823-
} else { // preemptively cache
832+
} else {
833+
// Preemptively cache
834+
// We use a function to defer promise creation for async hooks.
824835
ESMLoader.moduleMap.set(
825836
url,
826-
new ModuleJob(ESMLoader, url, () =>
837+
// Module job creation will start promises.
838+
// We make it a function to lazily trigger those promises
839+
// for async hooks compatibility.
840+
() => new ModuleJob(ESMLoader, url, () =>
827841
new ModuleWrap(url, undefined, ['default'], function() {
828842
this.setExport('default', exports);
829843
})
830-
)
844+
, false /* isMain */, false /* inspectBrk */)
831845
);
832846
}
833847
}
@@ -856,20 +870,19 @@ Module.prototype.require = function(id) {
856870
let resolvedArgv;
857871
let hasPausedEntry = false;
858872

859-
function wrapSafe(filename, content) {
873+
function wrapSafe(filename, content, cjsModuleInstance) {
860874
if (patched) {
861875
const wrapper = Module.wrap(content);
862876
return vm.runInThisContext(wrapper, {
863877
filename,
864878
lineOffset: 0,
865879
displayErrors: true,
866880
importModuleDynamically: experimentalModules ? async (specifier) => {
867-
const loader = await asyncESM.loaderPromise;
881+
const loader = asyncESM.ESMLoader;
868882
return loader.import(specifier, normalizeReferrerURL(filename));
869883
} : undefined,
870884
});
871885
}
872-
873886
let compiled;
874887
try {
875888
compiled = compileFunction(
@@ -890,17 +903,16 @@ function wrapSafe(filename, content) {
890903
]
891904
);
892905
} catch (err) {
893-
if (experimentalModules) {
906+
if (experimentalModules && process.mainModule === cjsModuleInstance)
894907
enrichCJSError(err);
895-
}
896908
throw err;
897909
}
898910

899911
if (experimentalModules) {
900912
const { callbackMap } = internalBinding('module_wrap');
901913
callbackMap.set(compiled.cacheKey, {
902914
importModuleDynamically: async (specifier) => {
903-
const loader = await asyncESM.loaderPromise;
915+
const loader = asyncESM.ESMLoader;
904916
return loader.import(specifier, normalizeReferrerURL(filename));
905917
}
906918
});
@@ -923,7 +935,7 @@ Module.prototype._compile = function(content, filename) {
923935
}
924936

925937
maybeCacheSourceMap(filename, content, this);
926-
const compiledWrapper = wrapSafe(filename, content);
938+
const compiledWrapper = wrapSafe(filename, content, this);
927939

928940
var inspectorWrapper = null;
929941
if (getOptionValue('--inspect-brk') && process._eval == null) {
@@ -979,7 +991,11 @@ Module._extensions['.js'] = function(module, filename) {
979991
'files in that package scope as ES modules.\nInstead rename ' +
980992
`${basename} to end in .cjs, change the requiring code to use ` +
981993
'import(), or remove "type": "module" from ' +
982-
`${path.resolve(pkg.path, 'package.json')}.`
994+
`${path.resolve(pkg.path, 'package.json')}.`,
995+
undefined,
996+
undefined,
997+
undefined,
998+
true
983999
);
9841000
warnRequireESM = false;
9851001
}
@@ -1022,26 +1038,15 @@ Module._extensions['.node'] = function(module, filename) {
10221038
return process.dlopen(module, path.toNamespacedPath(filename));
10231039
};
10241040

1025-
Module._extensions['.mjs'] = function(module, filename) {
1026-
throw new ERR_REQUIRE_ESM(filename);
1027-
};
1028-
10291041
// Bootstrap main module.
1030-
Module.runMain = function() {
1031-
// Load the main module--the command line argument.
1032-
if (experimentalModules) {
1033-
asyncESM.loaderPromise.then((loader) => {
1034-
return loader.import(pathToFileURL(process.argv[1]).href);
1035-
})
1036-
.catch((e) => {
1037-
internalBinding('errors').triggerUncaughtException(
1038-
e,
1039-
true /* fromPromise */
1040-
);
1041-
});
1042-
return;
1042+
Module.runMain = function(main = process.argv[1]) {
1043+
const resolvedMain = resolveMainPath(main);
1044+
const useESMLoader = shouldUseESMLoader(resolvedMain);
1045+
if (useESMLoader) {
1046+
runMainESM(resolvedMain || main);
1047+
} else {
1048+
Module._load(main, null, true);
10431049
}
1044-
Module._load(process.argv[1], null, true);
10451050
};
10461051

10471052
function createRequireFromPath(filename) {
@@ -1147,7 +1152,7 @@ Module.Module = Module;
11471152

11481153
// We have to load the esm things after module.exports!
11491154
if (experimentalModules) {
1150-
asyncESM = require('internal/process/esm_loader');
11511155
ModuleJob = require('internal/modules/esm/module_job');
1156+
asyncESM = require('internal/process/esm_loader');
11521157
({ ModuleWrap, kInstantiated } = internalBinding('module_wrap'));
11531158
}

lib/internal/modules/esm/loader.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const createDynamicModule = require(
2222
'internal/modules/esm/create_dynamic_module');
2323
const { translators } = require('internal/modules/esm/translators');
2424
const { ModuleWrap } = internalBinding('module_wrap');
25+
const { getOptionValue } = require('internal/options');
2526

2627
const debug = require('internal/util/debuglog').debuglog('esm');
2728

@@ -118,7 +119,7 @@ class Loader {
118119
url = pathToFileURL(`${process.cwd()}/[eval${++this.evalIndex}]`).href
119120
) {
120121
const evalInstance = (url) => new ModuleWrap(url, undefined, source, 0, 0);
121-
const job = new ModuleJob(this, url, evalInstance, false);
122+
const job = new ModuleJob(this, url, evalInstance, false, false);
122123
this.moduleMap.set(url, job);
123124
const { module, result } = await job.run();
124125
return {
@@ -146,6 +147,9 @@ class Loader {
146147
async getModuleJob(specifier, parentURL) {
147148
const { url, format } = await this.resolve(specifier, parentURL);
148149
let job = this.moduleMap.get(url);
150+
// CommonJS will set functions for lazy job evaluation.
151+
if (typeof job === 'function')
152+
this.moduleMap.set(url, job = job());
149153
if (job !== undefined)
150154
return job;
151155

@@ -169,7 +173,10 @@ class Loader {
169173
loaderInstance = translators.get(format);
170174
}
171175

172-
job = new ModuleJob(this, url, loaderInstance, parentURL === undefined);
176+
const inspectBrk = parentURL === undefined &&
177+
format === 'module' && getOptionValue('--inspect-brk');
178+
job = new ModuleJob(this, url, loaderInstance, parentURL === undefined,
179+
inspectBrk);
173180
this.moduleMap.set(url, job);
174181
return job;
175182
}

lib/internal/modules/esm/module_job.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ const {
99
const { ModuleWrap } = internalBinding('module_wrap');
1010

1111
const { decorateErrorStack } = require('internal/util');
12-
const { getOptionValue } = require('internal/options');
1312
const assert = require('internal/assert');
1413
const resolvedPromise = SafePromise.resolve();
1514

@@ -22,9 +21,10 @@ let hasPausedEntry = false;
2221
class ModuleJob {
2322
// `loader` is the Loader instance used for loading dependencies.
2423
// `moduleProvider` is a function
25-
constructor(loader, url, moduleProvider, isMain) {
24+
constructor(loader, url, moduleProvider, isMain, inspectBrk) {
2625
this.loader = loader;
2726
this.isMain = isMain;
27+
this.inspectBrk = inspectBrk;
2828

2929
// This is a Promise<{ module, reflect }>, whose fields will be copied
3030
// onto `this` by `link()` below once it has been resolved.
@@ -83,12 +83,12 @@ class ModuleJob {
8383
};
8484
await addJobsToDependencyGraph(this);
8585
try {
86-
if (!hasPausedEntry && this.isMain && getOptionValue('--inspect-brk')) {
86+
if (!hasPausedEntry && this.inspectBrk) {
8787
hasPausedEntry = true;
8888
const initWrapper = internalBinding('inspector').callAndPauseOnStart;
8989
initWrapper(this.module.instantiate, this.module);
9090
} else {
91-
this.module.instantiate();
91+
this.module.instantiate(true);
9292
}
9393
} catch (e) {
9494
decorateErrorStack(e);

lib/internal/modules/esm/module_map.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ class ModuleMap extends SafeMap {
1616
}
1717
set(url, job) {
1818
validateString(url, 'url');
19-
if (job instanceof ModuleJob !== true) {
19+
if (job instanceof ModuleJob !== true &&
20+
typeof job !== 'function') {
2021
throw new ERR_INVALID_ARG_TYPE('job', 'ModuleJob', job);
2122
}
2223
debug(`Storing ${url} in ModuleMap`);

0 commit comments

Comments
 (0)