| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,14 +25,16 @@ For more info about `node inspect`, see the [debugger][] documentation. | |||
| 25 | 25 | ||
| 26 | 26 | The program entry point is a specifier-like string. If the string is not an | |
| 27 | 27 | absolute path, it's resolved as a relative path from the current working | |
| 28 | - directory. That path is then resolved by [CommonJS][] module loader. If no | ||
| 29 | - corresponding file is found, an error is thrown. | ||
| 28 | + directory. That path is then resolved by [CommonJS][] module loader, or by the | ||
| 29 | + [ES module loader][Modules loaders] if [`--experimental-default-type=module`][] | ||
| 30 | + is passed. If no corresponding file is found, an error is thrown. | ||
| 30 | 31 | ||
| 31 | 32 | If a file is found, its path will be passed to the | |
| 32 | 33 | [ES module loader][Modules loaders] under any of the following conditions: | |
| 33 | 34 | ||
| 34 | 35 | * The program was started with a command-line flag that forces the entry | |
| 35 | - point to be loaded with ECMAScript module loader. | ||
| 36 | + point to be loaded with ECMAScript module loader, such as `--import` or | ||
| 37 | + [`--experimental-default-type=module`][]. | ||
| 36 | 38 | * The file has an `.mjs` extension. | |
| 37 | 39 | * The file does not have a `.cjs` extension, and the nearest parent | |
| 38 | 40 | `package.json` file contains a top-level [`"type"`][] field with a value of | |
@@ -45,8 +47,9 @@ Otherwise, the file is loaded using the CommonJS module loader. See | |||
| 45 | 47 | ||
| 46 | 48 | When loading, the [ES module loader][Modules loaders] loads the program | |
| 47 | 49 | entry point, the `node` command will accept as input only files with `.js`, | |
| 48 | - `.mjs`, or `.cjs` extensions; and with `.wasm` extensions when | ||
| 49 | - [`--experimental-wasm-modules`][] is enabled. | ||
| 50 | + `.mjs`, or `.cjs` extensions; with `.wasm` extensions when | ||
| 51 | + [`--experimental-wasm-modules`][] is enabled; and with no extension when | ||
| 52 | + [`--experimental-default-type=module`][] is passed. | ||
| 50 | 53 | ||
| 51 | 54 | ## Options | |
| 52 | 55 | ||
@@ -2417,6 +2420,7 @@ done | |||
| 2417 | 2420 | [`"type"`]: packages.md#type | |
| 2418 | 2421 | [`--cpu-prof-dir`]: #--cpu-prof-dir | |
| 2419 | 2422 | [`--diagnostic-dir`]: #--diagnostic-dirdirectory | |
| 2423 | + [`--experimental-default-type=module`]: #--experimental-default-typetype | ||
| 2420 | 2424 | [`--experimental-wasm-modules`]: #--experimental-wasm-modules | |
| 2421 | 2425 | [`--heap-prof-dir`]: #--heap-prof-dir | |
| 2422 | 2426 | [`--import`]: #--importmodule | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,18 +6,24 @@ const { | |||
| 6 | 6 | prepareMainThreadExecution, | |
| 7 | 7 | markBootstrapComplete, | |
| 8 | 8 | } = require('internal/process/pre_execution'); | |
| 9 | + const { getOptionValue } = require('internal/options'); | ||
| 9 | 10 | ||
| 10 | - prepareMainThreadExecution(true); | ||
| 11 | + const mainEntry = prepareMainThreadExecution(true); | ||
| 11 | 12 | ||
| 12 | 13 | markBootstrapComplete(); | |
| 13 | 14 | ||
| 14 | 15 | // Necessary to reset RegExp statics before user code runs. | |
| 15 | 16 | RegExpPrototypeExec(/^/, ''); | |
| 16 | 17 | ||
| 17 | - // Note: this loads the module through the ESM loader if the module is | ||
| 18 | - // determined to be an ES module. This hangs from the CJS module loader | ||
| 19 | - // because we currently allow monkey-patching of the module loaders | ||
| 20 | - // in the preloaded scripts through require('module'). | ||
| 21 | - // runMain here might be monkey-patched by users in --require. | ||
| 22 | - // XXX: the monkey-patchability here should probably be deprecated. | ||
| 23 | - require('internal/modules/cjs/loader').Module.runMain(process.argv[1]); | ||
| 18 | + if (getOptionValue('--experimental-default-type') === 'module') { | ||
| 19 | + require('internal/modules/run_main').executeUserEntryPoint(mainEntry); | ||
| 20 | + } else { | ||
| 21 | + /** | ||
| 22 | + * To support legacy monkey-patching of `Module.runMain`, we call `runMain` here to have the CommonJS loader begin | ||
| 23 | + * the execution of the main entry point, even if the ESM loader immediately takes over because the main entry is an | ||
| 24 | + * ES module or one of the other opt-in conditions (such as the use of `--import`) are met. Users can monkey-patch | ||
| 25 | + * before the main entry point is loaded by doing so via scripts loaded through `--require`. This monkey-patchability | ||
| 26 | + * is undesirable and is removed in `--experimental-default-type=module` mode. | ||
| 27 | + */ | ||
| 28 | + require('internal/modules/cjs/loader').Module.runMain(mainEntry); | ||
| 29 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1204,17 +1204,7 @@ function defaultResolve(specifier, context = {}) { | |||
| 1204 | 1204 | if (StringPrototypeStartsWith(specifier, 'file://')) { | |
| 1205 | 1205 | specifier = fileURLToPath(specifier); | |
| 1206 | 1206 | } | |
| 1207 | - const found = resolveAsCommonJS(specifier, parentURL); | ||
| 1208 | - if (found) { | ||
| 1209 | - // Modify the stack and message string to include the hint | ||
| 1210 | - const lines = StringPrototypeSplit(error.stack, '\n'); | ||
| 1211 | - const hint = `Did you mean to import ${found}?`; | ||
| 1212 | - error.stack = | ||
| 1213 | - ArrayPrototypeShift(lines) + '\n' + | ||
| 1214 | - hint + '\n' + | ||
| 1215 | - ArrayPrototypeJoin(lines, '\n'); | ||
| 1216 | - error.message += `\n${hint}`; | ||
| 1217 | - } | ||
| 1207 | + decorateErrorWithCommonJSHints(error, specifier, parentURL); | ||
| 1218 | 1208 | } | |
| 1219 | 1209 | throw error; | |
| 1220 | 1210 | } | |
@@ -1227,7 +1217,28 @@ function defaultResolve(specifier, context = {}) { | |||
| 1227 | 1217 | }; | |
| 1228 | 1218 | } | |
| 1229 | 1219 | ||
| 1220 | + /** | ||
| 1221 | + * Decorates the given error with a hint for CommonJS modules. | ||
| 1222 | + * @param {Error} error - The error to decorate. | ||
| 1223 | + * @param {string} specifier - The specifier that was attempted to be imported. | ||
| 1224 | + * @param {string} parentURL - The URL of the parent module. | ||
| 1225 | + */ | ||
| 1226 | + function decorateErrorWithCommonJSHints(error, specifier, parentURL) { | ||
| 1227 | + const found = resolveAsCommonJS(specifier, parentURL); | ||
| 1228 | + if (found) { | ||
| 1229 | + // Modify the stack and message string to include the hint | ||
| 1230 | + const lines = StringPrototypeSplit(error.stack, '\n'); | ||
| 1231 | + const hint = `Did you mean to import ${found}?`; | ||
| 1232 | + error.stack = | ||
| 1233 | + ArrayPrototypeShift(lines) + '\n' + | ||
| 1234 | + hint + '\n' + | ||
| 1235 | + ArrayPrototypeJoin(lines, '\n'); | ||
| 1236 | + error.message += `\n${hint}`; | ||
| 1237 | + } | ||
| 1238 | + } | ||
| 1239 | + | ||
| 1230 | 1240 | module.exports = { | |
| 1241 | + decorateErrorWithCommonJSHints, | ||
| 1231 | 1242 | defaultResolve, | |
| 1232 | 1243 | encodedSepRegEx, | |
| 1233 | 1244 | getPackageScopeConfig, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,7 +1,6 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | 2 | ||
| 3 | 3 | const { | |
| 4 | - ObjectCreate, | ||
| 5 | 4 | StringPrototypeEndsWith, | |
| 6 | 5 | } = primordials; | |
| 7 | 6 | ||
@@ -13,17 +12,33 @@ const path = require('path'); | |||
| 13 | 12 | * @param {string} main - Entry point path | |
| 14 | 13 | */ | |
| 15 | 14 | function resolveMainPath(main) { | |
| 16 | - // Note extension resolution for the main entry point can be deprecated in a | ||
| 17 | - // future major. | ||
| 18 | - // Module._findPath is monkey-patchable here. | ||
| 19 | - const { Module } = require('internal/modules/cjs/loader'); | ||
| 20 | - let mainPath = Module._findPath(path.resolve(main), null, true); | ||
| 15 | + const defaultType = getOptionValue('--experimental-default-type'); | ||
| 16 | + /** @type {string} */ | ||
| 17 | + let mainPath; | ||
| 18 | + if (defaultType === 'module') { | ||
| 19 | + if (getOptionValue('--preserve-symlinks-main')) { return; } | ||
| 20 | + mainPath = path.resolve(main); | ||
| 21 | + } else { | ||
| 22 | + // Extension searching for the main entry point is supported only in legacy mode. | ||
| 23 | + // Module._findPath is monkey-patchable here. | ||
| 24 | + const { Module } = require('internal/modules/cjs/loader'); | ||
| 25 | + mainPath = Module._findPath(path.resolve(main), null, true); | ||
| 26 | + } | ||
| 21 | 27 | if (!mainPath) { return; } | |
| 22 | 28 | ||
| 23 | 29 | const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); | |
| 24 | 30 | if (!preserveSymlinksMain) { | |
| 25 | 31 | const { toRealPath } = require('internal/modules/helpers'); | |
| 26 | - mainPath = toRealPath(mainPath); | ||
| 32 | + try { | ||
| 33 | + mainPath = toRealPath(mainPath); | ||
| 34 | + } catch (err) { | ||
| 35 | + if (defaultType === 'module' && err?.code === 'ENOENT') { | ||
| 36 | + const { decorateErrorWithCommonJSHints } = require('internal/modules/esm/resolve'); | ||
| 37 | + const { getCWDURL } = require('internal/util'); | ||
| 38 | + decorateErrorWithCommonJSHints(err, mainPath, getCWDURL()); | ||
| 39 | + } | ||
| 40 | + throw err; | ||
| 41 | + } | ||
| 27 | 42 | } | |
| 28 | 43 | ||
| 29 | 44 | return mainPath; | |
@@ -34,6 +49,8 @@ function resolveMainPath(main) { | |||
| 34 | 49 | * @param {string} mainPath - Absolute path to the main entry point | |
| 35 | 50 | */ | |
| 36 | 51 | function shouldUseESMLoader(mainPath) { | |
| 52 | + if (getOptionValue('--experimental-default-type') === 'module') { return true; } | ||
| 53 | + | ||
| 37 | 54 | /** | |
| 38 | 55 | * @type {string[]} userLoaders A list of custom loaders registered by the user | |
| 39 | 56 | * (or an empty list when none have been registered). | |
@@ -69,11 +86,10 @@ function shouldUseESMLoader(mainPath) { | |||
| 69 | 86 | function runMainESM(mainPath) { | |
| 70 | 87 | const { loadESM } = require('internal/process/esm_loader'); | |
| 71 | 88 | const { pathToFileURL } = require('internal/url'); | |
| 89 | + const main = pathToFileURL(mainPath).href; | ||
| 72 | 90 | ||
| 73 | 91 | handleMainPromise(loadESM((esmLoader) => { | |
| 74 | - const main = path.isAbsolute(mainPath) ? | ||
| 75 | - pathToFileURL(mainPath).href : mainPath; | ||
| 76 | - return esmLoader.import(main, undefined, ObjectCreate(null)); | ||
| 92 | + return esmLoader.import(main, undefined, { __proto__: null }); | ||
| 77 | 93 | })); | |
| 78 | 94 | } | |
| 79 | 95 | ||
@@ -97,8 +113,9 @@ async function handleMainPromise(promise) { | |||
| 97 | 113 | * Parse the CLI main entry point string and run it. | |
| 98 | 114 | * For backwards compatibility, we have to run a bunch of monkey-patchable code that belongs to the CJS loader (exposed | |
| 99 | 115 | * by `require('module')`) even when the entry point is ESM. | |
| 116 | + * This monkey-patchable code is bypassed under `--experimental-default-type=module`. | ||
| 100 | 117 | * Because of backwards compatibility, this function is exposed publicly via `import { runMain } from 'node:module'`. | |
| 101 | - * @param {string} main - Resolved absolute path for the main entry point, if found | ||
| 118 | + * @param {string} main - First positional CLI argument, such as `'entry.js'` from `node entry.js` | ||
| 102 | 119 | */ | |
| 103 | 120 | function executeUserEntryPoint(main = process.argv[1]) { | |
| 104 | 121 | const resolvedMain = resolveMainPath(main); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -37,7 +37,7 @@ const { | |||
| 37 | 37 | } = require('internal/v8/startup_snapshot'); | |
| 38 | 38 | ||
| 39 | 39 | function prepareMainThreadExecution(expandArgv1 = false, initializeModules = true) { | |
| 40 | - prepareExecution({ | ||
| 40 | + return prepareExecution({ | ||
| 41 | 41 | expandArgv1, | |
| 42 | 42 | initializeModules, | |
| 43 | 43 | isMainThread: true, | |
@@ -58,8 +58,8 @@ function prepareExecution(options) { | |||
| 58 | 58 | refreshRuntimeOptions(); | |
| 59 | 59 | reconnectZeroFillToggle(); | |
| 60 | 60 | ||
| 61 | - // Patch the process object with legacy properties and normalizations | ||
| 62 | - patchProcessObject(expandArgv1); | ||
| 61 | + // Patch the process object and get the resolved main entry point. | ||
| 62 | + const mainEntry = patchProcessObject(expandArgv1); | ||
| 63 | 63 | setupTraceCategoryState(); | |
| 64 | 64 | setupPerfHooks(); | |
| 65 | 65 | setupInspectorHooks(); | |
@@ -113,6 +113,8 @@ function prepareExecution(options) { | |||
| 113 | 113 | if (initializeModules) { | |
| 114 | 114 | setupUserModules(); | |
| 115 | 115 | } | |
| 116 | + | ||
| 117 | + return mainEntry; | ||
| 116 | 118 | } | |
| 117 | 119 | ||
| 118 | 120 | function setupSymbolDisposePolyfill() { | |
@@ -167,14 +169,17 @@ function patchProcessObject(expandArgv1) { | |||
| 167 | 169 | process._exiting = false; | |
| 168 | 170 | process.argv[0] = process.execPath; | |
| 169 | 171 | ||
| 172 | + /** @type {string} */ | ||
| 173 | + let mainEntry; | ||
| 170 | 174 | // If requested, update process.argv[1] to replace whatever the user provided with the resolved absolute file path of | |
| 171 | 175 | // the entry point. | |
| 172 | 176 | if (expandArgv1 && process.argv[1] && | |
| 173 | 177 | !StringPrototypeStartsWith(process.argv[1], '-')) { | |
| 174 | 178 | // Expand process.argv[1] into a full path. | |
| 175 | 179 | const path = require('path'); | |
| 176 | 180 | try { | |
| 177 | - process.argv[1] = path.resolve(process.argv[1]); | ||
| 181 | + mainEntry = path.resolve(process.argv[1]); | ||
| 182 | + process.argv[1] = mainEntry; | ||
| 178 | 183 | } catch { | |
| 179 | 184 | // Continue regardless of error. | |
| 180 | 185 | } | |
@@ -201,6 +206,8 @@ function patchProcessObject(expandArgv1) { | |||
| 201 | 206 | addReadOnlyProcessAlias('traceDeprecation', '--trace-deprecation'); | |
| 202 | 207 | addReadOnlyProcessAlias('_breakFirstLine', '--inspect-brk', false); | |
| 203 | 208 | addReadOnlyProcessAlias('_breakNodeFirstLine', '--inspect-brk-node', false); | |
| 209 | + | ||
| 210 | + return mainEntry; | ||
| 204 | 211 | } | |
| 205 | 212 | ||
| 206 | 213 | function addReadOnlyProcessAlias(name, option, enumerable = true) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,92 @@ | |||
| 1 | + import { spawnPromisified } from '../common/index.mjs'; | ||
| 2 | + import * as fixtures from '../common/fixtures.mjs'; | ||
| 3 | + import { describe, it } from 'node:test'; | ||
| 4 | + import { match, strictEqual } from 'node:assert'; | ||
| 5 | + | ||
| 6 | + describe('--experimental-default-type=module should not support extension searching', { concurrency: true }, () => { | ||
| 7 | + it('should support extension searching under --experimental-default-type=commonjs', async () => { | ||
| 8 | + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ | ||
| 9 | + '--experimental-default-type=commonjs', | ||
| 10 | + 'index', | ||
| 11 | + ], { | ||
| 12 | + cwd: fixtures.path('es-modules/package-without-type'), | ||
| 13 | + }); | ||
| 14 | + | ||
| 15 | + strictEqual(stdout, 'package-without-type\n'); | ||
| 16 | + strictEqual(stderr, ''); | ||
| 17 | + strictEqual(code, 0); | ||
| 18 | + strictEqual(signal, null); | ||
| 19 | + }); | ||
| 20 | + | ||
| 21 | + it('should error with implicit extension under --experimental-default-type=module', async () => { | ||
| 22 | + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ | ||
| 23 | + '--experimental-default-type=module', | ||
| 24 | + 'index', | ||
| 25 | + ], { | ||
| 26 | + cwd: fixtures.path('es-modules/package-without-type'), | ||
| 27 | + }); | ||
| 28 | + | ||
| 29 | + match(stderr, /ENOENT.*Did you mean to import .*index\.js\?/s); | ||
| 30 | + strictEqual(stdout, ''); | ||
| 31 | + strictEqual(code, 1); | ||
| 32 | + strictEqual(signal, null); | ||
| 33 | + }); | ||
| 34 | + }); | ||
| 35 | + | ||
| 36 | + describe('--experimental-default-type=module should not parse paths as URLs', { concurrency: true }, () => { | ||
| 37 | + it('should not parse a `?` in a filename as starting a query string', async () => { | ||
| 38 | + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ | ||
| 39 | + '--experimental-default-type=module', | ||
| 40 | + 'file#1.js', | ||
| 41 | + ], { | ||
| 42 | + cwd: fixtures.path('es-modules/package-without-type'), | ||
| 43 | + }); | ||
| 44 | + | ||
| 45 | + strictEqual(stderr, ''); | ||
| 46 | + strictEqual(stdout, 'file#1\n'); | ||
| 47 | + strictEqual(code, 0); | ||
| 48 | + strictEqual(signal, null); | ||
| 49 | + }); | ||
| 50 | + | ||
| 51 | + it('should resolve `..`', async () => { | ||
| 52 | + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ | ||
| 53 | + '--experimental-default-type=module', | ||
| 54 | + '../package-without-type/file#1.js', | ||
| 55 | + ], { | ||
| 56 | + cwd: fixtures.path('es-modules/package-without-type'), | ||
| 57 | + }); | ||
| 58 | + | ||
| 59 | + strictEqual(stderr, ''); | ||
| 60 | + strictEqual(stdout, 'file#1\n'); | ||
| 61 | + strictEqual(code, 0); | ||
| 62 | + strictEqual(signal, null); | ||
| 63 | + }); | ||
| 64 | + | ||
| 65 | + it('should allow a leading `./`', async () => { | ||
| 66 | + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ | ||
| 67 | + '--experimental-default-type=module', | ||
| 68 | + './file#1.js', | ||
| 69 | + ], { | ||
| 70 | + cwd: fixtures.path('es-modules/package-without-type'), | ||
| 71 | + }); | ||
| 72 | + | ||
| 73 | + strictEqual(stderr, ''); | ||
| 74 | + strictEqual(stdout, 'file#1\n'); | ||
| 75 | + strictEqual(code, 0); | ||
| 76 | + strictEqual(signal, null); | ||
| 77 | + }); | ||
| 78 | + | ||
| 79 | + it('should not require a leading `./`', async () => { | ||
| 80 | + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ | ||
| 81 | + '--experimental-default-type=module', | ||
| 82 | + 'file#1.js', | ||
| 83 | + ], { | ||
| 84 | + cwd: fixtures.path('es-modules/package-without-type'), | ||
| 85 | + }); | ||
| 86 | + | ||
| 87 | + strictEqual(stderr, ''); | ||
| 88 | + strictEqual(stdout, 'file#1\n'); | ||
| 89 | + strictEqual(code, 0); | ||
| 90 | + strictEqual(signal, null); | ||
| 91 | + }); | ||
| 92 | + }); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,7 +8,7 @@ Error: Should include grayed stack trace | |||
| 8 | 8 | [90m at Module._extensions..js (node:internal*modules*cjs*loader:1414:10)[39m | |
| 9 | 9 | [90m at Module.load (node:internal*modules*cjs*loader:1197:32)[39m | |
| 10 | 10 | [90m at Module._load (node:internal*modules*cjs*loader:1013:12)[39m | |
| 11 | - [90m at Function.executeUserEntryPoint [as runMain] (node:internal*modules*run_main:109:12)[39m | ||
| 12 | - [90m at node:internal*main*run_main_module:23:47[39m | ||
| 11 | + [90m at Function.executeUserEntryPoint [as runMain] (node:internal*modules*run_main:128:12)[39m | ||
| 12 | + [90m at node:internal*main*run_main_module:28:49[39m | ||
| 13 | 13 | ||
| 14 | 14 | Node.js * | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1 @@ | |||
| 1 | + console.log('file#1'); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments