| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent f1949ac commit 8bc7459
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,6 +9,7 @@ const bench = common.createBenchmark(main, { | |||
| 9 | 9 | script: [ | |
| 10 | 10 | 'benchmark/fixtures/require-builtins', | |
| 11 | 11 | 'test/fixtures/semicolon', | |
| 12 | + 'test/fixtures/snapshot/typescript', | ||
| 12 | 13 | ], | |
| 13 | 14 | mode: ['process', 'worker'], | |
| 14 | 15 | count: [30], | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -75,6 +75,7 @@ module.exports = { | |||
| 75 | 75 | cjsExportsCache, | |
| 76 | 76 | cjsSourceCache, | |
| 77 | 77 | initializeCJS, | |
| 78 | + entryPointSource: undefined, // Set below. | ||
| 78 | 79 | Module, | |
| 79 | 80 | wrapSafe, | |
| 80 | 81 | }; | |
@@ -1337,8 +1338,15 @@ function wrapSafe(filename, content, cjsModuleInstance, codeCache) { | |||
| 1337 | 1338 | return result; | |
| 1338 | 1339 | } catch (err) { | |
| 1339 | 1340 | if (process.mainModule === cjsModuleInstance) { | |
| 1340 | - const { enrichCJSError } = require('internal/modules/esm/translators'); | ||
| 1341 | - enrichCJSError(err, content, filename); | ||
| 1341 | + if (getOptionValue('--experimental-detect-module')) { | ||
| 1342 | + // For the main entry point, cache the source to potentially retry as ESM. | ||
| 1343 | + module.exports.entryPointSource = content; | ||
| 1344 | + } else { | ||
| 1345 | + // We only enrich the error (print a warning) if we're sure we're going to for-sure throw it; so if we're | ||
| 1346 | + // retrying as ESM, wait until we know whether we're going to retry before calling `enrichCJSError`. | ||
| 1347 | + const { enrichCJSError } = require('internal/modules/esm/translators'); | ||
| 1348 | + enrichCJSError(err, content, filename); | ||
| 1349 | + } | ||
| 1342 | 1350 | } | |
| 1343 | 1351 | throw err; | |
| 1344 | 1352 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -19,6 +19,15 @@ const { | |||
| 19 | 19 | } = require('internal/errors').codes; | |
| 20 | 20 | const { BuiltinModule } = require('internal/bootstrap/realm'); | |
| 21 | 21 | ||
| 22 | + const { | ||
| 23 | + shouldRetryAsESM: contextifyShouldRetryAsESM, | ||
| 24 | + constants: { | ||
| 25 | + syntaxDetectionErrors: { | ||
| 26 | + esmSyntaxErrorMessages, | ||
| 27 | + throwsOnlyInCommonJSErrorMessages, | ||
| 28 | + }, | ||
| 29 | + }, | ||
| 30 | + } = internalBinding('contextify'); | ||
| 22 | 31 | const { validateString } = require('internal/validators'); | |
| 23 | 32 | const fs = require('fs'); // Import all of `fs` so that it can be monkey-patched. | |
| 24 | 33 | const internalFS = require('internal/fs/utils'); | |
@@ -320,6 +329,31 @@ function normalizeReferrerURL(referrerName) { | |||
| 320 | 329 | } | |
| 321 | 330 | ||
| 322 | 331 | ||
| 332 | + let esmSyntaxErrorMessagesSet; // Declared lazily in shouldRetryAsESM | ||
| 333 | + let throwsOnlyInCommonJSErrorMessagesSet; // Declared lazily in shouldRetryAsESM | ||
| 334 | + /** | ||
| 335 | + * After an attempt to parse a module as CommonJS throws an error, should we try again as ESM? | ||
| 336 | + * We only want to try again as ESM if the error is due to syntax that is only valid in ESM; and if the CommonJS parse | ||
| 337 | + * throws on an error that would not have been a syntax error in ESM (like via top-level `await` or a lexical | ||
| 338 | + * redeclaration of one of the CommonJS variables) then we need to parse again to see if it would have thrown in ESM. | ||
| 339 | + * @param {string} errorMessage The string message thrown by V8 when attempting to parse as CommonJS | ||
| 340 | + * @param {string} source Module contents | ||
| 341 | + */ | ||
| 342 | + function shouldRetryAsESM(errorMessage, source) { | ||
| 343 | + esmSyntaxErrorMessagesSet ??= new SafeSet(esmSyntaxErrorMessages); | ||
| 344 | + if (esmSyntaxErrorMessagesSet.has(errorMessage)) { | ||
| 345 | + return true; | ||
| 346 | + } | ||
| 347 | + | ||
| 348 | + throwsOnlyInCommonJSErrorMessagesSet ??= new SafeSet(throwsOnlyInCommonJSErrorMessages); | ||
| 349 | + if (throwsOnlyInCommonJSErrorMessagesSet.has(errorMessage)) { | ||
| 350 | + return /** @type {boolean} */(contextifyShouldRetryAsESM(source)); | ||
| 351 | + } | ||
| 352 | + | ||
| 353 | + return false; | ||
| 354 | + } | ||
| 355 | + | ||
| 356 | + | ||
| 323 | 357 | // Whether we have started executing any user-provided CJS code. | |
| 324 | 358 | // This is set right before we call the wrapped CJS code (not after, | |
| 325 | 359 | // in case we are half-way in the execution when internals check this). | |
@@ -339,6 +373,7 @@ module.exports = { | |||
| 339 | 373 | loadBuiltinModule, | |
| 340 | 374 | makeRequireFunction, | |
| 341 | 375 | normalizeReferrerURL, | |
| 376 | + shouldRetryAsESM, | ||
| 342 | 377 | stripBOM, | |
| 343 | 378 | toRealPath, | |
| 344 | 379 | hasStartedUserCJSExecution() { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -5,7 +5,6 @@ const { | |||
| 5 | 5 | globalThis, | |
| 6 | 6 | } = primordials; | |
| 7 | 7 | ||
| 8 | - const { containsModuleSyntax } = internalBinding('contextify'); | ||
| 9 | 8 | const { getNearestParentPackageJSONType } = internalBinding('modules'); | |
| 10 | 9 | const { getOptionValue } = require('internal/options'); | |
| 11 | 10 | const { checkPackageJSONIntegrity } = require('internal/modules/package_json_reader'); | |
@@ -87,10 +86,6 @@ function shouldUseESMLoader(mainPath) { | |||
| 87 | 86 | ||
| 88 | 87 | // No package.json or no `type` field. | |
| 89 | 88 | if (response === undefined || response[0] === 'none') { | |
| 90 | - if (getOptionValue('--experimental-detect-module')) { | ||
| 91 | - // If the first argument of `containsModuleSyntax` is undefined, it will read `mainPath` from the file system. | ||
| 92 | - return containsModuleSyntax(undefined, mainPath); | ||
| 93 | - } | ||
| 94 | 89 | return false; | |
| 95 | 90 | } | |
| 96 | 91 | ||
@@ -157,12 +152,43 @@ function runEntryPointWithESMLoader(callback) { | |||
| 157 | 152 | * by `require('module')`) even when the entry point is ESM. | |
| 158 | 153 | * This monkey-patchable code is bypassed under `--experimental-default-type=module`. | |
| 159 | 154 | * Because of backwards compatibility, this function is exposed publicly via `import { runMain } from 'node:module'`. | |
| 155 | + * When `--experimental-detect-module` is passed, this function will attempt to run ambiguous (no explicit extension, no | ||
| 156 | + * `package.json` type field) entry points as CommonJS first; under certain conditions, it will retry running as ESM. | ||
| 160 | 157 | * @param {string} main - First positional CLI argument, such as `'entry.js'` from `node entry.js` | |
| 161 | 158 | */ | |
| 162 | 159 | function executeUserEntryPoint(main = process.argv[1]) { | |
| 163 | 160 | const resolvedMain = resolveMainPath(main); | |
| 164 | 161 | const useESMLoader = shouldUseESMLoader(resolvedMain); | |
| 165 | - if (useESMLoader) { | ||
| 162 | + | ||
| 163 | + // Unless we know we should use the ESM loader to handle the entry point per the checks in `shouldUseESMLoader`, first | ||
| 164 | + // try to run the entry point via the CommonJS loader; and if that fails under certain conditions, retry as ESM. | ||
| 165 | + let retryAsESM = false; | ||
| 166 | + if (!useESMLoader) { | ||
| 167 | + const cjsLoader = require('internal/modules/cjs/loader'); | ||
| 168 | + const { Module } = cjsLoader; | ||
| 169 | + if (getOptionValue('--experimental-detect-module')) { | ||
| 170 | + try { | ||
| 171 | + // Module._load is the monkey-patchable CJS module loader. | ||
| 172 | + Module._load(main, null, true); | ||
| 173 | + } catch (error) { | ||
| 174 | + const source = cjsLoader.entryPointSource; | ||
| 175 | + const { shouldRetryAsESM } = require('internal/modules/helpers'); | ||
| 176 | + retryAsESM = shouldRetryAsESM(error.message, source); | ||
| 177 | + // In case the entry point is a large file, such as a bundle, | ||
| 178 | + // ensure no further references can prevent it being garbage-collected. | ||
| 179 | + cjsLoader.entryPointSource = undefined; | ||
| 180 | + if (!retryAsESM) { | ||
| 181 | + const { enrichCJSError } = require('internal/modules/esm/translators'); | ||
| 182 | + enrichCJSError(error, source, resolvedMain); | ||
| 183 | + throw error; | ||
| 184 | + } | ||
| 185 | + } | ||
| 186 | + } else { // `--experimental-detect-module` is not passed | ||
| 187 | + Module._load(main, null, true); | ||
| 188 | + } | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + if (useESMLoader || retryAsESM) { | ||
| 166 | 192 | const mainPath = resolvedMain || main; | |
| 167 | 193 | const mainURL = pathToFileURL(mainPath).href; | |
| 168 | 194 | ||
@@ -171,10 +197,6 @@ function executeUserEntryPoint(main = process.argv[1]) { | |||
| 171 | 197 | // even after the event loop stops running. | |
| 172 | 198 | return cascadedLoader.import(mainURL, undefined, { __proto__: null }, true); | |
| 173 | 199 | }); | |
| 174 | - } else { | ||
| 175 | - // Module._load is the monkey-patchable CJS module loader. | ||
| 176 | - const { Module } = require('internal/modules/cjs/loader'); | ||
| 177 | - Module._load(main, null, true); | ||
| 178 | 200 | } | |
| 179 | 201 | } | |
| 180 | 202 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments