Skip to content

Commit 3a2d8bf

Browse files
legendecasmarco-ippolito
authored andcommitted
lib: convert WeakMaps in cjs loader with private symbol properties
Symbol properties are typically more GC-efficient than using WeakMaps, since WeakMap requires ephemeron GC. `module[kModuleExportNames]` would be easier to read than `importedCJSCache.get(module).exportNames` as well. PR-URL: #52095 Backport-PR-URL: #53500 Reviewed-By: Geoffrey Booth <[email protected]> Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent 51b88fa commit 3a2d8bf

File tree

3 files changed

+79
-54
lines changed

3 files changed

+79
-54
lines changed

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

Lines changed: 64 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -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');
6573

6674
const{ kEvaluated }=internalBinding('module_wrap');
6775

68-
// Map used to store CJS parsing data or for ESM loading.
69-
constimportedCJSCache=newSafeWeakMap();
76+
// Internal properties for Module instances.
77+
/**
78+
* Cached{@link Module} source string.
79+
*/
80+
constkModuleSource=module_source_private_symbol;
81+
/**
82+
* Cached{@link Module} export names for ESM loader.
83+
*/
84+
constkModuleExportNames=module_export_names_private_symbol;
85+
/**
86+
*{@link Module} circular dependency visited flag.
87+
*/
88+
constkModuleCircularVisited=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-
constcjsExportsCache=newSafeWeakMap();
74-
constrequiredESMSourceCache=newSafeWeakMap();
92+
constkModuleExport=module_export_private_symbol;
93+
/**
94+
*{@link Module} parent module.
95+
*/
96+
constkModuleParent=module_parent_private_symbol;
7597

7698
constkIsMainSymbol=Symbol('kIsMainSymbol');
7799
constkIsCachedByESMLoader=Symbol('kIsCachedByESMLoader');
78100
constkRequiredModuleSymbol=Symbol('kRequiredModuleSymbol');
79101
constkIsExecuting=Symbol('kIsExecuting');
80102
// Set first due to cycle with ESM loader functions.
81103
module.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
}
258282

259-
/** @type{Map<Module, Module>} */
260-
constmoduleParentCache=newSafeWeakMap();
261283
/**
262284
* Create a new module instance.
263285
* @param{string} id
@@ -267,7 +289,7 @@ function Module(id = '', parent){
267289
this.id=id;
268290
this.path=path.dirname(id);
269291
setOwnProperty(this,'exports',{});
270-
moduleParentCache.set(this,parent);
292+
this[kModuleParent]=parent;
271293
updateChildren(parent,this,false);
272294
this.filename=null;
273295
this.loaded=false;
@@ -355,17 +377,19 @@ ObjectDefineProperty(BuiltinModule.prototype, 'isPreloading', isPreloadingDesc);
355377

356378
/**
357379
* Get the parent of the current module from our cache.
380+
* @this{Module}
358381
*/
359382
functiongetModuleParent(){
360-
returnmoduleParentCache.get(this);
383+
returnthis[kModuleParent];
361384
}
362385

363386
/**
364387
* Set the parent of the current module in our cache.
388+
* @this{Module}
365389
* @param{Module} value
366390
*/
367391
functionsetModuleParent(value){
368-
moduleParentCache.set(this,value);
392+
this[kModuleParent]=value;
369393
}
370394

