✅ _getModuleGraphHashWithConnections(cgm, module, runtime)
-
作用 :
生成指定模块在某个 runtime 下,连同其"连接关系"后的唯一图结构哈希。
-
输入参数:
cgm
:该模块在 ChunkGraph 中的封装对象(ChunkGraphModule
)。module
:待处理的模块。runtime
:当前的 runtime,可为字符串或对象。
-
主要逻辑:
-
初始化缓存结构
graphHashesWithConnections
,基于 runtime 进行缓存。 -
生成基础图哈希(不包含连接信息)。
-
遍历模块的所有出边连接(Outgoing Connections):
- 根据连接的激活状态(true / false / TRANSITIVE_ONLY)和模块的导出类型,生成唯一的
stateInfo
字符串。 - 按照
stateInfo
分类收集连接模块到connectedModules
Map。 - 特殊处理命名空间导出模块(
Tnamespace
),加入activeNamespaceModules
。
- 根据连接的激活状态(true / false / TRANSITIVE_ONLY)和模块的导出类型,生成唯一的
-
如果是多 runtime,则分别遍历各个 runtime 合成状态信息。
-
如果没有有效连接,直接返回基本图哈希。
-
对
connectedModules
做排序,保证哈希的一致性。 -
创建哈希对象并依次加入:
- 命名空间模块的哈希(1个直接加,多个做异或合并)。
- 每组连接模块的哈希(按
stateInfo
和模块内容)。 - 原始模块的基本图哈希。
-
返回最终的十六进制哈希字符串。
-
✅ getTreeRuntimeRequirements(chunk)
-
作用 :
获取某个 chunk 所属的模块树(包含 chunk 的依赖模块)所需的 runtime requirements。
-
输入参数:
chunk
:目标 chunk 对象。
-
主要逻辑:
- 通过
_getChunkGraphChunk(chunk)
获取封装的ChunkGraphChunk
。 - 读取并返回其
runtimeRequirementsInTree
属性。
- 通过
-
用途:
- 判断生成的代码中是否需要引入某些 runtime 工具函数。
- 用于生成 runtime 模块或注入 runtime helpers。
js
/**
* 获取某个模块在指定 runtime 下,连同其连接信息后的图哈希值。
* @param {ChunkGraphModule} cgm 当前模块在 ChunkGraph 中的表示
* @param {Module} module 当前处理的模块
* @param {RuntimeSpec} runtime 当前的 runtime 规格(可以是字符串或对象)
* @returns {string} 哈希值
*/
_getModuleGraphHashWithConnections(cgm, module, runtime) {
// 初始化用于缓存哈希值的 Map(按 runtime 区分)
if (cgm.graphHashesWithConnections === undefined) {
cgm.graphHashesWithConnections = new RuntimeSpecMap();
}
/**
* 将模块连接的状态转换为字符形式。
* @param {ConnectionState} state 连接状态
* @returns {"F" | "T" | "O"} 字符化的状态
*/
const activeStateToString = state => {
if (state === false) return "F"; // 不活跃
if (state === true) return "T"; // 活跃
if (state === ModuleGraphConnection.TRANSITIVE_ONLY) return "O"; // 仅传递连接
throw new Error("Not implemented active state"); // 未知状态抛错
};
// 判断该模块是否是严格的 Harmony 模块
const strict = module.buildMeta && module.buildMeta.strictHarmonyModule;
// 提供 runtime 缓存处理逻辑
return cgm.graphHashesWithConnections.provide(runtime, () => {
// 获取当前模块图的基本哈希(BigInt → 十六进制字符串)
const graphHash = this._getModuleGraphHashBigInt(
cgm,
module,
runtime
).toString(16);
// 获取当前模块的所有出边连接
const connections = this.moduleGraph.getOutgoingConnections(module);
/** 用于记录活跃 namespace 模块 */
const activeNamespaceModules = new Set();
/** 记录连接模块的信息,key 是 stateInfo,value 是模块或模块集合 */
const connectedModules = new Map();
/**
* 处理一个连接,依据状态标识将其存入 map 中
* @param {ModuleGraphConnection} connection 连接对象
* @param {string} stateInfo 编码后的连接状态
*/
const processConnection = (connection, stateInfo) => {
const module = connection.module;
// 拼接上导出类型信息
stateInfo += module.getExportsType(this.moduleGraph, strict);
// 如果是命名空间模块,放入集合中
if (stateInfo === "Tnamespace") activeNamespaceModules.add(module);
else {
const oldModule = connectedModules.get(stateInfo);
if (oldModule === undefined) {
// 首次出现,直接存入
connectedModules.set(stateInfo, module);
} else if (oldModule instanceof Set) {
// 如果已有 Set,加入 Set
oldModule.add(module);
} else if (oldModule !== module) {
// 如果已有的不是当前模块,则变成 Set
connectedModules.set(stateInfo, new Set([oldModule, module]));
}
}
};
// 如果 runtime 是 undefined 或字符串(单一 runtime)
if (runtime === undefined || typeof runtime === "string") {
for (const connection of connections) {
const state = connection.getActiveState(runtime);
if (state === false) continue; // 忽略不活跃连接
processConnection(connection, state === true ? "T" : "O");
}
} else {
// 多 runtime 情况下,对每个 runtime 单独处理连接状态
for (const connection of connections) {
const states = new Set();
let stateInfo = "";
forEachRuntime(
runtime,
runtime => {
const state = connection.getActiveState(runtime);
states.add(state);
stateInfo += activeStateToString(state) + runtime;
},
true
);
// 如果所有 runtime 的状态一致,可以简化为单状态
if (states.size === 1) {
const state = first(states);
if (state === false) continue;
stateInfo = activeStateToString(state);
}
processConnection(connection, stateInfo);
}
}
// 如果没有任何连接模块,也没有命名空间模块,直接返回基本哈希
if (activeNamespaceModules.size === 0 && connectedModules.size === 0)
return graphHash;
// 如果连接模块多于 1 个,则对 key 排序,确保哈希顺序一致性
const connectedModulesInOrder =
connectedModules.size > 1
? Array.from(connectedModules).sort(([a], [b]) => (a < b ? -1 : 1))
: connectedModules;
// 创建哈希对象
const hash = createHash(this._hashFunction);
/**
* 将某个模块的哈希值写入 hash
* @param {Module} module 模块
*/
const addModuleToHash = module => {
hash.update(
this._getModuleGraphHashBigInt(
this._getChunkGraphModule(module),
module,
runtime
).toString(16)
);
};
/**
* 将一组模块的哈希值进行异或后写入 hash
* @param {Set<Module>} modules 模块集合
*/
const addModulesToHash = modules => {
let xor = ZERO_BIG_INT;
for (const m of modules) {
xor =
xor ^
this._getModuleGraphHashBigInt(
this._getChunkGraphModule(m),
m,
runtime
);
}
hash.update(xor.toString(16));
};
// 添加命名空间模块的哈希(单个直接加,多个异或加)
if (activeNamespaceModules.size === 1)
addModuleToHash(
/** @type {Module} */ (activeNamespaceModules.values().next().value)
);
else if (activeNamespaceModules.size > 1)
addModulesToHash(activeNamespaceModules);
// 添加连接模块的哈希
for (const [stateInfo, modules] of connectedModulesInOrder) {
hash.update(stateInfo);
if (modules instanceof Set) {
addModulesToHash(modules);
} else {
addModuleToHash(modules);
}
}
// 最后拼上原始模块的图哈希
hash.update(graphHash);
// 输出最终哈希结果
return /** @type {string} */ (hash.digest("hex"));
});
}
/**
* 获取一个 chunk 及其依赖模块树的 runtime 需求(runtime requirements)。
* 这些 runtime requirements 通常是为确保模块加载与执行所需的运行时代码(如 __webpack_require__, chunk loading 代码等)。
*
* @param {Chunk} chunk 要处理的 chunk 实例
* @returns {ReadOnlyRuntimeRequirements} 该 chunk 的只读 runtime 需求集合
*/
getTreeRuntimeRequirements(chunk) {
// 获取 ChunkGraphChunk 实例(这是 Chunk 在 ChunkGraph 中的封装,保存额外信息)
const cgc = this._getChunkGraphChunk(chunk);
// 返回该 chunk 所在模块树的 runtime 需求集合
return cgc.runtimeRequirementsInTree;
}