44 StringPrototypeEndsWith,
55} = primordials ;
66
7- const { containsModuleSyntax } = internalBinding ( 'contextify' ) ;
87const { getNearestParentPackageJSONType } = internalBinding ( 'modules' ) ;
98const { getOptionValue } = require ( 'internal/options' ) ;
109const { checkPackageJSONIntegrity } = require ( 'internal/modules/package_json_reader' ) ;
@@ -82,10 +81,6 @@ function shouldUseESMLoader(mainPath) {
8281
8382 // No package.json or no `type` field.
8483 if ( response === undefined || response [ 0 ] === 'none' ) {
85- if ( getOptionValue ( '--experimental-detect-module' ) ) {
86- // If the first argument of `containsModuleSyntax` is undefined, it will read `mainPath` from the file system.
87- return containsModuleSyntax ( undefined , mainPath ) ;
88- }
8984 return false ;
9085 }
9186
@@ -160,12 +155,43 @@ function runEntryPointWithESMLoader(callback) {
160155 * by `require('module')`) even when the entry point is ESM.
161156 * This monkey-patchable code is bypassed under `--experimental-default-type=module`.
162157 * Because of backwards compatibility, this function is exposed publicly via `import { runMain } from 'node:module'`.
158+ * When `--experimental-detect-module` is passed, this function will attempt to run ambiguous (no explicit extension, no
159+ * `package.json` type field) entry points as CommonJS first; under certain conditions, it will retry running as ESM.
163160 * @param {string } main - First positional CLI argument, such as `'entry.js'` from `node entry.js`
164161 */
165162function executeUserEntryPoint ( main = process . argv [ 1 ] ) {
166163 const resolvedMain = resolveMainPath ( main ) ;
167164 const useESMLoader = shouldUseESMLoader ( resolvedMain ) ;
168- if ( useESMLoader ) {
165+
166+ // Unless we know we should use the ESM loader to handle the entry point per the checks in `shouldUseESMLoader`, first
167+ // try to run the entry point via the CommonJS loader; and if that fails under certain conditions, retry as ESM.
168+ let retryAsESM = false ;
169+ if ( ! useESMLoader ) {
170+ const cjsLoader = require ( 'internal/modules/cjs/loader' ) ;
171+ const { Module } = cjsLoader ;
172+ if ( getOptionValue ( '--experimental-detect-module' ) ) {
173+ try {
174+ // Module._load is the monkey-patchable CJS module loader.
175+ Module . _load ( main , null , true ) ;
176+ } catch ( error ) {
177+ const source = cjsLoader . entryPointSource ;
178+ const { shouldRetryAsESM } = require ( 'internal/modules/helpers' ) ;
179+ retryAsESM = shouldRetryAsESM ( error . message , source ) ;
180+ // In case the entry point is a large file, such as a bundle,
181+ // ensure no further references can prevent it being garbage-collected.
182+ cjsLoader . entryPointSource = undefined ;
183+ if ( ! retryAsESM ) {
184+ const { enrichCJSError } = require ( 'internal/modules/esm/translators' ) ;
185+ enrichCJSError ( error , source , resolvedMain ) ;
186+ throw error ;
187+ }
188+ }
189+ } else { // `--experimental-detect-module` is not passed
190+ Module . _load ( main , null , true ) ;
191+ }
192+ }
193+
194+ if ( useESMLoader || retryAsESM ) {
169195 const mainPath = resolvedMain || main ;
170196 const mainURL = pathToFileURL ( mainPath ) . href ;
171197
@@ -174,10 +200,6 @@ function executeUserEntryPoint(main = process.argv[1]) {
174200 // even after the event loop stops running.
175201 return cascadedLoader . import ( mainURL , undefined , { __proto__ : null } , true ) ;
176202 } ) ;
177- } else {
178- // Module._load is the monkey-patchable CJS module loader.
179- const { Module } = require ( 'internal/modules/cjs/loader' ) ;
180- Module . _load ( main , null , true ) ;
181203 }
182204}
183205
0 commit comments