esm: populate separate cache for require(esm) in imported CJS · nodejs/node@010458d
@@ -69,7 +69,8 @@ const {
6969 module_export_names_private_symbol,
7070 module_circular_visited_private_symbol,
7171 module_export_private_symbol,
72- module_parent_private_symbol,
72+ module_first_parent_private_symbol,
73+ module_last_parent_private_symbol,
7374},
7475 isInsideNodeModules,
7576} = internalBinding('util');
@@ -94,9 +95,13 @@ const kModuleCircularVisited = module_circular_visited_private_symbol;
9495 */
9596const kModuleExport = module_export_private_symbol;
9697/**
97- * {@link Module} parent module.
98+ * {@link Module} The first parent module that loads a module with require().
9899 */
99-const kModuleParent = module_parent_private_symbol;
100+const kFirstModuleParent = module_first_parent_private_symbol;
101+/**
102+ * {@link Module} The last parent module that loads a module with require().
103+ */
104+const kLastModuleParent = module_last_parent_private_symbol;
100105101106const kIsMainSymbol = Symbol('kIsMainSymbol');
102107const kIsCachedByESMLoader = Symbol('kIsCachedByESMLoader');
@@ -117,6 +122,7 @@ module.exports = {
117122 findLongestRegisteredExtension,
118123 resolveForCJSWithHooks,
119124loadSourceForCJSWithHooks: loadSource,
125+ populateCJSExportsFromESM,
120126 wrapSafe,
121127 wrapModuleLoad,
122128 kIsMainSymbol,
@@ -326,7 +332,8 @@ function Module(id = '', parent) {
326332this.id = id;
327333this.path = path.dirname(id);
328334setOwnProperty(this, 'exports', {});
329-this[kModuleParent] = parent;
335+this[kFirstModuleParent] = parent;
336+this[kLastModuleParent] = parent;
330337updateChildren(parent, this, false);
331338this.filename = null;
332339this.loaded = false;
@@ -408,7 +415,7 @@ ObjectDefineProperty(BuiltinModule.prototype, 'isPreloading', isPreloadingDesc);
408415 * @returns {object}
409416 */
410417function getModuleParent() {
411-return this[kModuleParent];
418+return this[kFirstModuleParent];
412419}
413420414421/**
@@ -418,7 +425,7 @@ function getModuleParent() {
418425 * @returns {void}
419426 */
420427function setModuleParent(value) {
421-this[kModuleParent] = value;
428+this[kFirstModuleParent] = value;
422429}
423430424431let debug = debuglog('module', (fn) => {
@@ -998,7 +1005,7 @@ function getExportsForCircularRequire(module) {
9981005const requiredESM = module[kRequiredModuleSymbol];
9991006if (requiredESM && requiredESM.getStatus() !== kEvaluated) {
10001007let message = `Cannot require() ES Module ${module.id} in a cycle.`;
1001-const parent = module[kModuleParent];
1008+const parent = module[kLastModuleParent];
10021009if (parent) {
10031010message += ` (from ${parent.filename})`;
10041011}
@@ -1279,6 +1286,8 @@ Module._load = function(request, parent, isMain) {
12791286// load hooks for the module keyed by the (potentially customized) filename.
12801287module[kURL] = url;
12811288module[kFormat] = format;
1289+} else {
1290+module[kLastModuleParent] = parent;
12821291}
1283129212841293if (parent !== undefined) {
@@ -1398,7 +1407,8 @@ Module._resolveFilename = function(request, parent, isMain, options) {
13981407const requireStack = [];
13991408for (let cursor = parent;
14001409cursor;
1401-cursor = cursor[kModuleParent]) {
1410+// TODO(joyeecheung): it makes more sense to use kLastModuleParent here.
1411+cursor = cursor[kFirstModuleParent]) {
14021412ArrayPrototypePush(requireStack, cursor.filename || cursor.id);
14031413}
14041414let message = `Cannot find module '${request}'`;
@@ -1515,7 +1525,7 @@ function loadESMFromCJS(mod, filename, format, source) {
15151525// ESM won't be accessible via process.mainModule.
15161526setOwnProperty(process, 'mainModule', undefined);
15171527} else {
1518-const parent = mod[kModuleParent];
1528+const parent = mod[kLastModuleParent];
1519152915201530requireModuleWarningMode ??= getOptionValue('--trace-require-module');
15211531if (requireModuleWarningMode) {
@@ -1565,54 +1575,66 @@ function loadESMFromCJS(mod, filename, format, source) {
15651575 wrap,
15661576 namespace,
15671577} = cascadedLoader.importSyncForRequire(mod, filename, source, isMain, parent);
1568-// Tooling in the ecosystem have been using the __esModule property to recognize
1569-// transpiled ESM in consuming code. For example, a 'log' package written in ESM:
1570-//
1571-// export default function log(val) { console.log(val); }
1572-//
1573-// Can be transpiled as:
1574-//
1575-// exports.__esModule = true;
1576-// exports.default = function log(val) { console.log(val); }
1577-//
1578-// The consuming code may be written like this in ESM:
1579-//
1580-// import log from 'log'
1581-//
1582-// Which gets transpiled to:
1583-//
1584-// const _mod = require('log');
1585-// const log = _mod.__esModule ? _mod.default : _mod;
1586-//
1587-// So to allow transpiled consuming code to recognize require()'d real ESM
1588-// as ESM and pick up the default exports, we add a __esModule property by
1589-// building a source text module facade for any module that has a default
1590-// export and add .__esModule = true to the exports. This maintains the
1591-// enumerability of the re-exported names and the live binding of the exports,
1592-// without incurring a non-trivial per-access overhead on the exports.
1593-//
1594-// The source of the facade is defined as a constant per-isolate property
1595-// required_module_default_facade_source_string, which looks like this
1596-//
1597-// export * from 'original';
1598-// export { default } from 'original';
1599-// export const __esModule = true;
1600-//
1601-// And the 'original' module request is always resolved by
1602-// createRequiredModuleFacade() to `wrap` which is a ModuleWrap wrapping
1603-// over the original module.
1604-1605-// We don't do this to modules that are marked as CJS ESM or that
1606-// don't have default exports to avoid the unnecessary overhead.
1607-// If __esModule is already defined, we will also skip the extension
1608-// to allow users to override it.
1609-if (ObjectHasOwn(namespace, 'module.exports')) {
1610-mod.exports = namespace['module.exports'];
1611-} else if (!ObjectHasOwn(namespace, 'default') || ObjectHasOwn(namespace, '__esModule')) {
1612-mod.exports = namespace;
1613-} else {
1614-mod.exports = createRequiredModuleFacade(wrap);
1615-}
1578+1579+populateCJSExportsFromESM(mod, wrap, namespace);
1580+}
1581+}
1582+1583+/**
1584+ * Populate the exports of a CJS module entry from an ESM module's namespace object for
1585+ * require(esm).
1586+ * @param {Module} mod CJS module instance
1587+ * @param {ModuleWrap} wrap ESM ModuleWrap instance.
1588+ * @param {object} namespace The ESM namespace object.
1589+ */
1590+function populateCJSExportsFromESM(mod, wrap, namespace) {
1591+// Tooling in the ecosystem have been using the __esModule property to recognize
1592+// transpiled ESM in consuming code. For example, a 'log' package written in ESM:
1593+//
1594+// export default function log(val) { console.log(val); }
1595+//
1596+// Can be transpiled as:
1597+//
1598+// exports.__esModule = true;
1599+// exports.default = function log(val) { console.log(val); }
1600+//
1601+// The consuming code may be written like this in ESM:
1602+//
1603+// import log from 'log'
1604+//
1605+// Which gets transpiled to:
1606+//
1607+// const _mod = require('log');
1608+// const log = _mod.__esModule ? _mod.default : _mod;
1609+//
1610+// So to allow transpiled consuming code to recognize require()'d real ESM
1611+// as ESM and pick up the default exports, we add a __esModule property by
1612+// building a source text module facade for any module that has a default
1613+// export and add .__esModule = true to the exports. This maintains the
1614+// enumerability of the re-exported names and the live binding of the exports,
1615+// without incurring a non-trivial per-access overhead on the exports.
1616+//
1617+// The source of the facade is defined as a constant per-isolate property
1618+// required_module_default_facade_source_string, which looks like this
1619+//
1620+// export * from 'original';
1621+// export { default } from 'original';
1622+// export const __esModule = true;
1623+//
1624+// And the 'original' module request is always resolved by
1625+// createRequiredModuleFacade() to `wrap` which is a ModuleWrap wrapping
1626+// over the original module.
1627+1628+// We don't do this to modules that are marked as CJS ESM or that
1629+// don't have default exports to avoid the unnecessary overhead.
1630+// If __esModule is already defined, we will also skip the extension
1631+// to allow users to override it.
1632+if (ObjectHasOwn(namespace, 'module.exports')) {
1633+mod.exports = namespace['module.exports'];
1634+} else if (!ObjectHasOwn(namespace, 'default') || ObjectHasOwn(namespace, '__esModule')) {
1635+mod.exports = namespace;
1636+} else {
1637+mod.exports = createRequiredModuleFacade(wrap);
16161638}
16171639}
16181640@@ -1805,7 +1827,7 @@ function reconstructErrorStack(err, parentPath, parentSource) {
18051827 */
18061828function getRequireESMError(mod, pkg, content, filename) {
18071829// This is an error path because `require` of a `.js` file in a `"type": "module"` scope is not allowed.
1808-const parent = mod[kModuleParent];
1830+const parent = mod[kFirstModuleParent];
18091831const parentPath = parent?.filename;
18101832const packageJsonPath = pkg?.path;
18111833const usesEsm = containsModuleSyntax(content, filename);