GeezerVison is showing github.com — esm: add `--import` flag · nodejs/node@af0921d
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file 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 number | Diff line number | Diff line change |
|---|
@@ -445,7 +445,8 @@ Only the root context is supported. There is no guarantee that | 445 | 445 | `globalThis.Array` is indeed the default intrinsic reference. Code may break | 446 | 446 | under this flag. | 447 | 447 |
| 448 | | -To allow polyfills to be added, `--require` runs before freezing intrinsics. | | 448 | +To allow polyfills to be added, | | 449 | +[`--require`][] and [`--import`][] both run before freezing intrinsics. | 449 | 450 |
| 450 | 451 | ### `--force-node-api-uncaught-exceptions-policy` | 451 | 452 |
| @@ -594,6 +595,18 @@ added: v0.11.15 | 594 | 595 |
| 595 | 596 | Specify ICU data load path. (Overrides `NODE_ICU_DATA`.) | 596 | 597 |
| | 598 | +### `--import=module` | | 599 | + | | 600 | +<!-- YAML | | 601 | +added: REPLACEME | | 602 | +--> | | 603 | + | | 604 | +Preload the specified module at startup. | | 605 | + | | 606 | +Follows [ECMAScript module][] resolution rules. | | 607 | +Use [`--require`][] to load a [CommonJS module][]. | | 608 | +Modules preloaded with `--require` will run before modules preloaded with `--import`. | | 609 | + | 597 | 610 | ### `--input-type=type` | 598 | 611 |
| 599 | 612 | <!-- YAML | @@ -1527,8 +1540,9 @@ Preload the specified module at startup. | 1527 | 1540 | Follows `require()`'s module resolution | 1528 | 1541 | rules. `module` may be either a path to a file, or a node module name. | 1529 | 1542 |
| 1530 | | -Only CommonJS modules are supported. Attempting to preload a | 1531 | | -ES6 Module using `--require` will fail with an error. | | 1543 | +Only CommonJS modules are supported. | | 1544 | +Use [`--import`][] to preload an [ECMAScript module][]. | | 1545 | +Modules preloaded with `--require` will run before modules preloaded with `--import`. | 1532 | 1546 |
| 1533 | 1547 | ### `-v`, `--version` | 1534 | 1548 |
| @@ -1682,6 +1696,7 @@ Node.js options that are allowed are: | 1682 | 1696 | * `--heapsnapshot-signal` | 1683 | 1697 | * `--http-parser` | 1684 | 1698 | * `--icu-data-dir` | | 1699 | +* `--import` | 1685 | 1700 | * `--input-type` | 1686 | 1701 | * `--insecure-http-parser` | 1687 | 1702 | * `--inspect-brk` | @@ -2086,7 +2101,9 @@ done | 2086 | 2101 | [#42511]: https://github.com/nodejs/node/issues/42511 | 2087 | 2102 | [Chrome DevTools Protocol]: https://chromedevtools.github.io/devtools-protocol/ | 2088 | 2103 | [CommonJS]: modules.md | | 2104 | +[CommonJS module]: modules.md | 2089 | 2105 | [CustomEvent Web API]: https://dom.spec.whatwg.org/#customevent | | 2106 | +[ECMAScript module]: esm.md#modules-ecmascript-modules | 2090 | 2107 | [ECMAScript module loader]: esm.md#loaders | 2091 | 2108 | [Fetch API]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API | 2092 | 2109 | [Modules loaders]: packages.md#modules-loaders | @@ -2103,8 +2120,10 @@ done | 2103 | 2120 | [`--diagnostic-dir`]: #--diagnostic-dirdirectory | 2104 | 2121 | [`--experimental-wasm-modules`]: #--experimental-wasm-modules | 2105 | 2122 | [`--heap-prof-dir`]: #--heap-prof-dir | | 2123 | +[`--import`]: #--importmodule | 2106 | 2124 | [`--openssl-config`]: #--openssl-configfile | 2107 | 2125 | [`--redirect-warnings`]: #--redirect-warningsfile | | 2126 | +[`--require`]: #-r---require-module | 2108 | 2127 | [`Atomics.wait()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait | 2109 | 2128 | [`Buffer`]: buffer.md#class-buffer | 2110 | 2129 | [`CRYPTO_secure_malloc_init`]: https://www.openssl.org/docs/man1.1.0/man3/CRYPTO_secure_malloc_init.html | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/main/check_syntax.js +21-6Lines changed: 21 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 3 | 3 | // If user passed `-c` or `--check` arguments to Node, check its syntax | 4 | 4 | // instead of actually running the file. | 5 | 5 |
| | 6 | +const { getOptionValue } = require('internal/options'); | 6 | 7 | const { | 7 | 8 | prepareMainThreadExecution | 8 | 9 | } = require('internal/bootstrap/pre_execution'); | @@ -36,18 +37,29 @@ if (process.argv[1] && process.argv[1] !== '-') { | 36 | 37 |
| 37 | 38 | markBootstrapComplete(); | 38 | 39 |
| 39 | | - checkSyntax(source, filename); | | 40 | + loadESMIfNeeded(() => checkSyntax(source, filename)); | 40 | 41 | } else { | 41 | 42 | markBootstrapComplete(); | 42 | 43 |
| 43 | | - readStdin((code) => { | | 44 | + loadESMIfNeeded(() => readStdin((code) => { | 44 | 45 | checkSyntax(code, '[stdin]'); | 45 | | - }); | | 46 | + })); | 46 | 47 | } | 47 | 48 |
| 48 | | -async function checkSyntax(source, filename) { | | 49 | +function loadESMIfNeeded(cb) { | 49 | 50 | const { getOptionValue } = require('internal/options'); | 50 | | - let isModule = false; | | 51 | + const hasModulePreImport = getOptionValue('--import').length > 0; | | 52 | + | | 53 | + if (hasModulePreImport) { | | 54 | + const { loadESM } = require('internal/process/esm_loader'); | | 55 | + loadESM(cb); | | 56 | + return; | | 57 | + } | | 58 | + cb(); | | 59 | +} | | 60 | + | | 61 | +async function checkSyntax(source, filename) { | | 62 | + let isModule = true; | 51 | 63 | if (filename === '[stdin]' || filename === '[eval]') { | 52 | 64 | isModule = getOptionValue('--input-type') === 'module'; | 53 | 65 | } else { | @@ -57,11 +69,14 @@ async function checkSyntax(source, filename) { | 57 | 69 | const format = await defaultGetFormat(url); | 58 | 70 | isModule = format === 'module'; | 59 | 71 | } | | 72 | + | 60 | 73 | if (isModule) { | 61 | 74 | const { ModuleWrap } = internalBinding('module_wrap'); | 62 | 75 | new ModuleWrap(filename, undefined, source, 0, 0); | 63 | 76 | return; | 64 | 77 | } | 65 | 78 |
| 66 | | - wrapSafe(filename, source); | | 79 | + const { loadESM } = require('internal/process/esm_loader'); | | 80 | + const { handleMainPromise } = require('internal/modules/run_main'); | | 81 | + handleMainPromise(loadESM((loader) => wrapSafe(filename, source))); | 67 | 82 | } |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/main/eval_stdin.js +3-1Lines changed: 3 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -23,11 +23,13 @@ readStdin((code) => { | 23 | 23 | process._eval = code; | 24 | 24 |
| 25 | 25 | const print = getOptionValue('--print'); | | 26 | + const loadESM = getOptionValue('--import').length > 0; | 26 | 27 | if (getOptionValue('--input-type') === 'module') | 27 | 28 | evalModule(code, print); | 28 | 29 | else | 29 | 30 | evalScript('[stdin]', | 30 | 31 | code, | 31 | 32 | getOptionValue('--inspect-brk'), | 32 | | - print); | | 33 | + print, | | 34 | + loadESM); | 33 | 35 | }); |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/main/eval_string.js +3-1Lines changed: 3 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -21,10 +21,12 @@ markBootstrapComplete(); | 21 | 21 |
| 22 | 22 | const source = getOptionValue('--eval'); | 23 | 23 | const print = getOptionValue('--print'); | | 24 | +const loadESM = getOptionValue('--import').length > 0; | 24 | 25 | if (getOptionValue('--input-type') === 'module') | 25 | 26 | evalModule(source, print); | 26 | 27 | else | 27 | 28 | evalScript('[eval]', | 28 | 29 | source, | 29 | 30 | getOptionValue('--inspect-brk'), | 30 | | - print); | | 31 | + print, | | 32 | + loadESM); |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/modules/run_main.js +6-1Lines changed: 6 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -33,7 +33,12 @@ function shouldUseESMLoader(mainPath) { | 33 | 33 | * (or an empty list when none have been registered). | 34 | 34 | */ | 35 | 35 | 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) | 37 | 42 | return true; | 38 | 43 | const esModuleSpecifierResolution = | 39 | 44 | getOptionValue('--experimental-specifier-resolution'); | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/process/esm_loader.js +20-9Lines changed: 20 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | 'use strict'; | 2 | 2 |
| 3 | 3 | const { | | 4 | + ArrayIsArray, | 4 | 5 | ObjectCreate, | 5 | 6 | } = primordials; | 6 | 7 |
| @@ -56,8 +57,23 @@ async function initializeLoader() { | 56 | 57 |
| 57 | 58 | const { getOptionValue } = require('internal/options'); | 58 | 59 | const customLoaders = getOptionValue('--experimental-loader'); | | 60 | + const preloadModules = getOptionValue('--import'); | | 61 | + const loaders = await loadModulesInIsolation(customLoaders); | 59 | 62 |
| 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; } | 61 | 77 |
| 62 | 78 | let cwd; | 63 | 79 | try { | @@ -70,19 +86,14 @@ async function initializeLoader() { | 70 | 86 | // between internal Node.js and userland. For example, a module with internal | 71 | 87 | // state (such as a counter) should be independent. | 72 | 88 | const internalEsmLoader = new ESMLoader(); | | 89 | + internalEsmLoader.addCustomLoaders(loaders); | 73 | 90 |
| 74 | 91 | // 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, | 77 | 94 | pathToFileURL(cwd).href, | 78 | 95 | ObjectCreate(null), | 79 | 96 | ); | 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; | 86 | 97 | } | 87 | 98 |
| 88 | 99 | exports.loadESM = async function loadESM(callback) { | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/process/execution.js +35-26Lines changed: 35 additions & 26 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -48,7 +48,7 @@ function evalModule(source, print) { | 48 | 48 | return handleMainPromise(loadESM((loader) => loader.eval(source))); | 49 | 49 | } | 50 | 50 |
| 51 | | -function evalScript(name, body, breakFirstLine, print) { | | 51 | +function evalScript(name, body, breakFirstLine, print, shouldLoadESM = false) { | 52 | 52 | const CJSModule = require('internal/modules/cjs/loader').Module; | 53 | 53 | const { kVmBreakFirstLineSymbol } = require('internal/util'); | 54 | 54 | const { pathToFileURL } = require('url'); | @@ -60,35 +60,44 @@ function evalScript(name, body, breakFirstLine, print) { | 60 | 60 | module.filename = path.join(cwd, name); | 61 | 61 | module.paths = CJSModule._nodeModulePaths(cwd); | 62 | 62 |
| | 63 | + const { handleMainPromise } = require('internal/modules/run_main'); | 63 | 64 | const asyncESM = require('internal/process/esm_loader'); | 64 | 65 | const baseUrl = pathToFileURL(module.filename).href; | | 66 | + const { loadESM } = asyncESM; | | 67 | + | | 68 | + const runScript = () => { | | 69 | + // Create wrapper for cache entry | | 70 | + const script = ` | | 71 | + globalThis.module = module; | | 72 | + globalThis.exports = exports; | | 73 | + globalThis.__dirname = __dirname; | | 74 | + globalThis.require = require; | | 75 | + return (main) => main(); | | 76 | + `; | | 77 | + globalThis.__filename = name; | | 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 | + } | 65 | 92 |
| 66 | | - // Create wrapper for cache entry | 67 | | - const script = ` | 68 | | - globalThis.module = module; | 69 | | - globalThis.exports = exports; | 70 | | - globalThis.__dirname = __dirname; | 71 | | - globalThis.require = require; | 72 | | - return (main) => main(); | 73 | | - `; | 74 | | - globalThis.__filename = name; | 75 | | - const result = module._compile(script, `${name}-wrapper`)(() => | 76 | | - require('vm').runInThisContext(body, { | 77 | | - filename: name, | 78 | | - displayErrors: true, | 79 | | - [kVmBreakFirstLineSymbol]: !!breakFirstLine, | 80 | | - importModuleDynamically(specifier, _, importAssertions) { | 81 | | - const loader = asyncESM.esmLoader; | 82 | | - return loader.import(specifier, baseUrl, importAssertions); | 83 | | - } | 84 | | - })); | 85 | | - if (print) { | 86 | | - const { log } = require('internal/console/global'); | 87 | | - log(result); | 88 | | - } | | 93 | + if (origModule !== undefined) | | 94 | + globalThis.module = origModule; | | 95 | + }; | 89 | 96 |
| 90 | | - if (origModule !== undefined) | 91 | | - globalThis.module = origModule; | | 97 | + if (shouldLoadESM) { | | 98 | + return handleMainPromise(loadESM(runScript)); | | 99 | + } | | 100 | + return runScript(); | 92 | 101 | } | 93 | 102 |
| 94 | 103 | const exceptionHandlerState = { | |
Collapse file Copy file name to clipboardExpand all lines: src/node_options.cc +6-2Lines changed: 6 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -608,10 +608,14 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { | 608 | 608 | AddAlias("-pe", { "--print", "--eval" }); | 609 | 609 | AddAlias("-p", "--print"); | 610 | 610 | AddOption("--require", | 611 | | - "module to preload (option can be repeated)", | 612 | | - &EnvironmentOptions::preload_modules, | | 611 | + "CommonJS module to preload (option can be repeated)", | | 612 | + &EnvironmentOptions::preload_cjs_modules, | 613 | 613 | kAllowedInEnvironment); | 614 | 614 | AddAlias("-r", "--require"); | | 615 | + AddOption("--import", | | 616 | + "ES module to preload (option can be repeated)", | | 617 | + &EnvironmentOptions::preload_esm_modules, | | 618 | + kAllowedInEnvironment); | 615 | 619 | AddOption("--interactive", | 616 | 620 | "always enter the REPL even if stdin does not appear " | 617 | 621 | "to be a terminal", | |
Collapse file Copy file name to clipboardExpand all lines: src/node_options.h +3-1Lines changed: 3 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -188,7 +188,9 @@ class EnvironmentOptions : public Options { | 188 | 188 | bool tls_max_v1_3 = false; | 189 | 189 | std::string tls_keylog; | 190 | 190 |
| 191 | | - std::vector<std::string> preload_modules; | | 191 | + std::vector<std::string> preload_cjs_modules; | | 192 | + | | 193 | + std::vector<std::string> preload_esm_modules; | 192 | 194 |
| 193 | 195 | std::vector<std::string> user_argv; | 194 | 196 |
| |
|