@@ -100,6 +100,9 @@ const kIsMainSymbol = Symbol('kIsMainSymbol');
100100const kIsCachedByESMLoader = Symbol('kIsCachedByESMLoader');
101101const kRequiredModuleSymbol = Symbol('kRequiredModuleSymbol');
102102const kIsExecuting = Symbol('kIsExecuting');
103+
104+ const kFormat = Symbol('kFormat');
105+
103106// Set first due to cycle with ESM loader functions.
104107module.exports = {
105108 kModuleSource,
@@ -436,9 +439,8 @@ function initializeCJS() {
436439 Module._extensions['.ts'] = loadTS;
437440 }
438441 if (getOptionValue('--experimental-require-module')) {
439- Module._extensions['.mjs'] = loadESMFromCJS;
440442 if (tsEnabled) {
441- Module._extensions['.mts'] = loadESMFromCJS ;
443+ Module._extensions['.mts'] = loadMTS ;
442444 }
443445 }
444446}
@@ -653,8 +655,6 @@ function getDefaultExtensions() {
653655 if (tsEnabled) {
654656 // remove .ts and .cts from the default extensions
655657 // to avoid extensionless require of .ts and .cts files.
656- // it behaves similarly to how .mjs is handled when --experimental-require-module
657- // is enabled.
658658 extensions = ArrayPrototypeFilter(extensions, (ext) =>
659659 (ext !== '.ts' || Module._extensions['.ts'] !== loadTS) &&
660660 (ext !== '.cts' || Module._extensions['.cts'] !== loadCTS),
@@ -667,14 +667,10 @@ function getDefaultExtensions() {
667667
668668 if (tsEnabled) {
669669 extensions = ArrayPrototypeFilter(extensions, (ext) =>
670- ext !== '.mts' || Module._extensions['.mts'] !== loadESMFromCJS ,
670+ ext !== '.mts' || Module._extensions['.mts'] !== loadMTS ,
671671 );
672672 }
673- // If the .mjs extension is added by --experimental-require-module,
674- // remove it from the supported default extensions to maintain
675- // compatibility.
676- // TODO(joyeecheung): allow both .mjs and .cjs?
677- return ArrayPrototypeFilter(extensions, (ext) => ext !== '.mjs' || Module._extensions['.mjs'] !== loadESMFromCJS);
673+ return extensions;
678674}
679675
680676/**
@@ -1301,10 +1297,6 @@ Module.prototype.load = function(filename) {
13011297 this.paths = Module._nodeModulePaths(path.dirname(filename));
13021298
13031299 const extension = findLongestRegisteredExtension(filename);
1304- // allow .mjs to be overridden
1305- if (StringPrototypeEndsWith(filename, '.mjs') && !Module._extensions['.mjs']) {
1306- throw new ERR_REQUIRE_ESM(filename, true);
1307- }
13081300
13091301 if (getOptionValue('--experimental-strip-types')) {
13101302 if (StringPrototypeEndsWith(filename, '.mts') && !Module._extensions['.mts']) {
@@ -1353,12 +1345,10 @@ let hasPausedEntry = false;
13531345 * Resolve and evaluate it synchronously as ESM if it's ESM.
13541346 * @param {Module} mod CJS module instance
13551347 * @param {string} filename Absolute path of the file.
1348+ * @param {string} format Format of the module. If it had types, this would be what it is after type-stripping.
1349+ * @param {string} source Source the module. If it had types, this would have the type stripped.
13561350 */
1357- function loadESMFromCJS(mod, filename) {
1358- let source = getMaybeCachedSource(mod, filename);
1359- if (getOptionValue('--experimental-strip-types') && path.extname(filename) === '.mts') {
1360- source = stripTypeScriptModuleTypes(source, filename);
1361- }
1351+ function loadESMFromCJS(mod, filename, format, source) {
13621352 const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
13631353 const isMain = mod[kIsMainSymbol];
13641354 if (isMain) {
@@ -1512,9 +1502,30 @@ function wrapSafe(filename, content, cjsModuleInstance, format) {
15121502 * `exports`) to the file. Returns exception, if any.
15131503 * @param {string} content The source code of the module
15141504 * @param {string} filename The file path of the module
1515- * @param {'module'|'commonjs'|undefined} format Intended format of the module.
1505+ * @param {
1506+ * 'module'|'commonjs'|'commonjs-typescript'|'module-typescript'
1507+ * } format Intended format of the module.
15161508 */
15171509Module.prototype._compile = function(content, filename, format) {
1510+ if (format === 'commonjs-typescript' || format === 'module-typescript' || format === 'typescript') {
1511+ content = stripTypeScriptModuleTypes(content, filename);
1512+ switch (format) {
1513+ case 'commonjs-typescript': {
1514+ format = 'commonjs';
1515+ break;
1516+ }
1517+ case 'module-typescript': {
1518+ format = 'module';
1519+ break;
1520+ }
1521+ // If the format is still unknown i.e. 'typescript', detect it in
1522+ // wrapSafe using the type-stripped source.
1523+ default:
1524+ format = undefined;
1525+ break;
1526+ }
1527+ }
1528+
15181529 let redirects;
15191530
15201531 let compiledWrapper;
@@ -1527,9 +1538,7 @@ Module.prototype._compile = function(content, filename, format) {
15271538 }
15281539
15291540 if (format === 'module') {
1530- // Pass the source into the .mjs extension handler indirectly through the cache.
1531- this[kModuleSource] = content;
1532- loadESMFromCJS(this, filename);
1541+ loadESMFromCJS(this, filename, format, content);
15331542 return;
15341543 }
15351544
@@ -1582,72 +1591,76 @@ Module.prototype._compile = function(content, filename, format) {
15821591
15831592/**
15841593 * Get the source code of a module, using cached ones if it's cached.
1594+ * After this returns, mod[kFormat], mod[kModuleSource] and mod[kURL] will be set.
15851595 * @param {Module} mod Module instance whose source is potentially already cached.
15861596 * @param {string} filename Absolute path to the file of the module.
1587- * @returns {string}
1597+ * @returns {{source: string, format?: string} }
15881598 */
1589- function getMaybeCachedSource(mod, filename) {
1590- // If already analyzed the source, then it will be cached.
1591- let content;
1592- if (mod[kModuleSource] !== undefined) {
1593- content = mod[kModuleSource];
1599+ function loadSource(mod, filename, formatFromNode) {
1600+ if (formatFromNode !== undefined) {
1601+ mod[kFormat] = formatFromNode;
1602+ }
1603+ const format = mod[kFormat];
1604+
1605+ let source = mod[kModuleSource];
1606+ if (source !== undefined) {
15941607 mod[kModuleSource] = undefined;
15951608 } else {
15961609 // TODO(joyeecheung): we can read a buffer instead to speed up
15971610 // compilation.
1598- content = fs.readFileSync(filename, 'utf8');
1611+ source = fs.readFileSync(filename, 'utf8');
15991612 }
1600- return content;
1613+ return { source, format };
1614+ }
1615+
1616+ /**
1617+ * Built-in handler for `.mts` files.
1618+ * @param {Module} mod CJS module instance
1619+ * @param {string} filename The file path of the module
1620+ */
1621+ function loadMTS(mod, filename) {
1622+ const loadResult = loadSource(mod, filename, 'module-typescript');
1623+ mod._compile(loadResult.source, filename, loadResult.format);
16011624}
16021625
1626+ /**
1627+ * Built-in handler for `.cts` files.
1628+ * @param {Module} module CJS module instance
1629+ * @param {string} filename The file path of the module
1630+ */
1631+
16031632function loadCTS(module, filename) {
1604- const source = getMaybeCachedSource(module, filename);
1605- const code = stripTypeScriptModuleTypes(source, filename);
1606- module._compile(code, filename, 'commonjs');
1633+ const loadResult = loadSource(module, filename, 'commonjs-typescript');
1634+ module._compile(loadResult.source, filename, loadResult.format);
16071635}
16081636
16091637/**
16101638 * Built-in handler for `.ts` files.
1611- * @param {Module} module The module to compile
1639+ * @param {Module} module CJS module instance
16121640 * @param {string} filename The file path of the module
16131641 */
16141642function loadTS(module, filename) {
1615- // If already analyzed the source, then it will be cached.
1616- const source = getMaybeCachedSource(module, filename);
1617- const content = stripTypeScriptModuleTypes(source, filename);
1618- let format;
16191643 const pkg = packageJsonReader.getNearestParentPackageJSON(filename);
1620- // Function require shouldn't be used in ES modules.
1621- if (pkg?.data.type === 'module') {
1622- if (getOptionValue('--experimental-require-module')) {
1623- module._compile(content, filename, 'module');
1624- return;
1625- }
1644+ const typeFromPjson = pkg?.data.type;
16261645
1627- const parent = module[kModuleParent];
1628- const parentPath = parent?.filename;
1629- const packageJsonPath = pkg.path;
1630- const usesEsm = containsModuleSyntax(content, filename);
1631- const err = new ERR_REQUIRE_ESM(filename, usesEsm, parentPath,
1632- packageJsonPath);
1633- // Attempt to reconstruct the parent require frame.
1634- if (Module._cache[parentPath]) {
1635- let parentSource;
1636- try {
1637- parentSource = stripTypeScriptModuleTypes(fs.readFileSync(parentPath, 'utf8'), parentPath);
1638- } catch {
1639- // Continue regardless of error.
1640- }
1641- if (parentSource) {
1642- reconstructErrorStack(err, parentPath, parentSource);
1643- }
1644- }
1646+ let format;
1647+ if (typeFromPjson === 'module') {
1648+ format = 'module-typescript';
1649+ } else if (typeFromPjson === 'commonjs') {
1650+ format = 'commonjs-typescript';
1651+ } else {
1652+ format = 'typescript';
1653+ }
1654+ const loadResult = loadSource(module, filename, format);
1655+
1656+ // Function require shouldn't be used in ES modules when require(esm) is disabled.
1657+ if (typeFromPjson === 'module' && !getOptionValue('--experimental-require-module')) {
1658+ const err = getRequireESMError(module, pkg, loadResult.source, filename);
16451659 throw err;
1646- } else if (pkg?.data.type === 'commonjs') {
1647- format = 'commonjs';
16481660 }
16491661
1650- module._compile(content, filename, format);
1662+ module[kFormat] = loadResult.format;
1663+ module._compile(loadResult.source, filename, loadResult.format);
16511664};
16521665
16531666function reconstructErrorStack(err, parentPath, parentSource) {
@@ -1663,53 +1676,64 @@ function reconstructErrorStack(err, parentPath, parentSource) {
16631676 }
16641677}
16651678
1679+ /**
1680+ * Generate the legacy ERR_REQUIRE_ESM for the cases where require(esm) is disabled.
1681+ * @param {Module} mod The module being required.
1682+ * @param {undefined|object} pkg Data of the nearest package.json of the module.
1683+ * @param {string} content Source code of the module.
1684+ * @param {string} filename Filename of the module
1685+ * @returns {Error}
1686+ */
1687+ function getRequireESMError(mod, pkg, content, filename) {
1688+ // This is an error path because `require` of a `.js` file in a `"type": "module"` scope is not allowed.
1689+ const parent = mod[kModuleParent];
1690+ const parentPath = parent?.filename;
1691+ const packageJsonPath = pkg?.path;
1692+ const usesEsm = containsModuleSyntax(content, filename);
1693+ const err = new ERR_REQUIRE_ESM(filename, usesEsm, parentPath,
1694+ packageJsonPath);
1695+ // Attempt to reconstruct the parent require frame.
1696+ const parentModule = Module._cache[parentPath];
1697+ if (parentModule) {
1698+ let parentSource;
1699+ try {
1700+ ({ source: parentSource } = loadSource(parentModule, parentPath));
1701+ } catch {
1702+ // Continue regardless of error.
1703+ }
1704+ if (parentSource) {
1705+ // TODO(joyeecheung): trim off internal frames from the stack.
1706+ reconstructErrorStack(err, parentPath, parentSource);
1707+ }
1708+ }
1709+ return err;
1710+ }
1711+
16661712/**
16671713 * Built-in handler for `.js` files.
16681714 * @param {Module} module The module to compile
16691715 * @param {string} filename The file path of the module
16701716 */
16711717Module._extensions['.js'] = function(module, filename) {
1672- // If already analyzed the source, then it will be cached.
1673- const content = getMaybeCachedSource(module, filename);
1674-
1675- let format;
1676- if (StringPrototypeEndsWith(filename, '.js')) {
1677- const pkg = packageJsonReader.getNearestParentPackageJSON(filename);
1678- // Function require shouldn't be used in ES modules.
1679- if (pkg?.data.type === 'module') {
1680- if (getOptionValue('--experimental-require-module')) {
1681- module._compile(content, filename, 'module');
1682- return;
1683- }
1684-
1685- // This is an error path because `require` of a `.js` file in a `"type": "module"` scope is not allowed.
1686- const parent = module[kModuleParent];
1687- const parentPath = parent?.filename;
1688- const packageJsonPath = pkg.path;
1689- const usesEsm = containsModuleSyntax(content, filename);
1690- const err = new ERR_REQUIRE_ESM(filename, usesEsm, parentPath,
1691- packageJsonPath);
1692- // Attempt to reconstruct the parent require frame.
1693- if (Module._cache[parentPath]) {
1694- let parentSource;
1695- try {
1696- parentSource = fs.readFileSync(parentPath, 'utf8');
1697- } catch {
1698- // Continue regardless of error.
1699- }
1700- if (parentSource) {
1701- reconstructErrorStack(err, parentPath, parentSource);
1702- }
1703- }
1704- throw err;
1705- } else if (pkg?.data.type === 'commonjs') {
1706- format = 'commonjs';
1707- }
1708- } else if (StringPrototypeEndsWith(filename, '.cjs')) {
1718+ let format, pkg;
1719+ if (StringPrototypeEndsWith(filename, '.cjs')) {
17091720 format = 'commonjs';
1721+ } else if (StringPrototypeEndsWith(filename, '.mjs')) {
1722+ format = 'module';
1723+ } else if (StringPrototypeEndsWith(filename, '.js')) {
1724+ pkg = packageJsonReader.getNearestParentPackageJSON(filename);
1725+ const typeFromPjson = pkg?.data.type;
1726+ if (typeFromPjson === 'module' || typeFromPjson === 'commonjs' || !typeFromPjson) {
1727+ format = typeFromPjson;
1728+ }
17101729 }
1711-
1712- module._compile(content, filename, format);
1730+ const { source, format: loadedFormat } = loadSource(module, filename, format);
1731+ // Function require shouldn't be used in ES modules when require(esm) is disabled.
1732+ if (loadedFormat === 'module' && !getOptionValue('--experimental-require-module')) {
1733+ const err = getRequireESMError(module, pkg, source, filename);
1734+ throw err;
1735+ }
1736+ module._compile(source, filename, loadedFormat);
17131737};
17141738
17151739/**
0 commit comments