44 StringPrototypeEndsWith,
55} = primordials ;
66
7- const { containsModuleSyntax } = internalBinding ( 'contextify' ) ;
87const { getOptionValue } = require ( 'internal/options' ) ;
98const path = require ( 'path' ) ;
109const { pathToFileURL } = require ( 'internal/url' ) ;
@@ -85,10 +84,6 @@ function shouldUseESMLoader(mainPath) {
8584 case 'commonjs' :
8685 return false ;
8786 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- }
9287 return false ;
9388 }
9489 }
@@ -153,12 +148,43 @@ function runEntryPointWithESMLoader(callback) {
153148 * by `require('module')`) even when the entry point is ESM.
154149 * This monkey-patchable code is bypassed under `--experimental-default-type=module`.
155150 * Because of backwards compatibility, this function is exposed publicly via `import { runMain } from 'node:module'`.
151+ * When `--experimental-detect-module` is passed, this function will attempt to run ambiguous (no explicit extension, no
152+ * `package.json` type field) entry points as CommonJS first; under certain conditions, it will retry running as ESM.
156153 * @param {string } main - First positional CLI argument, such as `'entry.js'` from `node entry.js`
157154 */
158155function executeUserEntryPoint ( main = process . argv [ 1 ] ) {
159156 const resolvedMain = resolveMainPath ( main ) ;
160157 const useESMLoader = shouldUseESMLoader ( resolvedMain ) ;
161- if ( useESMLoader ) {
158+
159+ // Unless we know we should use the ESM loader to handle the entry point per the checks in `shouldUseESMLoader`, first
160+ // try to run the entry point via the CommonJS loader; and if that fails under certain conditions, retry as ESM.
161+ let retryAsESM = false ;
162+ if ( ! useESMLoader ) {
163+ const cjsLoader = require ( 'internal/modules/cjs/loader' ) ;
164+ const { Module } = cjsLoader ;
165+ if ( getOptionValue ( '--experimental-detect-module' ) ) {
166+ try {
167+ // Module._load is the monkey-patchable CJS module loader.
168+ Module . _load ( main , null , true ) ;
169+ } catch ( error ) {
170+ const source = cjsLoader . entryPointSource ;
171+ const { shouldRetryAsESM } = require ( 'internal/modules/helpers' ) ;
172+ retryAsESM = shouldRetryAsESM ( error . message , source ) ;
173+ // In case the entry point is a large file, such as a bundle,
174+ // ensure no further references can prevent it being garbage-collected.
175+ cjsLoader . entryPointSource = undefined ;
176+ if ( ! retryAsESM ) {
177+ const { enrichCJSError } = require ( 'internal/modules/esm/translators' ) ;
178+ enrichCJSError ( error , source , resolvedMain ) ;
179+ throw error ;
180+ }
181+ }
182+ } else { // `--experimental-detect-module` is not passed
183+ Module . _load ( main , null , true ) ;
184+ }
185+ }
186+
187+ if ( useESMLoader || retryAsESM ) {
162188 const mainPath = resolvedMain || main ;
163189 const mainURL = pathToFileURL ( mainPath ) . href ;
164190
@@ -167,10 +193,6 @@ function executeUserEntryPoint(main = process.argv[1]) {
167193 // even after the event loop stops running.
168194 return cascadedLoader . import ( mainURL , undefined , { __proto__ : null } , true ) ;
169195 } ) ;
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 ) ;
174196 }
175197}
176198
0 commit comments