Skip to content

Commit 02c4d27

Browse files
bfarias-godaddyBethGriggs
authored andcommitted
module: refactor modules bootstrap
PR-URL: #29937 Reviewed-By: Myles Borins <myles.borins@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent a5d2b66 commit 02c4d27

17 files changed

Lines changed: 181 additions & 102 deletions

‎doc/api/esm.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -905,13 +905,13 @@ _isMain_ is **true** when resolving the Node.js application entry point.
905905
> 1. Throw a _Module Not Found_ error.
906906
> 1. If _pjson.exports_ is not **null** or **undefined**, then
907907
> 1. If _pjson.exports_ is a String or Array, then
908-
> 1. Return _PACKAGE_EXPORTS_TARGET_RESOLVE(packageURL, pjson.exports,
909-
> "")_.
908+
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_,
909+
> _pjson.exports_, "")_.
910910
> 1. If _pjson.exports is an Object, then
911911
> 1. If _pjson.exports_ contains a _"."_ property, then
912912
> 1. Let _mainExport_ be the _"."_ property in _pjson.exports_.
913-
> 1. Return _PACKAGE_EXPORTS_TARGET_RESOLVE(packageURL, mainExport,
914-
> "")_.
913+
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_,
914+
> _mainExport_, "")_.
915915
> 1. If _pjson.main_ is a String, then
916916
> 1. Let _resolvedMain_ be the URL resolution of _packageURL_, "/", and
917917
> _pjson.main_.

‎lib/internal/bootstrap/loaders.js‎

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

‎lib/internal/bootstrap/pre_execution.js‎

Lines changed: 60 additions & 9 deletions
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+
constpath=require('path');
89

