module: eliminate performance cost of detection for cjs entry · nodejs/node@132a5c1
@@ -4,7 +4,6 @@ const {
44 StringPrototypeEndsWith,
55} = primordials;
667-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) {
8584case 'commonjs':
8685return false;
8786default: { // 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-}
9287return 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]) {
159156const resolvedMain = resolveMainPath(main);
160157const 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) {
162188const mainPath = resolvedMain || main;
163189const mainURL = pathToFileURL(mainPath).href;
164190@@ -167,10 +193,6 @@ function executeUserEntryPoint(main = process.argv[1]) {
167193// even after the event loop stops running.
168194return 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