5
5
globalThis,
6
6
} = primordials ;
7
7
8
- const { containsModuleSyntax } = internalBinding ( 'contextify' ) ;
9
8
const { getNearestParentPackageJSONType } = internalBinding ( 'modules' ) ;
10
9
const { getOptionValue } = require ( 'internal/options' ) ;
11
10
const { checkPackageJSONIntegrity } = require ( 'internal/modules/package_json_reader' ) ;
@@ -87,10 +86,6 @@ function shouldUseESMLoader(mainPath) {
87
86
88
87
// No package.json or no `type` field.
89
88
if ( response === undefined || response [ 0 ] === 'none' ) {
90
- if ( getOptionValue ( '--experimental-detect-module' ) ) {
91
- // If the first argument of `containsModuleSyntax` is undefined, it will read `mainPath` from the file system.
92
- return containsModuleSyntax ( undefined , mainPath ) ;
93
- }
94
89
return false ;
95
90
}
96
91
@@ -157,12 +152,43 @@ function runEntryPointWithESMLoader(callback) {
157
152
* by `require('module')`) even when the entry point is ESM.
158
153
* This monkey-patchable code is bypassed under `--experimental-default-type=module`.
159
154
* Because of backwards compatibility, this function is exposed publicly via `import { runMain } from 'node:module'`.
155
+ * When `--experimental-detect-module` is passed, this function will attempt to run ambiguous (no explicit extension, no
156
+ * `package.json` type field) entry points as CommonJS first; under certain conditions, it will retry running as ESM.
160
157
* @param {string } main - First positional CLI argument, such as `'entry.js'` from `node entry.js`
161
158
*/
162
159
function executeUserEntryPoint ( main = process . argv [ 1 ] ) {
163
160
const resolvedMain = resolveMainPath ( main ) ;
164
161
const useESMLoader = shouldUseESMLoader ( resolvedMain ) ;
165
- if ( useESMLoader ) {
162
+
163
+ // Unless we know we should use the ESM loader to handle the entry point per the checks in `shouldUseESMLoader`, first
164
+ // try to run the entry point via the CommonJS loader; and if that fails under certain conditions, retry as ESM.
165
+ let retryAsESM = false ;
166
+ if ( ! useESMLoader ) {
167
+ const cjsLoader = require ( 'internal/modules/cjs/loader' ) ;
168
+ const { Module } = cjsLoader ;
169
+ if ( getOptionValue ( '--experimental-detect-module' ) ) {
170
+ try {
171
+ // Module._load is the monkey-patchable CJS module loader.
172
+ Module . _load ( main , null , true ) ;
173
+ } catch ( error ) {
174
+ const source = cjsLoader . entryPointSource ;
175
+ const { shouldRetryAsESM } = require ( 'internal/modules/helpers' ) ;
176
+ retryAsESM = shouldRetryAsESM ( error . message , source ) ;
177
+ // In case the entry point is a large file, such as a bundle,
178
+ // ensure no further references can prevent it being garbage-collected.
179
+ cjsLoader . entryPointSource = undefined ;
180
+ if ( ! retryAsESM ) {
181
+ const { enrichCJSError } = require ( 'internal/modules/esm/translators' ) ;
182
+ enrichCJSError ( error , source , resolvedMain ) ;
183
+ throw error ;
184
+ }
185
+ }
186
+ } else { // `--experimental-detect-module` is not passed
187
+ Module . _load ( main , null , true ) ;
188
+ }
189
+ }
190
+
191
+ if ( useESMLoader || retryAsESM ) {
166
192
const mainPath = resolvedMain || main ;
167
193
const mainURL = pathToFileURL ( mainPath ) . href ;
168
194
@@ -171,10 +197,6 @@ function executeUserEntryPoint(main = process.argv[1]) {
171
197
// even after the event loop stops running.
172
198
return cascadedLoader . import ( mainURL , undefined , { __proto__ : null } , true ) ;
173
199
} ) ;
174
- } else {
175
- // Module._load is the monkey-patchable CJS module loader.
176
- const { Module } = require ( 'internal/modules/cjs/loader' ) ;
177
- Module . _load ( main , null , true ) ;
178
200
}
179
201
}
180
202
0 commit comments