◐ Shell
clean mode source ↗

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');

65736674

const { 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;

75977698

const kIsMainSymbol = Symbol('kIsMainSymbol');

7799

const kIsCachedByESMLoader = Symbol('kIsCachedByESMLoader');

78100

const kRequiredModuleSymbol = Symbol('kRequiredModuleSymbol');

79101

const kIsExecuting = 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

}

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) {

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);

355377356378

/**

357379

* Get the parent of the current module from our cache.

380+

* @this {Module}

358381

*/

359382

function 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

*/

367391

function setModuleParent(value) {

368-

moduleParentCache.set(this, value);

392+

this[kModuleParent] = value;

369393

}

370394371395

let debug = require('internal/util/debuglog').debuglog('module', (fn) => {

@@ -955,7 +979,7 @@ function getExportsForCircularRequire(module) {

955979

const requiredESM = module[kRequiredModuleSymbol];

956980

if (requiredESM && requiredESM.getStatus() !== kEvaluated) {

957981

let message = `Cannot require() ES Module ${module.id} in a cycle.`;

958-

const parent = moduleParentCache.get(module);

982+

const parent = module[kModuleParent];

959983

if (parent) {

960984

message += ` (from ${parent.filename})`;

961985

}

@@ -1028,25 +1052,24 @@ Module._load = function(request, parent, isMain) {

10281052

const cachedModule = 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-

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) {

10481056

return 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

}

1051107410521075

if (BuiltinModule.canBeRequiredWithoutScheme(filename)) {

@@ -1190,7 +1213,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {

11901213

const requireStack = [];

11911214

for (let cursor = parent;

11921215

cursor;

1193-

cursor = moduleParentCache.get(cursor)) {

1216+

cursor = cursor[kModuleParent]) {

11941217

ArrayPrototypePush(requireStack, cursor.filename || cursor.id);

11951218

}

11961219

let 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

12691292

const 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) {

13131334

const 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.

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

function getMaybeCachedSource(mod, filename) {

1463-

const cached = importedCJSCache.get(mod);

1484+

// If already analyzed the source, then it will be cached.

14641485

let 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

}

14731494

return 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];

14961517

const parentPath = parent?.filename;

14971518

const packageJsonPath = path.resolve(pkg.path, 'package.json');

14981519

const usesEsm = containsModuleSyntax(content, filename);