Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f3e3429

Browse filesBrowse 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
Copy full SHA for f3e3429

File tree

Expand file treeCollapse file tree

15 files changed

+216
-133
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

15 files changed

+216
-133
lines changed
Open diff view settings
Collapse file

‎doc/api/esm.md‎

Copy file name to clipboardExpand all lines: doc/api/esm.md
+12-4Lines changed: 12 additions & 4 deletions
  • Display the source diff
  • Display the rich diff
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-
import url from 'url';
116+
const baseURL = new URL('file://');
117+
baseURL.pathname = process.cwd() + '/';
117118

118-
export async function resolve(specifier, parentModuleURL, defaultResolver) {
119+
export async function resolve(specifier,
120+
parentModuleURL = baseURL,
121+
defaultResolver) {
119122
return {
120123
url: new URL(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
const builtins = Module.builtinModules;
153158
const JS_EXTENSIONS = new Set(['.js', '.mjs']);
154159

155-
export function resolve(specifier, parentModuleURL/*, defaultResolve */) {
160+
const baseURL = new URL('file://');
161+
baseURL.pathname = process.cwd() + '/';
162+
163+
export function resolve(specifier, parentModuleURL = baseURL, defaultResolve) {
156164
if (builtins.includes(specifier)) {
157165
return {
158166
url: specifier,
Collapse file

‎lib/internal/bootstrap_node.js‎

Copy file name to clipboardExpand all lines: lib/internal/bootstrap_node.js
+1Lines 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

Collapse file

‎lib/internal/loader/DefaultResolve.js‎

Copy file name to clipboardExpand all lines: lib/internal/loader/DefaultResolve.js
+16-5Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

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

1515
const realpathCache = new Map();
1616

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

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

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

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

8293
module.exports = resolve;
Collapse file

‎lib/internal/loader/Loader.js‎

Copy file name to clipboardExpand all lines: lib/internal/loader/Loader.js
+11-48Lines changed: 11 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,21 @@
11
'use strict';
22

3-
const path = require('path');
4-
const { getURLFromFilePath, URL } = require('internal/url');
53
const errors = require('internal/errors');
6-
74
const ModuleMap = require('internal/loader/ModuleMap');
85
const ModuleJob = require('internal/loader/ModuleJob');
96
const defaultResolve = require('internal/loader/DefaultResolve');
107
const createDynamicModule = require('internal/loader/CreateDynamicModule');
118
const translators = require('internal/loader/Translators');
12-
const { setImportModuleDynamicallyCallback } = internalBinding('module_wrap');
9+
1310
const FunctionBind = Function.call.bind(Function.prototype.bind);
1411

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

17-
// Returns a file URL for the current working directory.
18-
function getURLStringForCwd() {
19-
try {
20-
return getURLFromFilePath(`${process.cwd()}/`).href;
21-
} catch (e) {
22-
e.stack;
23-
// If the current working directory no longer exists.
24-
if (e.code === 'ENOENT') {
25-
return undefined;
26-
}
27-
throw e;
28-
}
29-
}
30-
31-
function normalizeReferrerURL(referrer) {
32-
if (typeof referrer === 'string' && path.isAbsolute(referrer)) {
33-
return getURLFromFilePath(referrer).href;
34-
}
35-
return new URL(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
class Loader {
42-
constructor(base = getURLStringForCwd()) {
43-
if (typeof base !== 'string')
44-
throw new errors.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-
async resolve(specifier, parentURL = this.base) {
75-
if (typeof parentURL !== 'string')
44+
async resolve(specifier, parentURL) {
45+
const isMain = parentURL === undefined;
46+
if (!isMain && typeof parentURL !== 'string')
7647
throw new errors.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-
async import(specifier, parent = this.base) {
67+
async import(specifier, parent) {
9768
const job = await this.getModuleJob(specifier, parent);
9869
const module = await job.run();
9970
return module.namespace();
@@ -107,7 +78,7 @@ class Loader {
10778
this._dynamicInstantiate = FunctionBind(dynamicInstantiate, null);
10879
}
10980

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

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

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

‎lib/internal/loader/Translators.js‎

Copy file name to clipboardExpand all lines: lib/internal/loader/Translators.js
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const JsonParse = JSON.parse;
1919
const translators = new SafeMap();
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
const source = `${await readFileAsync(new URL(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
return createDynamicModule(['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
return createDynamicModule(['default'], url, (reflect) => {
Collapse file

‎lib/internal/process/modules.js‎

Copy file name to clipboard
+47Lines 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+
const Loader = require('internal/loader/Loader');
9+
const path = require('path');
10+
const { URL } = require('url');
11+
12+
function normalizeReferrerURL(referrer) {
13+
if (typeof referrer === 'string' && path.isAbsolute(referrer)) {
14+
return getURLFromFilePath(referrer).href;
15+
}
16+
return new URL(referrer).href;
17+
}
18+
19+
let loaderResolve;
20+
exports.loaderPromise = new Promise((resolve, reject) => {
21+
loaderResolve = resolve;
22+
});
23+
24+
exports.ESMLoader = undefined;
25+
26+
exports.setup = function() {
27+
let ESMLoader = new Loader();
28+
const loaderPromise = (async () => {
29+
const userLoader = process.binding('config').userLoader;
30+
if (userLoader) {
31+
const hooks = await ESMLoader.import(
32+
userLoader, getURLFromFilePath(`${process.cwd()}/`).href);
33+
ESMLoader = new Loader();
34+
ESMLoader.hook(hooks);
35+
exports.ESMLoader = ESMLoader;
36+
}
37+
return ESMLoader;
38+
})();
39+
loaderResolve(loaderPromise);
40+
41+
setImportModuleDynamicallyCallback(async (referrer, specifier) => {
42+
const loader = await loaderPromise;
43+
return loader.import(specifier, normalizeReferrerURL(referrer));
44+
});
45+
46+
exports.ESMLoader = ESMLoader;
47+
};
Collapse file

‎lib/module.js‎

Copy file name to clipboardExpand all lines: lib/module.js
+8-21Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
const NativeModule = require('native_module');
2525
const util = require('util');
2626
const { decorateErrorStack } = require('internal/util');
27-
const internalModule = require('internal/module');
2827
const { getURLFromFilePath } = require('internal/url');
2928
const vm = require('vm');
3029
const assert = require('assert').ok;
@@ -35,6 +34,7 @@ const {
3534
internalModuleReadFile,
3635
internalModuleStat
3736
} = process.binding('fs');
37+
const internalModule = require('internal/module');
3838
const preserveSymlinks = !!process.binding('config').preserveSymlinks;
3939
const experimentalModules = !!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-
const Loader = require('internal/loader/Loader');
46+
const internalESModule = require('internal/process/modules');
4747
const ModuleJob = require('internal/loader/ModuleJob');
4848
const createDynamicModule = require('internal/loader/CreateDynamicModule');
49-
let ESMLoader;
5049

5150
function stat(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 = new Loader();
465-
const userLoader = process.binding('config').userLoader;
466-
if (userLoader) {
467-
ESMLoader.isMain = false;
468-
const hooks = await ESMLoader.import(userLoader);
469-
ESMLoader = new Loader();
470-
ESMLoader.hook(hooks);
471-
}
472-
}
473-
Loader.registerImportDynamicallyCallback(ESMLoader);
474-
await ESMLoader.import(getURLFromFilePath(request).pathname);
475-
})()
458+
if (experimentalModules && isMain) {
459+
internalESModule.loaderPromise.then((loader) => {
460+
return loader.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+
const ESMLoader = internalESModule.ESMLoader;
579566
const url = getURLFromFilePath(filename);
580567
const urlString = `${url}`;
581568
const exports = this.exports;
Collapse file

‎node.gyp‎

Copy file name to clipboardExpand all lines: node.gyp
+1Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.