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 c39b7c2

Browse filesBrowse files
MoLowruyadorno
authored andcommitted
esm: add --import flag
PR-URL: #43942 Backport-PR-URL: #49539 Fixes: #40110 Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Jacob Smith <jacob@frende.me> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent c224e1b commit c39b7c2
Copy full SHA for c39b7c2

File tree

Expand file treeCollapse file tree

16 files changed

+348
-66
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

16 files changed

+348
-66
lines changed
Open diff view settings
Collapse file

‎doc/api/cli.md‎

Copy file name to clipboardExpand all lines: doc/api/cli.md
+22-3Lines changed: 22 additions & 3 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,8 @@ Only the root context is supported. There is no guarantee that
530530
`globalThis.Array` is indeed the default intrinsic reference. Code may break
531531
under this flag.
532532

533-
To allow polyfills to be added, `--require` runs before freezing intrinsics.
533+
To allow polyfills to be added,
534+
[`--require`][] and [`--import`][] both run before freezing intrinsics.
534535

535536
### `--force-node-api-uncaught-exceptions-policy`
536537

@@ -679,6 +680,18 @@ added: v0.11.15
679680

680681
Specify ICU data load path. (Overrides `NODE_ICU_DATA`.)
681682

683+
### `--import=module`
684+
685+
<!-- YAML
686+
added: REPLACEME
687+
-->
688+
689+
Preload the specified module at startup.
690+
691+
Follows [ECMAScript module][] resolution rules.
692+
Use [`--require`][] to load a [CommonJS module][].
693+
Modules preloaded with `--require` will run before modules preloaded with `--import`.
694+
682695
### `--input-type=type`
683696

684697
<!-- YAML
@@ -1739,8 +1752,9 @@ Preload the specified module at startup.
17391752
Follows `require()`'s module resolution
17401753
rules. `module` may be either a path to a file, or a node module name.
17411754

1742-
Only CommonJS modules are supported. Attempting to preload a
1743-
ES6 Module using `--require` will fail with an error.
1755+
Only CommonJS modules are supported.
1756+
Use [`--import`][] to preload an [ECMAScript module][].
1757+
Modules preloaded with `--require` will run before modules preloaded with `--import`.
17441758

17451759
### `-v`, `--version`
17461760

@@ -1895,6 +1909,7 @@ Node.js options that are allowed are:
18951909
* `--heapsnapshot-signal`
18961910
* `--http-parser`
18971911
* `--icu-data-dir`
1912+
* `--import`
18981913
* `--input-type`
18991914
* `--insecure-http-parser`
19001915
* `--inspect-brk`
@@ -2315,7 +2330,9 @@ done
23152330
[#42511]: https://github.com/nodejs/node/issues/42511
23162331
[Chrome DevTools Protocol]: https://chromedevtools.github.io/devtools-protocol/
23172332
[CommonJS]: modules.md
2333+
[CommonJS module]: modules.md
23182334
[CustomEvent Web API]: https://dom.spec.whatwg.org/#customevent
2335+
[ECMAScript module]: esm.md#modules-ecmascript-modules
23192336
[ECMAScript module loader]: esm.md#loaders
23202337
[Fetch API]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
23212338
[Modules loaders]: packages.md#modules-loaders
@@ -2333,9 +2350,11 @@ done
23332350
[`--diagnostic-dir`]: #--diagnostic-dirdirectory
23342351
[`--experimental-wasm-modules`]: #--experimental-wasm-modules
23352352
[`--heap-prof-dir`]: #--heap-prof-dir
2353+
[`--import`]: #--importmodule
23362354
[`--openssl-config`]: #--openssl-configfile
23372355
[`--preserve-symlinks`]: #--preserve-symlinks
23382356
[`--redirect-warnings`]: #--redirect-warningsfile
2357+
[`--require`]: #-r---require-module
23392358
[`Atomics.wait()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait
23402359
[`Buffer`]: buffer.md#class-buffer
23412360
[`CRYPTO_secure_malloc_init`]: https://www.openssl.org/docs/man3.0/man3/CRYPTO_secure_malloc_init.html
Collapse file

‎lib/internal/main/check_syntax.js‎

Copy file name to clipboardExpand all lines: lib/internal/main/check_syntax.js
+21-6Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// instead of actually running the file.
55

66
const { URL } = require('internal/url');
7+
const { getOptionValue } = require('internal/options');
78
const {
89
prepareMainThreadExecution,
910
markBootstrapComplete,
@@ -38,18 +39,29 @@ if (process.argv[1] && process.argv[1] !== '-') {
3839

3940
markBootstrapComplete();
4041

41-
checkSyntax(source, filename);
42+
loadESMIfNeeded(() => checkSyntax(source, filename));
4243
} else {
4344
markBootstrapComplete();
4445

45-
readStdin((code) => {
46+
loadESMIfNeeded(() => readStdin((code) => {
4647
checkSyntax(code, '[stdin]');
47-
});
48+
}));
4849
}
4950

50-
async function checkSyntax(source, filename) {
51+
function loadESMIfNeeded(cb) {
5152
const { getOptionValue } = require('internal/options');
52-
let isModule = false;
53+
const hasModulePreImport = getOptionValue('--import').length > 0;
54+
55+
if (hasModulePreImport) {
56+
const { loadESM } = require('internal/process/esm_loader');
57+
loadESM(cb);
58+
return;
59+
}
60+
cb();
61+
}
62+
63+
async function checkSyntax(source, filename) {
64+
let isModule = true;
5365
if (filename === '[stdin]' || filename === '[eval]') {
5466
isModule = getOptionValue('--input-type') === 'module';
5567
} else {
@@ -59,11 +71,14 @@ async function checkSyntax(source, filename) {
5971
const format = await defaultGetFormat(new URL(url));
6072
isModule = format === 'module';
6173
}
74+
6275
if (isModule) {
6376
const { ModuleWrap } = internalBinding('module_wrap');
6477
new ModuleWrap(filename, undefined, source, 0, 0);
6578
return;
6679
}
6780

68-
wrapSafe(filename, source);
81+
const { loadESM } = require('internal/process/esm_loader');
82+
const { handleMainPromise } = require('internal/modules/run_main');
83+
handleMainPromise(loadESM((loader) => wrapSafe(filename, source)));
6984
}
Collapse file

‎lib/internal/main/eval_stdin.js‎

Copy file name to clipboardExpand all lines: lib/internal/main/eval_stdin.js
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ readStdin((code) => {
2424
process._eval = code;
2525

2626
const print = getOptionValue('--print');
27+
const loadESM = getOptionValue('--import').length > 0;
2728
if (getOptionValue('--input-type') === 'module')
2829
evalModule(code, print);
2930
else
3031
evalScript('[stdin]',
3132
code,
3233
getOptionValue('--inspect-brk'),
33-
print);
34+
print,
35+
loadESM);
3436
});
Collapse file

‎lib/internal/main/eval_string.js‎

Copy file name to clipboardExpand all lines: lib/internal/main/eval_string.js
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ markBootstrapComplete();
2222

2323
const source = getOptionValue('--eval');
2424
const print = getOptionValue('--print');
25+
const loadESM = getOptionValue('--import').length > 0;
2526
if (getOptionValue('--input-type') === 'module')
2627
evalModule(source, print);
2728
else
2829
evalScript('[eval]',
2930
source,
3031
getOptionValue('--inspect-brk'),
31-
print);
32+
print,
33+
loadESM);
Collapse file

‎lib/internal/modules/run_main.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/run_main.js
+6-1Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ function shouldUseESMLoader(mainPath) {
3333
* (or an empty list when none have been registered).
3434
*/
3535
const userLoaders = getOptionValue('--experimental-loader');
36-
if (userLoaders.length > 0)
36+
/**
37+
* @type {string[]} userImports A list of preloaded modules registered by the user
38+
* (or an empty list when none have been registered).
39+
*/
40+
const userImports = getOptionValue('--import');
41+
if (userLoaders.length > 0 || userImports.length > 0)
3742
return true;
3843
const esModuleSpecifierResolution =
3944
getOptionValue('--experimental-specifier-resolution');
Collapse file

‎lib/internal/process/esm_loader.js‎

Copy file name to clipboardExpand all lines: lib/internal/process/esm_loader.js
+20-9Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const {
4+
ArrayIsArray,
45
ObjectCreate,
56
} = primordials;
67

@@ -56,8 +57,23 @@ async function initializeLoader() {
5657

5758
const { getOptionValue } = require('internal/options');
5859
const customLoaders = getOptionValue('--experimental-loader');
60+
const preloadModules = getOptionValue('--import');
61+
const loaders = await loadModulesInIsolation(customLoaders);
5962

60-
if (customLoaders.length === 0) return;
63+
// Hooks must then be added to external/public loader
64+
// (so they're triggered in userland)
65+
esmLoader.addCustomLoaders(loaders);
66+
67+
// Preload after loaders are added so they can be used
68+
if (preloadModules?.length) {
69+
await loadModulesInIsolation(preloadModules, loaders);
70+
}
71+
72+
isESMInitialized = true;
73+
}
74+
75+
function loadModulesInIsolation(specifiers, loaders = []) {
76+
if (!ArrayIsArray(specifiers) || specifiers.length === 0) { return; }
6177

6278
let cwd;
6379
try {
@@ -70,19 +86,14 @@ async function initializeLoader() {
7086
// between internal Node.js and userland. For example, a module with internal
7187
// state (such as a counter) should be independent.
7288
const internalEsmLoader = new ESMLoader();
89+
internalEsmLoader.addCustomLoaders(loaders);
7390

7491
// Importation must be handled by internal loader to avoid poluting userland
75-
const keyedExportsList = await internalEsmLoader.import(
76-
customLoaders,
92+
return internalEsmLoader.import(
93+
specifiers,
7794
pathToFileURL(cwd).href,
7895
ObjectCreate(null),
7996
);
80-
81-
// Hooks must then be added to external/public loader
82-
// (so they're triggered in userland)
83-
await esmLoader.addCustomLoaders(keyedExportsList);
84-
85-
isESMInitialized = true;
8697
}
8798

8899
exports.loadESM = async function loadESM(callback) {
Collapse file

‎lib/internal/process/execution.js‎

Copy file name to clipboardExpand all lines: lib/internal/process/execution.js
+36-27Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ function evalModule(source, print) {
5050
return handleMainPromise(loadESM((loader) => loader.eval(source)));
5151
}
5252

53-
function evalScript(name, body, breakFirstLine, print) {
53+
function evalScript(name, body, breakFirstLine, print, shouldLoadESM = false) {
5454
const CJSModule = require('internal/modules/cjs/loader').Module;
5555
const { kVmBreakFirstLineSymbol } = require('internal/util');
5656
const { pathToFileURL } = require('url');
@@ -62,36 +62,45 @@ function evalScript(name, body, breakFirstLine, print) {
6262
module.filename = path.join(cwd, name);
6363
module.paths = CJSModule._nodeModulePaths(cwd);
6464

65+
const { handleMainPromise } = require('internal/modules/run_main');
6566
const asyncESM = require('internal/process/esm_loader');
6667
const baseUrl = pathToFileURL(module.filename).href;
68+
const { loadESM } = asyncESM;
69+
70+
const runScript = () => {
71+
// Create wrapper for cache entry
72+
const script = `
73+
globalThis.module = module;
74+
globalThis.exports = exports;
75+
globalThis.__dirname = __dirname;
76+
globalThis.require = require;
77+
return (main) => main();
78+
`;
79+
globalThis.__filename = name;
80+
RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs.
81+
const result = module._compile(script, `${name}-wrapper`)(() =>
82+
require('vm').runInThisContext(body, {
83+
filename: name,
84+
displayErrors: true,
85+
[kVmBreakFirstLineSymbol]: !!breakFirstLine,
86+
importModuleDynamically(specifier, _, importAssertions) {
87+
const loader = asyncESM.esmLoader;
88+
return loader.import(specifier, baseUrl, importAssertions);
89+
},
90+
}));
91+
if (print) {
92+
const { log } = require('internal/console/global');
93+
log(result);
94+
}
6795

68-
// Create wrapper for cache entry
69-
const script = `
70-
globalThis.module = module;
71-
globalThis.exports = exports;
72-
globalThis.__dirname = __dirname;
73-
globalThis.require = require;
74-
return (main) => main();
75-
`;
76-
globalThis.__filename = name;
77-
RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs.
78-
const result = module._compile(script, `${name}-wrapper`)(() =>
79-
require('vm').runInThisContext(body, {
80-
filename: name,
81-
displayErrors: true,
82-
[kVmBreakFirstLineSymbol]: !!breakFirstLine,
83-
importModuleDynamically(specifier, _, importAssertions) {
84-
const loader = asyncESM.esmLoader;
85-
return loader.import(specifier, baseUrl, importAssertions);
86-
},
87-
}));
88-
if (print) {
89-
const { log } = require('internal/console/global');
90-
log(result);
91-
}
96+
if (origModule !== undefined)
97+
globalThis.module = origModule;
98+
};
9299

93-
if (origModule !== undefined)
94-
globalThis.module = origModule;
100+
if (shouldLoadESM) {
101+
return handleMainPromise(loadESM(runScript));
102+
}
103+
return runScript();
95104
}
96105

97106
const exceptionHandlerState = {
Collapse file

‎src/node_options.cc‎

Copy file name to clipboardExpand all lines: src/node_options.cc
+6-2Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -666,10 +666,14 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
666666
AddAlias("-pe", { "--print", "--eval" });
667667
AddAlias("-p", "--print");
668668
AddOption("--require",
669-
"module to preload (option can be repeated)",
670-
&EnvironmentOptions::preload_modules,
669+
"CommonJS module to preload (option can be repeated)",
670+
&EnvironmentOptions::preload_cjs_modules,
671671
kAllowedInEnvvar);
672672
AddAlias("-r", "--require");
673+
AddOption("--import",
674+
"ES module to preload (option can be repeated)",
675+
&EnvironmentOptions::preload_esm_modules,
676+
kAllowedInEnvironment);
673677
AddOption("--interactive",
674678
"always enter the REPL even if stdin does not appear "
675679
"to be a terminal",
Collapse file

‎src/node_options.h‎

Copy file name to clipboardExpand all lines: src/node_options.h
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,9 @@ class EnvironmentOptions : public Options {
200200
bool tls_max_v1_3 = false;
201201
std::string tls_keylog;
202202

203-
std::vector<std::string> preload_modules;
203+
std::vector<std::string> preload_cjs_modules;
204+
205+
std::vector<std::string> preload_esm_modules;
204206

205207
std::vector<std::string> user_argv;
206208

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.