44 StringPrototypeEndsWith,
55} = primordials ;
66
7- const { containsModuleSyntax } = internalBinding ( 'contextify' ) ;
87const { getOptionValue } = require ( 'internal/options' ) ;
98const path = require ( 'path' ) ;
109const { pathToFileURL } = require ( 'internal/url' ) ;
@@ -44,6 +43,7 @@ function resolveMainPath(main) {
4443 } catch ( err ) {
4544 if ( defaultType === 'module' && err ?. code === 'ENOENT' ) {
4645 const { decorateErrorWithCommonJSHints } = require ( 'internal/modules/esm/resolve' ) ;
46+ const { getCWDURL } = require ( 'internal/util' ) ;
4747 decorateErrorWithCommonJSHints ( err , mainPath , getCWDURL ( ) ) ;
4848 }
4949 throw err ;
@@ -85,10 +85,6 @@ function shouldUseESMLoader(mainPath) {
8585 case 'commonjs' :
8686 return false ;
8787 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- }
9288 return false ;
9389 }
9490 }
@@ -153,12 +149,43 @@ function runEntryPointWithESMLoader(callback) {
153149 * by `require('module')`) even when the entry point is ESM.
154150 * This monkey-patchable code is bypassed under `--experimental-default-type=module`.
155151 * Because of backwards compatibility, this function is exposed publicly via `import { runMain } from 'node:module'`.
152+ * When `--experimental-detect-module` is passed, this function will attempt to run ambiguous (no explicit extension, no
153+ * `package.json` type field) entry points as CommonJS first; under certain conditions, it will retry running as ESM.
156154 * @param {string } main - First positional CLI argument, such as `'entry.js'` from `node entry.js`
157155 */
158156function executeUserEntryPoint ( main = process . argv [ 1 ] ) {
159157 const resolvedMain = resolveMainPath ( main ) ;
160158 const useESMLoader = shouldUseESMLoader ( resolvedMain ) ;
161- if ( useESMLoader ) {
159+
160+ // Unless we know we should use the ESM loader to handle the entry point per the checks in `shouldUseESMLoader`, first
161+ // try to run the entry point via the CommonJS loader; and if that fails under certain conditions, retry as ESM.
162+ let retryAsESM = false ;
163+ if ( ! useESMLoader ) {
164+ const cjsLoader = require ( 'internal/modules/cjs/loader' ) ;
165+ const { Module } = cjsLoader ;
166+ if ( getOptionValue ( '--experimental-detect-module' ) ) {
167+ try {
168+ // Module._load is the monkey-patchable CJS module loader.
169+ Module . _load ( main , null , true ) ;
170+ } catch ( error ) {
171+ const source = cjsLoader . entryPointSource ;
172+ const { shouldRetryAsESM } = require ( 'internal/modules/helpers' ) ;
173+ retryAsESM = shouldRetryAsESM ( error . message , source ) ;
174+ // In case the entry point is a large file, such as a bundle,
175+ // ensure no further references can prevent it being garbage-collected.
176+ cjsLoader . entryPointSource = undefined ;
177+ if ( ! retryAsESM ) {
178+ const { enrichCJSError } = require ( 'internal/modules/esm/translators' ) ;
179+ enrichCJSError ( error , source , resolvedMain ) ;
180+ throw error ;
181+ }
182+ }
183+ } else { // `--experimental-detect-module` is not passed
184+ Module . _load ( main , null , true ) ;
185+ }
186+ }
187+
188+ if ( useESMLoader || retryAsESM ) {
162189 const mainPath = resolvedMain || main ;
163190 const mainURL = pathToFileURL ( mainPath ) . href ;
164191
@@ -167,10 +194,6 @@ function executeUserEntryPoint(main = process.argv[1]) {
167194 // even after the event loop stops running.
168195 return cascadedLoader . import ( mainURL , undefined , { __proto__ : null } , true ) ;
169196 } ) ;
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 ) ;
174197 }
175198}
176199
0 commit comments