4
4
StringPrototypeEndsWith,
5
5
} = primordials ;
6
6
7
- const { containsModuleSyntax } = internalBinding ( 'contextify' ) ;
8
7
const { getOptionValue } = require ( 'internal/options' ) ;
9
8
const path = require ( 'path' ) ;
10
9
const { pathToFileURL } = require ( 'internal/url' ) ;
@@ -44,6 +43,7 @@ function resolveMainPath(main) {
44
43
} catch ( err ) {
45
44
if ( defaultType === 'module' && err ?. code === 'ENOENT' ) {
46
45
const { decorateErrorWithCommonJSHints } = require ( 'internal/modules/esm/resolve' ) ;
46
+ const { getCWDURL } = require ( 'internal/util' ) ;
47
47
decorateErrorWithCommonJSHints ( err , mainPath , getCWDURL ( ) ) ;
48
48
}
49
49
throw err ;
@@ -85,10 +85,6 @@ function shouldUseESMLoader(mainPath) {
85
85
case 'commonjs' :
86
86
return false ;
87
87
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
- }
92
88
return false ;
93
89
}
94
90
}
@@ -153,12 +149,43 @@ function runEntryPointWithESMLoader(callback) {
153
149
* by `require('module')`) even when the entry point is ESM.
154
150
* This monkey-patchable code is bypassed under `--experimental-default-type=module`.
155
151
* 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.
156
154
* @param {string } main - First positional CLI argument, such as `'entry.js'` from `node entry.js`
157
155
*/
158
156
function executeUserEntryPoint ( main = process . argv [ 1 ] ) {
159
157
const resolvedMain = resolveMainPath ( main ) ;
160
158
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 ) {
162
189
const mainPath = resolvedMain || main ;
163
190
const mainURL = pathToFileURL ( mainPath ) . href ;
164
191
@@ -167,10 +194,6 @@ function executeUserEntryPoint(main = process.argv[1]) {
167
194
// even after the event loop stops running.
168
195
return cascadedLoader . import ( mainURL , undefined , { __proto__ : null } , true ) ;
169
196
} ) ;
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 ) ;
174
197
}
175
198
}
176
199
0 commit comments