371395
letdebug=require('internal/util/debuglog').debuglog('module',(fn)=>{
@@ -955,7 +979,7 @@ function getExportsForCircularRequire(module){
955979
constrequiredESM=module[kRequiredModuleSymbol];
956980
if(requiredESM&&requiredESM.getStatus()!==kEvaluated){
957981
letmessage=`Cannot require() ES Module ${module.id} in a cycle.`;
958-
constparent=moduleParentCache.get(module);
982+
constparent=module[kModuleParent];
959983
if(parent){
960984
message+=` (from ${parent.filename})`;
961985
}
@@ -1028,25 +1052,24 @@ Module._load = function(request, parent, isMain){
10281052
constcachedModule=Module._cache[filename];
10291053
if(cachedModule!==undefined){
10301054
updateChildren(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-
returngetExportsForCircularRequire(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-
constimportedCJSMetadata=importedCJSCache.get(cachedModule);
1043-
if(importedCJSMetadata.loading){
1044-
returngetExportsForCircularRequire(cachedModule);
1045-
}
1046-
importedCJSMetadata.loading=true;
1047-
}else{
1055+
if(cachedModule.loaded){
10481056
returncachedModule.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+
returngetExportsForCircularRequire(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+
returngetExportsForCircularRequire(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
}
10511074

10521075
if(BuiltinModule.canBeRequiredWithoutScheme(filename)){
@@ -1190,7 +1213,7 @@ Module._resolveFilename = function(request, parent, isMain, options){
11901213
constrequireStack=[];
11911214
for(letcursor=parent;
11921215
cursor;
1193-
cursor=moduleParentCache.get(cursor)){
1216+
cursor=cursor[kModuleParent]){
11941217
ArrayPrototypePush(requireStack,cursor.filename||cursor.id);
11951218
}
11961219
letmessage=`Cannot find module '${request}'`;
@@ -1268,9 +1291,7 @@ Module.prototype.load = function(filename){
12681291
// Create module entry at load time to snapshot exports correctly
12691292
constexports=this.exports;
12701293
// Preemptively cache for ESM loader.
1271-
if(!cjsExportsCache.has(this)){
1272-
cjsExportsCache.set(this,exports);
1273-
}
1294+
this[kModuleExport]=exports;
12741295
};
12751296

12761297
/**
@@ -1313,7 +1334,7 @@ function loadESMFromCJS(mod, filename){
13131334
constisMain=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
}
13181339

13191340
/**
@@ -1399,7 +1420,7 @@ Module.prototype._compile = function(content, filename, loadAsESM = false){
13991420
// Only modules being require()'d really need to avoid TLA.
14001421
if(loadAsESM){
14011422
// Pass the source into the .mjs extension handler indirectly through the cache.
1402-
requiredESMSourceCache.set(this,content);
1423+
this[kModuleSource]=content;
14031424
loadESMFromCJS(this,filename);
14041425
return;
14051426
}
@@ -1460,15 +1481,15 @@ Module.prototype._compile = function(content, filename, loadAsESM = false){
14601481
* @returns{string}
14611482
*/
14621483
functiongetMaybeCachedSource(mod,filename){
1463-
constcached=importedCJSCache.get(mod);
1484+
// If already analyzed the source, then it will be cached.
14641485
letcontent;
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
}
14731494
returncontent;
14741495
}
@@ -1492,7 +1513,7 @@ Module._extensions['.js'] = function(module, filename){
14921513
}
14931514

14941515
// This is an error path because `require` of a `.js` file in a `"type": "module"` scope is not allowed.
1495-
constparent=moduleParentCache.get(module);
1516+
constparent=module[kModuleParent];
14961517
constparentPath=parent?.filename;
14971518
constpackageJsonPath=path.resolve(pkg.path,'package.json');
14981519
constusesEsm=containsModuleSyntax(content,filename);

‎lib/internal/modules/esm/translators.js‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ const{
4343
stripBOM,
4444
}=require('internal/modules/helpers');
4545
const{
46-
cjsExportsCache,
47-
importedCJSCache,
4846
kIsCachedByESMLoader,
4947
Module: CJSModule,
48+
kModuleSource,
49+
kModuleExport,
50+
kModuleExportNames,
5051
}=require('internal/modules/cjs/loader');
5152
const{ fileURLToPath, pathToFileURL,URL}=require('internal/url');
5253
letdebug=require('internal/util/debuglog').debuglog('esm',(fn)=>{
@@ -285,9 +286,9 @@ function createCJSModuleWrap(url, source, isMain, loadCJS = loadCJSModule){
285286
}
286287

287288
letexports;
288-
if(cjsExportsCache.has(module)){
289-
exports=cjsExportsCache.get(module);
290-
cjsExportsCache.delete(module);
289+
if(module[kModuleExport]!==undefined){
290+
exports=module[kModuleExport];
291+
module[kModuleExport]=undefined;
291292
}else{
292293
({ exports }=module);
293294
}
@@ -366,18 +367,16 @@ translators.set('commonjs', async function commonjsStrategy(url, source,
366367
functioncjsPreparseModuleExports(filename,source){
367368
// TODO: Do we want to keep hitting the user mutable CJS loader here?
368369
letmodule=CJSModule._cache[filename];
369-
if(module){
370-
constcached=importedCJSCache.get(module);
371-
if(cached){
372-
return{ module,exportNames: cached.exportNames};
373-
}
370+
if(module&&module[kModuleExportNames]!==undefined){
371+
return{ module,exportNames: module[kModuleExportNames]};
374372
}
375373
constloaded=Boolean(module);
376374
if(!loaded){
377375
module=newCJSModule(filename);
378376
module.filename=filename;
379377
module.paths=CJSModule._nodeModulePaths(module.path);
380378
module[kIsCachedByESMLoader]=true;
379+
module[kModuleSource]=source;
381380
CJSModule._cache[filename]=module;
382381
}
383382

@@ -392,7 +391,7 @@ function cjsPreparseModuleExports(filename, source){
392391
constexportNames=newSafeSet(newSafeArrayIterator(exports));
393392

394393
// Set first for cycles.
395-
importedCJSCache.set(module,{ source,exportNames});
394+
module[kModuleExportNames]=exportNames;
396395

397396
if(reexports.length){
398397
module.filename=filename;

‎src/env_properties.h‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
V(contextify_context_private_symbol, "node:contextify:context") \
2323
V(decorated_private_symbol, "node:decorated") \
2424
V(host_defined_option_symbol, "node:host_defined_option_symbol") \
25+
V(module_source_private_symbol, "node:module_source") \
26+
V(module_export_names_private_symbol, "node:module_export_names") \
27+
V(module_circular_visited_private_symbol, "node:module_circular_visited") \
28+
V(module_export_private_symbol, "node:module_export") \
29+
V(module_parent_private_symbol, "node:module_parent") \
2530
V(napi_type_tag, "node:napi:type_tag") \
2631
V(napi_wrapper, "node:napi:wrapper") \
2732
V(untransferable_object_private_symbol, "node:untransferableObject") \

0 commit comments

Comments
(0)