lib: convert WeakMaps in cjs loader with private symbol properties · nodejs/node@3a2d8bf
@@ -50,7 +50,6 @@ const {
5050 ReflectSet,
5151 RegExpPrototypeExec,
5252 SafeMap,
53- SafeWeakMap,
5453 String,
5554 StringPrototypeCharAt,
5655 StringPrototypeCharCodeAt,
@@ -62,25 +61,50 @@ const {
6261 StringPrototypeStartsWith,
6362 Symbol,
6463} = primordials;
64+const {
65+privateSymbols: {
66+ module_source_private_symbol,
67+ module_export_names_private_symbol,
68+ module_circular_visited_private_symbol,
69+ module_export_private_symbol,
70+ module_parent_private_symbol,
71+},
72+} = internalBinding('util');
65736674const { kEvaluated } = internalBinding('module_wrap');
677568-// Map used to store CJS parsing data or for ESM loading.
69-const importedCJSCache = new SafeWeakMap();
76+// Internal properties for Module instances.
77+/**
78+ * Cached {@link Module} source string.
79+ */
80+const kModuleSource = module_source_private_symbol;
81+/**
82+ * Cached {@link Module} export names for ESM loader.
83+ */
84+const kModuleExportNames = module_export_names_private_symbol;
85+/**
86+ * {@link Module} circular dependency visited flag.
87+ */
88+const kModuleCircularVisited = module_circular_visited_private_symbol;
7089/**
71- * Map of already-loaded CJS modules to use.
90+ * {@link Module} export object snapshot for ESM loader.
7291 */
73-const cjsExportsCache = new SafeWeakMap();
74-const requiredESMSourceCache = new SafeWeakMap();
92+const kModuleExport = module_export_private_symbol;
93+/**
94+ * {@link Module} parent module.
95+ */
96+const kModuleParent = module_parent_private_symbol;
75977698const kIsMainSymbol = Symbol('kIsMainSymbol');
7799const kIsCachedByESMLoader = Symbol('kIsCachedByESMLoader');
78100const kRequiredModuleSymbol = Symbol('kRequiredModuleSymbol');
79101const kIsExecuting = Symbol('kIsExecuting');
80102// Set first due to cycle with ESM loader functions.
81103module.exports = {
82- cjsExportsCache,
83- importedCJSCache,
104+ kModuleSource,
105+ kModuleExport,
106+ kModuleExportNames,
107+ kModuleCircularVisited,
84108 initializeCJS,
85109 Module,
86110 wrapSafe,
@@ -256,8 +280,6 @@ function reportModuleNotFoundToWatchMode(basePath, extensions) {
256280}
257281}
258282259-/** @type {Map<Module, Module>} */
260-const moduleParentCache = new SafeWeakMap();
261283/**
262284 * Create a new module instance.
263285 * @param {string} id
@@ -267,7 +289,7 @@ function Module(id = '', parent) {
267289this.id = id;
268290this.path = path.dirname(id);
269291setOwnProperty(this, 'exports', {});
270-moduleParentCache.set(this, parent);
292+this[kModuleParent] = parent;
271293updateChildren(parent, this, false);
272294this.filename = null;
273295this.loaded = false;
@@ -355,17 +377,19 @@ ObjectDefineProperty(BuiltinModule.prototype, 'isPreloading', isPreloadingDesc);
355377356378/**
357379 * Get the parent of the current module from our cache.
380+ * @this {Module}
358381 */
359382function getModuleParent() {
360-return moduleParentCache.get(this);
383+return this[kModuleParent];
361384}
362385363386/**
364387 * Set the parent of the current module in our cache.
388+ * @this {Module}
365389 * @param {Module} value
366390 */
367391function setModuleParent(value) {
368-moduleParentCache.set(this, value);
392+this[kModuleParent] = value;
369393}
370394371395let debug = require('internal/util/debuglog').debuglog('module', (fn) => {
@@ -955,7 +979,7 @@ function getExportsForCircularRequire(module) {
955979const requiredESM = module[kRequiredModuleSymbol];
956980if (requiredESM && requiredESM.getStatus() !== kEvaluated) {
957981let message = `Cannot require() ES Module ${module.id} in a cycle.`;
958-const parent = moduleParentCache.get(module);
982+const parent = module[kModuleParent];
959983if (parent) {
960984message += ` (from ${parent.filename})`;
961985}
@@ -1028,25 +1052,24 @@ Module._load = function(request, parent, isMain) {
10281052const cachedModule = Module._cache[filename];
10291053if (cachedModule !== undefined) {
10301054updateChildren(parent, cachedModule, true);
1031-if (!cachedModule.loaded) {
1032-// If it's not cached by the ESM loader, the loading request
1033-// comes from required CJS, and we can consider it a circular
1034-// dependency when it's cached.
1035-if (!cachedModule[kIsCachedByESMLoader]) {
1036-return getExportsForCircularRequire(cachedModule);
1037-}
1038-// If it's cached by the ESM loader as a way to indirectly pass
1039-// the module in to avoid creating it twice, the loading request
1040-// come from imported CJS. In that case use the importedCJSCache
1041-// to determine if it's loading or not.
1042-const importedCJSMetadata = importedCJSCache.get(cachedModule);
1043-if (importedCJSMetadata.loading) {
1044-return getExportsForCircularRequire(cachedModule);
1045-}
1046-importedCJSMetadata.loading = true;
1047-} else {
1055+if (cachedModule.loaded) {
10481056return cachedModule.exports;
10491057}
1058+// If it's not cached by the ESM loader, the loading request
1059+// comes from required CJS, and we can consider it a circular
1060+// dependency when it's cached.
1061+if (!cachedModule[kIsCachedByESMLoader]) {
1062+return getExportsForCircularRequire(cachedModule);
1063+}
1064+// If it's cached by the ESM loader as a way to indirectly pass
1065+// the module in to avoid creating it twice, the loading request
1066+// come from imported CJS. In that case use the kModuleCircularVisited
1067+// to determine if it's loading or not.
1068+if (cachedModule[kModuleCircularVisited]) {
1069+return getExportsForCircularRequire(cachedModule);
1070+}
1071+// This is an ESM loader created cache entry, mark it as visited and fallthrough to loading the module.
1072+cachedModule[kModuleCircularVisited] = true;
10501073}
1051107410521075if (BuiltinModule.canBeRequiredWithoutScheme(filename)) {
@@ -1190,7 +1213,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {
11901213const requireStack = [];
11911214for (let cursor = parent;
11921215cursor;
1193-cursor = moduleParentCache.get(cursor)) {
1216+cursor = cursor[kModuleParent]) {
11941217ArrayPrototypePush(requireStack, cursor.filename || cursor.id);
11951218}
11961219let message = `Cannot find module '${request}'`;
@@ -1268,9 +1291,7 @@ Module.prototype.load = function(filename) {
12681291// Create module entry at load time to snapshot exports correctly
12691292const exports = this.exports;
12701293// Preemptively cache for ESM loader.
1271-if (!cjsExportsCache.has(this)) {
1272-cjsExportsCache.set(this, exports);
1273-}
1294+this[kModuleExport] = exports;
12741295};
1275129612761297/**
@@ -1313,7 +1334,7 @@ function loadESMFromCJS(mod, filename) {
13131334const isMain = mod[kIsMainSymbol];
13141335// TODO(joyeecheung): we may want to invent optional special handling for default exports here.
13151336// For now, it's good enough to be identical to what `import()` returns.
1316-mod.exports = cascadedLoader.importSyncForRequire(mod, filename, source, isMain, moduleParentCache.get(mod));
1337+mod.exports = cascadedLoader.importSyncForRequire(mod, filename, source, isMain, mod[kModuleParent]);
13171338}
1318133913191340/**
@@ -1399,7 +1420,7 @@ Module.prototype._compile = function(content, filename, loadAsESM = false) {
13991420// Only modules being require()'d really need to avoid TLA.
14001421if (loadAsESM) {
14011422// Pass the source into the .mjs extension handler indirectly through the cache.
1402-requiredESMSourceCache.set(this, content);
1423+this[kModuleSource] = content;
14031424loadESMFromCJS(this, filename);
14041425return;
14051426}
@@ -1460,15 +1481,15 @@ Module.prototype._compile = function(content, filename, loadAsESM = false) {
14601481 * @returns {string}
14611482 */
14621483function getMaybeCachedSource(mod, filename) {
1463-const cached = importedCJSCache.get(mod);
1484+// If already analyzed the source, then it will be cached.
14641485let content;
1465-if (cached?.source) {
1466-content = cached.source;
1467-cached.source = undefined;
1486+if (mod[kModuleSource] !== undefined) {
1487+content = mod[kModuleSource];
1488+mod[kModuleSource] = undefined;
14681489} else {
14691490// TODO(joyeecheung): we can read a buffer instead to speed up
14701491// compilation.
1471-content = requiredESMSourceCache.get(mod) ?? fs.readFileSync(filename, 'utf8');
1492+content = fs.readFileSync(filename, 'utf8');
14721493}
14731494return content;
14741495}
@@ -1492,7 +1513,7 @@ Module._extensions['.js'] = function(module, filename) {
14921513}
1493151414941515// This is an error path because `require` of a `.js` file in a `"type": "module"` scope is not allowed.
1495-const parent = moduleParentCache.get(module);
1516+const parent = module[kModuleParent];
14961517const parentPath = parent?.filename;
14971518const packageJsonPath = path.resolve(pkg.path, 'package.json');
14981519const usesEsm = containsModuleSyntax(content, filename);