◐ Shell
clean mode source ↗

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) {

13331332

const source = getMaybeCachedSource(mod, filename);

13341333

const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();

13351334

const 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().

13491359

const hostDefinedOptionId = vm_dynamic_import_default_internal;

13501360

const importModuleDynamically = vm_dynamic_import_default_internal;

13511361

if (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) {

14181415

let moduleURL;

14191416

let redirects;

14201417

const manifest = policy()?.manifest;

@@ -1424,17 +1421,24 @@ Module.prototype._compile = function(content, filename, loadAsESM = false) {

14241421

manifest.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.

14311437

this[kModuleSource] = content;

14321438

loadESMFromCJS(this, filename);

14331439

return;

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.

15121516

const content = getMaybeCachedSource(module, filename);

151315171518+

let format;

15141519

if (StringPrototypeEndsWith(filename, '.js')) {

15151520

const pkg = packageJsonReader.readPackageScope(filename) || { __proto__: null };

15161521

// Function require shouldn't be used in ES modules.

15171522

if (pkg.data?.type === 'module') {

15181523

if (getOptionValue('--experimental-require-module')) {

1519-

module._compile(content, filename, true);

1524+

module._compile(content, filename, 'module');

15201525

return;

15211526

}

15221527

@@ -1550,10 +1555,14 @@ Module._extensions['.js'] = function(module, filename) {

15501555

}

15511556

}

15521557

throw 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

/**