Skip to content

Commit f3e3429

Browse files
guybedfordMylesBorins
authored andcommitted
module: support main w/o extension, pjson cache
This adds support for ensuring that the top-level main into Node is supported loading when it has no extension for backwards-compat with NodeJS bin workflows. In addition package.json caching is implemented in the module lookup process. Backport-PR-URL: #18923 PR-URL: #18728 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
1 parent 50d1233 commit f3e3429

15 files changed

Lines changed: 216 additions & 133 deletions

File tree

‎doc/api/esm.md‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,22 @@ The resolve hook returns the resolved file URL and module format for a
113113
given module specifier and parent file URL:
114114

115115
```js
116-
importurlfrom'url';
116+
constbaseURL=newURL('file://');
117+
baseURL.pathname=process.cwd() +'/';
117118

118-
exportasyncfunctionresolve(specifier, parentModuleURL, defaultResolver) {
119+
exportasyncfunctionresolve(specifier,
120+
parentModuleURL=baseURL,
121+
defaultResolver) {
119122
return {
120123
url:newURL(specifier, parentModuleURL).href,
121124
format:'esm'
122125
};
123126
}
124127
```
125128

126-
The default NodeJS ES module resolution function is provided as a third
129+
The parentURL is provided as `undefined` when performing main Node.js load itself.
130+
131+
The default Node.js ES module resolution function is provided as a third
127132
argument to the resolver for easy compatibility workflows.
128133

129134
In addition to returning the resolved file URL value, the resolve hook also
@@ -152,7 +157,10 @@ import Module from 'module';
152157
constbuiltins=Module.builtinModules;
153158
constJS_EXTENSIONS=newSet(['.js', '.mjs']);
154159