910
functionprepareMainThreadExecution(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-
constuserLoader=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+
functionresolveMainPath(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+
letmainPath=CJSModule._findPath(path.resolve(main),null,true);
448+
if(!mainPath)
449+
return;
450+
451+
constpreserveSymlinksMain=getOptionValue('--preserve-symlinks-main');
452+
if(!preserveSymlinksMain)
453+
mainPath=toRealPath(mainPath);
454+
455+
returnmainPath;
456+
}
457+
458+
functionshouldUseESMLoader(mainPath){
459+
constexperimentalModules=getOptionValue('--experimental-modules');
460+
if(!experimentalModules)
461+
returnfalse;
462+
constuserLoader=getOptionValue('--experimental-loader');
463+
if(userLoader)
464+
returntrue;
465+
// Determine the module format of the main
466+
if(mainPath&&mainPath.endsWith('.mjs'))
467+
returntrue;
468+
if(!mainPath||mainPath.endsWith('.cjs'))
469+
returnfalse;
470+
const{ readPackageScope }=require('internal/modules/cjs/loader');
471+
constpkg=readPackageScope(mainPath);
472+
returnpkg&&pkg.data.type==='module';
473+
}
474+
475+
functionrunMainESM(mainPath){
476+
constesmLoader=require('internal/process/esm_loader');
477+
const{ pathToFileURL }=require('internal/url');
478+
const{ hasUncaughtExceptionCaptureCallback }=
479+
require('internal/process/execution');
480+
returnesmLoader.initializeLoader().then(()=>{
481+
constmain=path.isAbsolute(mainPath) ?
482+
pathToFileURL(mainPath).href : mainPath;
483+
returnesmLoader.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‎

Lines changed: 4 additions & 5 deletions
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‎

Lines changed: 3 additions & 2 deletions
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+
constCJSModule=require('internal/modules/cjs/loader').Module;
140+
CJSModule.runMain(process.argv[1]=filename);
140141
}
141142
}elseif(message.type===STDIO_PAYLOAD){
142143
const{ stream, chunk, encoding }=message;

‎lib/internal/modules/cjs/loader.js‎

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,14 @@ const {
7070
ERR_REQUIRE_ESM
7171
}=require('internal/errors').codes;
7272
const{ validateString }=require('internal/validators');
73+
const{
74+
resolveMainPath,
75+
shouldUseESMLoader,
76+
runMainESM
77+
}=require('internal/bootstrap/pre_execution');
7378
constpendingDeprecation=getOptionValue('--pending-deprecation');
7479

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

7782
letasyncESM,ModuleJob,ModuleWrap,kInstantiated;
7883

@@ -898,6 +903,10 @@ Module.prototype.load = function(filename) {
898903
this.paths=Module._nodeModulePaths(path.dirname(filename));
899904

900905
constextension=findLongestRegisteredExtension(filename);
906+
// allow .mjs to be overridden
907+
if(filename.endsWith('.mjs')&&!Module._extensions['.mjs']){
908+
thrownewERR_REQUIRE_ESM(filename);
909+
}
901910
Module._extensions[extension](this,filename);
902911
this.loaded=true;
903912

@@ -911,14 +920,19 @@ Module.prototype.load = function(filename) {
911920
if(module!==undefined&&module.module!==undefined){
912921
if(module.module.getStatus()>=kInstantiated)
913922
module.module.setExport('default',exports);
914-
}else{// preemptively cache
923+
}else{
924+
// Preemptively cache
925+
// We use a function to defer promise creation for async hooks.
915926
ESMLoader.moduleMap.set(
916927
url,
917-
newModuleJob(ESMLoader,url,()=>
928+
// Module job creation will start promises.
929+
// We make it a function to lazily trigger those promises
930+
// for async hooks compatibility.
931+
()=>newModuleJob(ESMLoader,url,()=>
918932
newModuleWrap(url,undefined,['default'],function(){
919933
this.setExport('default',exports);
920934
})
921-
)
935+
,false/* isMain */,false/* inspectBrk */)
922936
);
923937
}
924938
}
@@ -947,15 +961,15 @@ Module.prototype.require = function(id) {
947961
letresolvedArgv;
948962
lethasPausedEntry=false;
949963

950-
functionwrapSafe(filename,content){
964+
functionwrapSafe(filename,content,cjsModuleInstance){
951965
if(patched){
952966
constwrapper=Module.wrap(content);
953967
returnvm.runInThisContext(wrapper,{
954968
filename,
955969
lineOffset: 0,
956970
displayErrors: true,
957971
importModuleDynamically: experimentalModules ? async(specifier)=>{
958-
constloader=awaitasyncESM.loaderPromise;
972+
constloader=asyncESM.ESMLoader;
959973
returnloader.import(specifier,normalizeReferrerURL(filename));
960974
} : undefined,
961975
});
@@ -981,17 +995,16 @@ function wrapSafe(filename, content) {
981995
]
982996
);
983997
}catch(err){
984-
if(experimentalModules){
998+
if(experimentalModules&&process.mainModule===cjsModuleInstance)
985999
enrichCJSError(err);
986-
}
9871000
throwerr;
9881001
}
9891002

9901003
if(experimentalModules){
9911004
const{ callbackMap }=internalBinding('module_wrap');
9921005
callbackMap.set(compiled.cacheKey,{
9931006
importModuleDynamically: async(specifier)=>{
994-
constloader=awaitasyncESM.loaderPromise;
1007+
constloader=asyncESM.ESMLoader;
9951008
returnloader.import(specifier,normalizeReferrerURL(filename));
9961009
}
9971010
});
@@ -1014,7 +1027,7 @@ Module.prototype._compile = function(content, filename) {
10141027
}
10151028

10161029
maybeCacheSourceMap(filename,content,this);
1017-
constcompiledWrapper=wrapSafe(filename,content);
1030+
constcompiledWrapper=wrapSafe(filename,content,this);
10181031

10191032
varinspectorWrapper=null;
10201033
if(getOptionValue('--inspect-brk')&&process._eval==null){
@@ -1070,7 +1083,11 @@ Module._extensions['.js'] = function(module, filename) {
10701083
'files in that package scope as ES modules.\nInstead rename '+
10711084
`${basename} to end in .cjs, change the requiring code to use `+
10721085
'import(), or remove "type": "module" from '+
1073-
`${path.resolve(pkg.path,'package.json')}.`
1086+
`${path.resolve(pkg.path,'package.json')}.`,
1087+
undefined,
1088+
undefined,
1089+
undefined,
1090+
true
10741091
);
10751092
warnRequireESM=false;
10761093
}
@@ -1113,26 +1130,16 @@ Module._extensions['.node'] = function(module, filename) {
11131130
returnprocess.dlopen(module,path.toNamespacedPath(filename));
11141131
};
11151132

1116-
Module._extensions['.mjs']=function(module,filename){
1117-
thrownewERR_REQUIRE_ESM(filename);
1118-
};
1119-
11201133
// Bootstrap main module.
1121-
Module.runMain=function(){
1122-
// Load the main module--the command line argument.
1123-
if(experimentalModules){
1124-
asyncESM.loaderPromise.then((loader)=>{
1125-
returnloader.import(pathToFileURL(process.argv[1]).href);
1126-
})
1127-
.catch((e)=>{
1128-
internalBinding('errors').triggerUncaughtException(
1129-
e,
1130-
true/* fromPromise */
1131-
);
1132-
});
1133-
return;
1134+
Module.runMain=function(main=process.argv[1]){
1135+
constresolvedMain=resolveMainPath(main);
1136+
constuseESMLoader=shouldUseESMLoader(resolvedMain);
1137+
module.exports.asyncRunMain=useESMLoader;
1138+
if(useESMLoader){
1139+
runMainESM(resolvedMain||main);
1140+
}else{
1141+
Module._load(main,null,true);
11341142
}
1135-
Module._load(process.argv[1],null,true);
11361143
};
11371144

11381145
functioncreateRequireFromPath(filename){
@@ -1238,7 +1245,7 @@ Module.Module = Module;
12381245

12391246
// We have to load the esm things after module.exports!
12401247
if(experimentalModules){
1241-
asyncESM=require('internal/process/esm_loader');
12421248
ModuleJob=require('internal/modules/esm/module_job');
1249+
asyncESM=require('internal/process/esm_loader');
12431250
({ ModuleWrap, kInstantiated }=internalBinding('module_wrap'));
12441251
}

‎lib/internal/modules/esm/loader.js‎

Lines changed: 9 additions & 2 deletions
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
constdebug=require('internal/util/debuglog').debuglog('esm');
2728

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

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

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

‎lib/internal/modules/esm/module_job.js‎

Lines changed: 4 additions & 4 deletions
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
constassert=require('internal/assert');
1413
constresolvedPromise=SafePromise.resolve();
1514

@@ -22,9 +21,10 @@ let hasPausedEntry = false;
2221
classModuleJob{
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
awaitaddJobsToDependencyGraph(this);
8585
try{
86-
if(!hasPausedEntry&&this.isMain&&getOptionValue('--inspect-brk')){
86+
if(!hasPausedEntry&&this.inspectBrk){
8787
hasPausedEntry=true;
8888
constinitWrapper=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‎

Lines changed: 2 additions & 1 deletion
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(jobinstanceofModuleJob!==true){
19+
if(jobinstanceofModuleJob!==true&&
20+
typeofjob!=='function'){
2021
thrownewERR_INVALID_ARG_TYPE('job','ModuleJob',job);
2122
}
2223
debug(`Storing ${url} in ModuleMap`);

0 commit comments

Comments
 (0)