Skip to content

Commit cfd45eb

Browse files
bfarias-godaddyMylesBorins
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 62b4ca6 commit cfd45eb

17 files changed

Lines changed: 176 additions & 101 deletions

‎doc/api/esm.md‎

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

‎lib/internal/bootstrap/loaders.js‎

Lines changed: 4 additions & 1 deletion
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+
constinternal=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‎

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
@@ -140,8 +140,9 @@ port.on('message', (message) => {
140140
const{ evalScript }=require('internal/process/execution');
141141
evalScript('[worker eval]',filename);
142142
}else{
143-
process.argv[1]=filename;// script filename
144-
require('module').runMain();
143+
// script filename
144+
constCJSModule=require('internal/modules/cjs/loader').Module;
145+
CJSModule.runMain(process.argv[1]=filename);
145146
}
146147
}elseif(message.type===STDIO_PAYLOAD){
147148
const{ stream, chunk, encoding }=message;

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

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,14 @@ 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
constpendingDeprecation=getOptionValue('--pending-deprecation');
7378

74-
module.exports={ wrapSafe, Module };
79+
module.exports={ wrapSafe, Module, toRealPath, readPackageScope};
7580

7681
letasyncESM,ModuleJob,ModuleWrap,kInstantiated;
7782

@@ -810,6 +815,10 @@ Module.prototype.load = function(filename) {
810815
this.paths=Module._nodeModulePaths(path.dirname(filename));
811816

812817
constextension=findLongestRegisteredExtension(filename);
818+
// allow .mjs to be overridden
819+
if(filename.endsWith('.mjs')&&!Module._extensions['.mjs']){
820+
thrownewERR_REQUIRE_ESM(filename);
821+
}
813822
Module._extensions[extension](this,filename);
814823
this.loaded=true;
815824

@@ -823,14 +832,19 @@ Module.prototype.load = function(filename) {
823832
if(module!==undefined&&module.module!==undefined){
824833
if(module.module.getStatus()>=kInstantiated)
825834
module.module.setExport('default',exports);
826-
}else{// preemptively cache
835+
}else{
836+
// Preemptively cache
837+
// We use a function to defer promise creation for async hooks.
827838
ESMLoader.moduleMap.set(
828839
url,
829-
newModuleJob(ESMLoader,url,()=>
840+
// Module job creation will start promises.
841+
// We make it a function to lazily trigger those promises
842+
// for async hooks compatibility.
843+
()=>newModuleJob(ESMLoader,url,()=>
830844
newModuleWrap(url,undefined,['default'],function(){
831845
this.setExport('default',exports);
832846
})
833-
)
847+
,false/* isMain */,false/* inspectBrk */)
834848
);
835849
}
836850
}
@@ -859,15 +873,15 @@ Module.prototype.require = function(id) {
859873
varresolvedArgv;
860874
lethasPausedEntry=false;
861875

862-
functionwrapSafe(filename,content){
876+
functionwrapSafe(filename,content,cjsModuleInstance){
863877
if(patched){
864878
constwrapper=Module.wrap(content);
865879
returnvm.runInThisContext(wrapper,{
866880
filename,
867881
lineOffset: 0,
868882
displayErrors: true,
869883
importModuleDynamically: experimentalModules ? async(specifier)=>{
870-
constloader=awaitasyncESM.loaderPromise;
884+
constloader=asyncESM.ESMLoader;
871885
returnloader.import(specifier,normalizeReferrerURL(filename));
872886
} : undefined,
873887
});
@@ -892,17 +906,16 @@ function wrapSafe(filename, content) {
892906
]
893907
);
894908
}catch(err){
895-
if(experimentalModules){
909+
if(experimentalModules&&process.mainModule===cjsModuleInstance)
896910
enrichCJSError(err);
897-
}
898911
throwerr;
899912
}
900913

901914
if(experimentalModules){
902915
const{ callbackMap }=internalBinding('module_wrap');
903916
callbackMap.set(compiled.cacheKey,{
904917
importModuleDynamically: async(specifier)=>{
905-
constloader=awaitasyncESM.loaderPromise;
918+
constloader=asyncESM.ESMLoader;
906919
returnloader.import(specifier,normalizeReferrerURL(filename));
907920
}
908921
});
@@ -925,7 +938,7 @@ Module.prototype._compile = function(content, filename) {
925938
}
926939

927940
maybeCacheSourceMap(filename,content,this);
928-
constcompiledWrapper=wrapSafe(filename,content);
941+
constcompiledWrapper=wrapSafe(filename,content,this);
929942

930943
varinspectorWrapper=null;
931944
if(getOptionValue('--inspect-brk')&&process._eval==null){
@@ -981,7 +994,11 @@ Module._extensions['.js'] = function(module, filename) {
981994
'files in that package scope as ES modules.\nInstead rename '+
982995
`${basename} to end in .cjs, change the requiring code to use `+
983996
'import(), or remove "type": "module" from '+
984-
`${path.resolve(pkg.path,'package.json')}.`
997+
`${path.resolve(pkg.path,'package.json')}.`,
998+
undefined,
999+
undefined,
1000+
undefined,
1001+
true
9851002
);
9861003
warnRequireESM=false;
9871004
}
@@ -1024,26 +1041,15 @@ Module._extensions['.node'] = function(module, filename) {
10241041
returnprocess.dlopen(module,path.toNamespacedPath(filename));
10251042
};
10261043

1027-
Module._extensions['.mjs']=function(module,filename){
1028-
thrownewERR_REQUIRE_ESM(filename);
1029-
};
1030-
10311044
// Bootstrap main module.
1032-
Module.runMain=function(){
1033-
// Load the main module--the command line argument.
1034-
if(experimentalModules){
1035-
asyncESM.loaderPromise.then((loader)=>{
1036-
returnloader.import(pathToFileURL(process.argv[1]).href);
1037-
})
1038-
.catch((e)=>{
1039-
internalBinding('errors').triggerUncaughtException(
1040-
e,
1041-
true/* fromPromise */
1042-
);
1043-
});
1044-
return;
1045+
Module.runMain=function(main=process.argv[1]){
1046+
constresolvedMain=resolveMainPath(main);
1047+
constuseESMLoader=shouldUseESMLoader(resolvedMain);
1048+
if(useESMLoader){
1049+
runMainESM(resolvedMain||main);
1050+
}else{
1051+
Module._load(main,null,true);
10451052
}
1046-
Module._load(process.argv[1],null,true);
10471053
};
10481054

10491055
functioncreateRequireFromPath(filename){
@@ -1164,7 +1170,7 @@ Module.Module = Module;
11641170

11651171
// We have to load the esm things after module.exports!
11661172
if(experimentalModules){
1167-
asyncESM=require('internal/process/esm_loader');
11681173
ModuleJob=require('internal/modules/esm/module_job');
1174+
asyncESM=require('internal/process/esm_loader');
11691175
({ ModuleWrap, kInstantiated }=internalBinding('module_wrap'));
11701176
}

‎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)