Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 56c46ab

Browse filesBrowse files
joyeecheungRafaelGSS
authored andcommitted
module: unify TypeScript and .mjs handling in CommonJS
This refactors the CommonJS loading a bit to create a center point that handles source loading (`loadSource`) and make format detection more consistent to pave the way for future synchronous hooks. - Handle .mjs in the .js handler, similar to how .cjs has been handled. - Generate the legacy ERR_REQUIRE_ESM in a getRequireESMError() for both .mts and require(esm) handling (when it's disabled). PR-URL: #55590 Refs: nodejs/loaders#198 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com> Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com>
1 parent 25b1422 commit 56c46ab
Copy full SHA for 56c46ab

File tree

Expand file treeCollapse file tree

1 file changed

+129
-105
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

1 file changed

+129
-105
lines changed
Open diff view settings
Collapse file

‎lib/internal/modules/cjs/loader.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/cjs/loader.js
+129-105Lines changed: 129 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ const kIsMainSymbol = Symbol('kIsMainSymbol');
100100
const kIsCachedByESMLoader = Symbol('kIsCachedByESMLoader');
101101
const kRequiredModuleSymbol = Symbol('kRequiredModuleSymbol');
102102
const kIsExecuting = Symbol('kIsExecuting');
103+
104+
const kFormat = Symbol('kFormat');
105+
103106
// Set first due to cycle with ESM loader functions.
104107
module.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
*/
15171509
Module.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+
16031632
function 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
*/
16141642
function 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

16531666
function 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
*/
16711717
Module._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

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.