155-
exportfunctionresolve(specifier, parentModuleURL/*, defaultResolve */) {
160+
constbaseURL=newURL('file://');
161+
baseURL.pathname=process.cwd() +'/';
162+
163+
exportfunctionresolve(specifier, parentModuleURL=baseURL, defaultResolve) {
156164
if (builtins.includes(specifier)) {
157165
return {
158166
url: specifier,

‎lib/internal/bootstrap_node.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
process.emitWarning(
106106
'The ESM module loader is experimental.',
107107
'ExperimentalWarning',undefined);
108+
NativeModule.require('internal/process/modules').setup();
108109
}
109110

110111

‎lib/internal/loader/DefaultResolve.js‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
const{URL}=require('url');
44
constCJSmodule=require('module');
5-
constinternalURLModule=require('internal/url');
65
constinternalFS=require('internal/fs');
76
constNativeModule=require('native_module');
87
const{ extname }=require('path');
@@ -11,6 +10,7 @@ const preserveSymlinks = !!process.binding('config').preserveSymlinks;
1110
consterrors=require('internal/errors');
1211
const{resolve: moduleWrapResolve}=internalBinding('module_wrap');
1312
constStringStartsWith=Function.call.bind(String.prototype.startsWith);
13+
const{ getURLFromFilePath, getPathFromURL }=require('internal/url');
1414

1515
constrealpathCache=newMap();
1616

@@ -57,7 +57,8 @@ function resolve(specifier, parentURL) {
5757

5858
leturl;
5959
try{
60-
url=search(specifier,parentURL);
60+
url=search(specifier,
61+
parentURL||getURLFromFilePath(`${process.cwd()}/`).href);
6162
}catch(e){
6263
if(typeofe.message==='string'&&
6364
StringStartsWith(e.message,'Cannot find module'))
@@ -66,17 +67,27 @@ function resolve(specifier, parentURL) {
6667
}
6768

6869
if(!preserveSymlinks){
69-
constreal=realpathSync(internalURLModule.getPathFromURL(url),{
70+
constreal=realpathSync(getPathFromURL(url),{
7071
[internalFS.realpathCacheKey]: realpathCache
7172
});
7273
constold=url;
73-
url=internalURLModule.getURLFromFilePath(real);
74+
url=getURLFromFilePath(real);
7475
url.search=old.search;
7576
url.hash=old.hash;
7677
}
7778

7879
constext=extname(url.pathname);
79-
return{url: `${url}`,format: extensionFormatMap[ext]||ext};
80+
81+
letformat=extensionFormatMap[ext];
82+
if(!format){
83+
constisMain=parentURL===undefined;
84+
if(isMain)
85+
format='cjs';
86+
else
87+
thrownewerrors.Error('ERR_UNKNOWN_FILE_EXTENSION',url.pathname);
88+
}
89+
90+
return{url: `${url}`, format };
8091
}
8192

8293
module.exports=resolve;

‎lib/internal/loader/Loader.js‎

Lines changed: 11 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,21 @@
11
'use strict';
22

3-
constpath=require('path');
4-
const{ getURLFromFilePath,URL}=require('internal/url');
53
consterrors=require('internal/errors');
6-
74
constModuleMap=require('internal/loader/ModuleMap');
85
constModuleJob=require('internal/loader/ModuleJob');
96
constdefaultResolve=require('internal/loader/DefaultResolve');
107
constcreateDynamicModule=require('internal/loader/CreateDynamicModule');
118
consttranslators=require('internal/loader/Translators');
12-
const{ setImportModuleDynamicallyCallback }=internalBinding('module_wrap');
9+
1310
constFunctionBind=Function.call.bind(Function.prototype.bind);
1411

1512
constdebug=require('util').debuglog('esm');
1613

17-
// Returns a file URL for the current working directory.
18-
functiongetURLStringForCwd(){
19-
try{
20-
returngetURLFromFilePath(`${process.cwd()}/`).href;
21-
}catch(e){
22-
e.stack;
23-
// If the current working directory no longer exists.
24-
if(e.code==='ENOENT'){
25-
returnundefined;
26-
}
27-
throwe;
28-
}
29-
}
30-
31-
functionnormalizeReferrerURL(referrer){
32-
if(typeofreferrer==='string'&&path.isAbsolute(referrer)){
33-
returngetURLFromFilePath(referrer).href;
34-
}
35-
returnnewURL(referrer).href;
36-
}
37-
3814
/* A Loader instance is used as the main entry point for loading ES modules.
3915
* Currently, this is a singleton -- there is only one used for loading
4016
* the main module and everything in its dependency graph. */
4117
classLoader{
42-
constructor(base=getURLStringForCwd()){
43-
if(typeofbase!=='string')
44-
thrownewerrors.TypeError('ERR_INVALID_ARG_TYPE','base','string');
45-
46-
this.base=base;
47-
this.isMain=true;
48-
18+
constructor(){
4919
// methods which translate input code or other information
5020
// into es modules
5121
this.translators=translators;
@@ -71,8 +41,9 @@ class Loader {
7141
this._dynamicInstantiate=undefined;
7242
}
7343

74-
asyncresolve(specifier,parentURL=this.base){
75-
if(typeofparentURL!=='string')
44+
asyncresolve(specifier,parentURL){
45+
constisMain=parentURL===undefined;
46+
if(!isMain&&typeofparentURL!=='string')
7647
thrownewerrors.TypeError('ERR_INVALID_ARG_TYPE','parentURL','string');
7748

7849
const{ url, format }=
@@ -93,7 +64,7 @@ class Loader {
9364
return{ url, format };
9465
}
9566

96-
asyncimport(specifier,parent=this.base){
67+
asyncimport(specifier,parent){
9768
constjob=awaitthis.getModuleJob(specifier,parent);
9869
constmodule=awaitjob.run();
9970
returnmodule.namespace();
@@ -107,7 +78,7 @@ class Loader {
10778
this._dynamicInstantiate=FunctionBind(dynamicInstantiate,null);
10879
}
10980

110-
asyncgetModuleJob(specifier,parentURL=this.base){
81+
asyncgetModuleJob(specifier,parentURL){
11182
const{ url, format }=awaitthis.resolve(specifier,parentURL);
11283
letjob=this.moduleMap.get(url);
11384
if(job!==undefined)
@@ -134,24 +105,16 @@ class Loader {
134105
}
135106

136107
letinspectBrk=false;
137-
if(this.isMain){
138-
if(process._breakFirstLine){
139-
deleteprocess._breakFirstLine;
140-
inspectBrk=true;
141-
}
142-
this.isMain=false;
108+
if(process._breakFirstLine){
109+
deleteprocess._breakFirstLine;
110+
inspectBrk=true;
143111
}
144112
job=newModuleJob(this,url,loaderInstance,inspectBrk);
145113
this.moduleMap.set(url,job);
146114
returnjob;
147115
}
148-
149-
staticregisterImportDynamicallyCallback(loader){
150-
setImportModuleDynamicallyCallback(async(referrer,specifier)=>{
151-
returnloader.import(specifier,normalizeReferrerURL(referrer));
152-
});
153-
}
154116
}
155117

156118
Object.setPrototypeOf(Loader.prototype,null);
119+
157120
module.exports=Loader;

‎lib/internal/loader/Translators.js‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const JsonParse = JSON.parse;
1919
consttranslators=newSafeMap();
2020
module.exports=translators;
2121

22-
// Stragety for loading a standard JavaScript module
22+
// Strategy for loading a standard JavaScript module
2323
translators.set('esm',async(url)=>{
2424
constsource=`${awaitreadFileAsync(newURL(url))}`;
2525
debug(`Translating StandardModule ${url}`);
@@ -62,7 +62,7 @@ translators.set('builtin', async (url) => {
6262
});
6363
});
6464

65-
// Stragety for loading a node native module
65+
// Strategy for loading a node native module
6666
translators.set('addon',async(url)=>{
6767
debug(`Translating NativeModule ${url}`);
6868
returncreateDynamicModule(['default'],url,(reflect)=>{
@@ -74,7 +74,7 @@ translators.set('addon', async (url) => {
7474
});
7575
});
7676

77-
// Stragety for loading a JSON file
77+
// Strategy for loading a JSON file
7878
translators.set('json',async(url)=>{
7979
debug(`Translating JSONModule ${url}`);
8080
returncreateDynamicModule(['default'],url,(reflect)=>{

‎lib/internal/process/modules.js‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
const{
4+
setImportModuleDynamicallyCallback
5+
}=internalBinding('module_wrap');
6+
7+
const{ getURLFromFilePath }=require('internal/url');
8+
constLoader=require('internal/loader/Loader');
9+
constpath=require('path');
10+
const{URL}=require('url');
11+
12+
functionnormalizeReferrerURL(referrer){
13+
if(typeofreferrer==='string'&&path.isAbsolute(referrer)){
14+
returngetURLFromFilePath(referrer).href;
15+
}
16+
returnnewURL(referrer).href;
17+
}
18+
19+
letloaderResolve;
20+
exports.loaderPromise=newPromise((resolve,reject)=>{
21+
loaderResolve=resolve;
22+
});
23+
24+
exports.ESMLoader=undefined;
25+
26+
exports.setup=function(){
27+
letESMLoader=newLoader();
28+
constloaderPromise=(async()=>{
29+
constuserLoader=process.binding('config').userLoader;
30+
if(userLoader){
31+
consthooks=awaitESMLoader.import(
32+
userLoader,getURLFromFilePath(`${process.cwd()}/`).href);
33+
ESMLoader=newLoader();
34+
ESMLoader.hook(hooks);
35+
exports.ESMLoader=ESMLoader;
36+
}
37+
returnESMLoader;
38+
})();
39+
loaderResolve(loaderPromise);
40+
41+
setImportModuleDynamicallyCallback(async(referrer,specifier)=>{
42+
constloader=awaitloaderPromise;
43+
returnloader.import(specifier,normalizeReferrerURL(referrer));
44+
});
45+
46+
exports.ESMLoader=ESMLoader;
47+
};

‎lib/module.js‎

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
constNativeModule=require('native_module');
2525
constutil=require('util');
2626
const{ decorateErrorStack }=require('internal/util');
27-
constinternalModule=require('internal/module');
2827
const{ getURLFromFilePath }=require('internal/url');
2928
constvm=require('vm');
3029
constassert=require('assert').ok;
@@ -35,6 +34,7 @@ const {
3534
internalModuleReadFile,
3635
internalModuleStat
3736
}=process.binding('fs');
37+
constinternalModule=require('internal/module');
3838
constpreserveSymlinks=!!process.binding('config').preserveSymlinks;
3939
constexperimentalModules=!!process.binding('config').experimentalModules;
4040

@@ -43,10 +43,9 @@ const errors = require('internal/errors');
4343
module.exports=Module;
4444

4545
// these are below module.exports for the circular reference
46-
constLoader=require('internal/loader/Loader');
46+
constinternalESModule=require('internal/process/modules');
4747
constModuleJob=require('internal/loader/ModuleJob');
4848
constcreateDynamicModule=require('internal/loader/CreateDynamicModule');
49-
letESMLoader;
5049

5150
functionstat(filename){
5251
filename=path.toNamespacedPath(filename);
@@ -444,7 +443,6 @@ Module._resolveLookupPaths = function(request, parent, newReturn) {
444443
return(newReturn ? parentDir : [id,parentDir]);
445444
};
446445

447-
448446
// Check the cache for the requested file.
449447
// 1. If a module already exists in the cache: return its exports object.
450448
// 2. If the module is native: call `NativeModule.require()` with the
@@ -457,22 +455,10 @@ Module._load = function(request, parent, isMain) {
457455
debug('Module._load REQUEST %s parent: %s',request,parent.id);
458456
}
459457

460-
if(isMain&&experimentalModules){
461-
(async()=>{
462-
// loader setup
463-
if(!ESMLoader){
464-
ESMLoader=newLoader();
465-
constuserLoader=process.binding('config').userLoader;
466-
if(userLoader){
467-
ESMLoader.isMain=false;
468-
consthooks=awaitESMLoader.import(userLoader);
469-
ESMLoader=newLoader();
470-
ESMLoader.hook(hooks);
471-
}
472-
}
473-
Loader.registerImportDynamicallyCallback(ESMLoader);
474-
awaitESMLoader.import(getURLFromFilePath(request).pathname);
475-
})()
458+
if(experimentalModules&&isMain){
459+
internalESModule.loaderPromise.then((loader)=>{
460+
returnloader.import(getURLFromFilePath(request).pathname);
461+
})
476462
.catch((e)=>{
477463
decorateErrorStack(e);
478464
console.error(e);
@@ -575,7 +561,8 @@ Module.prototype.load = function(filename) {
575561
Module._extensions[extension](this,filename);
576562
this.loaded=true;
577563

578-
if(ESMLoader){
564+
if(experimentalModules){
565+
constESMLoader=internalESModule.ESMLoader;
579566
consturl=getURLFromFilePath(filename);
580567
consturlString=`${url}`;
581568
constexports=this.exports;

‎node.gyp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
'lib/internal/net.js',
116116
'lib/internal/module.js',
117117
'lib/internal/os.js',
118+
'lib/internal/process/modules.js',
118119
'lib/internal/process/next_tick.js',
119120
'lib/internal/process/promises.js',
120121
'lib/internal/process/stdio.js',

0 commit comments

Comments
 (0)