@@ -44,6 +44,7 @@ function resolveMainPath(main) {
4444 } catch ( err ) {
4545 if ( defaultType === 'module' && err ?. code === 'ENOENT' ) {
4646 const { decorateErrorWithCommonJSHints } = require ( 'internal/modules/esm/resolve' ) ;
47+ const { getCWDURL } = require ( 'internal/util' ) ;
4748 decorateErrorWithCommonJSHints ( err , mainPath , getCWDURL ( ) ) ;
4849 }
4950 throw err ;
@@ -85,10 +86,6 @@ function shouldUseESMLoader(mainPath) {
8586 case 'commonjs' :
8687 return false ;
8788 default : { // No package.json or no `type` field.
88- if ( getOptionValue ( '--experimental-detect-module' ) ) {
89- // If the first argument of `containsModuleSyntax` is undefined, it will read `mainPath` from the file system.
90- return containsModuleSyntax ( undefined , mainPath ) ;
91- }
9289 return false ;
9390 }
9491 }
@@ -153,12 +150,43 @@ function runEntryPointWithESMLoader(callback) {
153150 * by `require('module')`) even when the entry point is ESM.
154151 * This monkey-patchable code is bypassed under `--experimental-default-type=module`.
155152 * Because of backwards compatibility, this function is exposed publicly via `import { runMain } from 'node:module'`.
153+ * When `--experimental-detect-module` is passed, this function will attempt to run ambiguous (no explicit extension, no
154+ * `package.json` type field) entry points as CommonJS first; under certain conditions, it will retry running as ESM.
156155 * @param {string } main - First positional CLI argument, such as `'entry.js'` from `node entry.js`
157156 */
158157function executeUserEntryPoint ( main = process . argv [ 1 ] ) {
159158 const resolvedMain = resolveMainPath ( main ) ;
160159 const useESMLoader = shouldUseESMLoader ( resolvedMain ) ;
161- if ( useESMLoader ) {
160+
161+ // Unless we know we should use the ESM loader to handle the entry point per the checks in `shouldUseESMLoader`, first
162+ // try to run the entry point via the CommonJS loader; and if that fails under certain conditions, retry as ESM.
163+ let retryAsESM = false ;
164+ if ( ! useESMLoader ) {
165+ const cjsLoader = require ( 'internal/modules/cjs/loader' ) ;
166+ const { Module } = cjsLoader ;
167+ if ( getOptionValue ( '--experimental-detect-module' ) ) {
168+ try {
169+ // Module._load is the monkey-patchable CJS module loader.
170+ Module . _load ( main , null , true ) ;
171+ } catch ( error ) {
172+ const source = cjsLoader . entryPointSource ;
173+ const { shouldRetryAsESM } = require ( 'internal/modules/helpers' ) ;
174+ retryAsESM = shouldRetryAsESM ( error . message , source ) ;
175+ // In case the entry point is a large file, such as a bundle,
176+ // ensure no further references can prevent it being garbage-collected.
177+ cjsLoader . entryPointSource = undefined ;
178+ if ( ! retryAsESM ) {
179+ const { enrichCJSError } = require ( 'internal/modules/esm/translators' ) ;
180+ enrichCJSError ( error , source , resolvedMain ) ;
181+ throw error ;
182+ }
183+ }
184+ } else { // `--experimental-detect-module` is not passed
185+ Module . _load ( main , null , true ) ;
186+ }
187+ }
188+
189+ if ( useESMLoader || retryAsESM ) {
162190 const mainPath = resolvedMain || main ;
163191 const mainURL = pathToFileURL ( mainPath ) . href ;
164192
@@ -167,10 +195,6 @@ function executeUserEntryPoint(main = process.argv[1]) {
167195 // even after the event loop stops running.
168196 return cascadedLoader . import ( mainURL , undefined , { __proto__ : null } , true ) ;
169197 } ) ;
170- } else {
171- // Module._load is the monkey-patchable CJS module loader.
172- const { Module } = require ( 'internal/modules/cjs/loader' ) ;
173- Module . _load ( main , null , true ) ;
174198 }
175199}
176200
0 commit comments