55 globalThis,
66} = primordials ;
77
8- const { containsModuleSyntax } = internalBinding ( 'contextify' ) ;
98const { getNearestParentPackageJSONType } = internalBinding ( 'modules' ) ;
109const { getOptionValue } = require ( 'internal/options' ) ;
1110const { checkPackageJSONIntegrity } = require ( 'internal/modules/package_json_reader' ) ;
@@ -87,10 +86,6 @@ function shouldUseESMLoader(mainPath) {
8786
8887 // No package.json or no `type` field.
8988 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- }
9489 return false ;
9590 }
9691
@@ -157,12 +152,43 @@ function runEntryPointWithESMLoader(callback) {
157152 * by `require('module')`) even when the entry point is ESM.
158153 * This monkey-patchable code is bypassed under `--experimental-default-type=module`.
159154 * 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.
160157 * @param {string } main - First positional CLI argument, such as `'entry.js'` from `node entry.js`
161158 */
162159function executeUserEntryPoint ( main = process . argv [ 1 ] ) {
163160 const resolvedMain = resolveMainPath ( main ) ;
164161 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 ) {
166192 const mainPath = resolvedMain || main ;
167193 const mainURL = pathToFileURL ( mainPath ) . href ;
168194
@@ -171,10 +197,6 @@ function executeUserEntryPoint(main = process.argv[1]) {
171197 // even after the event loop stops running.
172198 return cascadedLoader . import ( mainURL , undefined , { __proto__ : null } , true ) ;
173199 } ) ;
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 ) ;
178200 }
179201}
180202
0 commit comments