Skip to content

Commit 1f396d2

Browse 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 097dea0 commit 1f396d2

16 files changed

+348
-66
lines changed

doc/api/cli.md

+22-3
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

lib/internal/main/check_syntax.js

+21-6
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
}

lib/internal/main/eval_stdin.js

+3-1
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
});

lib/internal/main/eval_string.js

+3-1
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);

lib/internal/modules/run_main.js

+6-1
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');

lib/internal/process/esm_loader.js

+20-9
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) {

lib/internal/process/execution.js

+36-27
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 = {

src/node_options.cc

+6-2
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",

src/node_options.h

+3-1
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)