module: support ESM detection in the CJS loader · nodejs/node@a03faf2
@@ -106,7 +106,6 @@ module.exports = {
106106 kModuleExportNames,
107107 kModuleCircularVisited,
108108 initializeCJS,
109-entryPointSource: undefined, // Set below.
110109 Module,
111110 wrapSafe,
112111 kIsMainSymbol,
@@ -1333,9 +1332,18 @@ function loadESMFromCJS(mod, filename) {
13331332const source = getMaybeCachedSource(mod, filename);
13341333const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
13351334const isMain = mod[kIsMainSymbol];
1336-// TODO(joyeecheung): we may want to invent optional special handling for default exports here.
1337-// For now, it's good enough to be identical to what `import()` returns.
1338-mod.exports = cascadedLoader.importSyncForRequire(mod, filename, source, isMain, mod[kModuleParent]);
1335+if (isMain) {
1336+require('internal/modules/run_main').runEntryPointWithESMLoader((cascadedLoader) => {
1337+const mainURL = pathToFileURL(filename).href;
1338+cascadedLoader.import(mainURL, undefined, { __proto__: null }, true);
1339+});
1340+// ESM won't be accessible via process.mainModule.
1341+setOwnProperty(process, 'mainModule', undefined);
1342+} else {
1343+// TODO(joyeecheung): we may want to invent optional special handling for default exports here.
1344+// For now, it's good enough to be identical to what `import()` returns.
1345+mod.exports = cascadedLoader.importSyncForRequire(mod, filename, source, isMain, mod[kModuleParent]);
1346+}
13391347}
1340134813411349/**
@@ -1344,8 +1352,10 @@ function loadESMFromCJS(mod, filename) {
13441352 * @param {string} content The content of the file being loaded
13451353 * @param {Module} cjsModuleInstance The CommonJS loader instance
13461354 * @param {object} codeCache The SEA code cache
1355+ * @param {'commonjs'|undefined} format Intended format of the module.
13471356 */
1348-function wrapSafe(filename, content, cjsModuleInstance, codeCache) {
1357+function wrapSafe(filename, content, cjsModuleInstance, codeCache, format) {
1358+assert(format !== 'module'); // ESM should be handled in loadESMFromCJS().
13491359const hostDefinedOptionId = vm_dynamic_import_default_internal;
13501360const importModuleDynamically = vm_dynamic_import_default_internal;
13511361if (patched) {
@@ -1375,46 +1385,33 @@ function wrapSafe(filename, content, cjsModuleInstance, codeCache) {
13751385};
13761386}
137713871378-try {
1379-const result = compileFunctionForCJSLoader(content, filename);
1380-1381-// cachedDataRejected is only set for cache coming from SEA.
1382-if (codeCache &&
1383-result.cachedDataRejected !== false &&
1384-internalBinding('sea').isSea()) {
1385-process.emitWarning('Code cache data rejected.');
1386-}
1388+const isMain = !!(cjsModuleInstance && cjsModuleInstance[kIsMainSymbol]);
1389+const shouldDetectModule = (format !== 'commonjs' && getOptionValue('--experimental-detect-module'));
1390+const result = compileFunctionForCJSLoader(content, filename, isMain, shouldDetectModule);
138713911388-// Cache the source map for the module if present.
1389-if (result.sourceMapURL) {
1390-maybeCacheSourceMap(filename, content, this, false, undefined, result.sourceMapURL);
1391-}
1392+// cachedDataRejected is only set for cache coming from SEA.
1393+if (codeCache &&
1394+result.cachedDataRejected !== false &&
1395+internalBinding('sea').isSea()) {
1396+process.emitWarning('Code cache data rejected.');
1397+}
139213981393-return result;
1394-} catch (err) {
1395-if (process.mainModule === cjsModuleInstance) {
1396-if (getOptionValue('--experimental-detect-module')) {
1397-// For the main entry point, cache the source to potentially retry as ESM.
1398-module.exports.entryPointSource = content;
1399-} else {
1400-// We only enrich the error (print a warning) if we're sure we're going to for-sure throw it; so if we're
1401-// retrying as ESM, wait until we know whether we're going to retry before calling `enrichCJSError`.
1402-const { enrichCJSError } = require('internal/modules/esm/translators');
1403-enrichCJSError(err, content, filename);
1404-}
1405-}
1406-throw err;
1399+// Cache the source map for the module if present.
1400+if (result.sourceMapURL) {
1401+maybeCacheSourceMap(filename, content, this, false, undefined, result.sourceMapURL);
14071402}
1403+1404+return result;
14081405}
1409140614101407/**
14111408 * Run the file contents in the correct scope or sandbox. Expose the correct helper variables (`require`, `module`,
14121409 * `exports`) to the file. Returns exception, if any.
14131410 * @param {string} content The source code of the module
14141411 * @param {string} filename The file path of the module
1415- * @param {boolean} loadAsESM Whether it's known to be ESM via .mjs or "type" in package.json.
1412+ * @param {'module'|'commonjs'|undefined} format Intended format of the module.
14161413 */
1417-Module.prototype._compile = function(content, filename, loadAsESM = false) {
1414+Module.prototype._compile = function(content, filename, format) {
14181415let moduleURL;
14191416let redirects;
14201417const manifest = policy()?.manifest;
@@ -1424,17 +1421,24 @@ Module.prototype._compile = function(content, filename, loadAsESM = false) {
14241421manifest.assertIntegrity(moduleURL, content);
14251422}
142614231424+let compiledWrapper;
1425+if (format !== 'module') {
1426+const result = wrapSafe(filename, content, this, undefined, format);
1427+compiledWrapper = result.function;
1428+if (result.canParseAsESM) {
1429+format = 'module';
1430+}
1431+}
1432+14271433// TODO(joyeecheung): when the module is the entry point, consider allowing TLA.
14281434// Only modules being require()'d really need to avoid TLA.
1429-if (loadAsESM) {
1435+if (format === 'module') {
14301436// Pass the source into the .mjs extension handler indirectly through the cache.
14311437this[kModuleSource] = content;
14321438loadESMFromCJS(this, filename);
14331439return;
14341440}
143514411436-const { function: compiledWrapper } = wrapSafe(filename, content, this);
1437-14381442// TODO(joyeecheung): the detection below is unnecessarily complex. Using the
14391443// kIsMainSymbol, or a kBreakOnStartSymbol that gets passed from
14401444// higher level instead of doing hacky detection here.
@@ -1511,12 +1515,13 @@ Module._extensions['.js'] = function(module, filename) {
15111515// If already analyzed the source, then it will be cached.
15121516const content = getMaybeCachedSource(module, filename);
151315171518+let format;
15141519if (StringPrototypeEndsWith(filename, '.js')) {
15151520const pkg = packageJsonReader.readPackageScope(filename) || { __proto__: null };
15161521// Function require shouldn't be used in ES modules.
15171522if (pkg.data?.type === 'module') {
15181523if (getOptionValue('--experimental-require-module')) {
1519-module._compile(content, filename, true);
1524+module._compile(content, filename, 'module');
15201525return;
15211526}
15221527@@ -1550,10 +1555,14 @@ Module._extensions['.js'] = function(module, filename) {
15501555}
15511556}
15521557throw err;
1558+} else if (pkg.data?.type === 'commonjs') {
1559+format = 'commonjs';
15531560}
1561+} else if (StringPrototypeEndsWith(filename, '.cjs')) {
1562+format = 'commonjs';
15541563}
155515641556-module._compile(content, filename, false);
1565+module._compile(content, filename, format);
15571566};
1558156715591568/**