webpack 检出图 第 九 节 lib/ChunkGraph.js

模块与 Chunk 的运行时需求处理

  • addModuleRuntimeRequirements

    • 为模块添加运行时依赖项。
    • 支持"所有权转移"机制,以优化性能(避免 Set 拷贝)。
    • 若该 runtime 尚未有依赖,则直接设置;若已存在则合并集合(智能合并较小集合到较大集合中)。
  • addChunkRuntimeRequirements

    • 为 Chunk 添加运行时依赖项。
    • 同样采用了大小判断合并策略,优化内存和性能。
  • addTreeRuntimeRequirements

    • 向 chunk 所在的整个 chunk 树添加运行时需求项。
    • 用于处理跨 chunk 的树状依赖需求。

🔍 运行时需求的读取

  • getModuleRuntimeRequirements

    • 获取模块在指定 runtime 下的运行时需求集合。
    • 若未设置,返回一个共享的空集合 EMPTY_SET
  • getChunkRuntimeRequirements

    • 获取 Chunk 的运行时需求集合。
    • 同样支持空值处理,避免出现 undefined

🧮 模块图哈希计算

  • getModuleGraphHash

    • 计算模块图的哈希(字符串形式)。
    • 可选是否包含模块连接信息。
    • 用于缓存/增量构建中的 hash 校验。
  • getModuleGraphHashBigInt

    • 获取模块图的哈希(BigInt 形式),适合后续数学运算或比较。
    • 提供与 getModuleGraphHash 相同功能,但以 BigInt 表示。
  • _getModuleGraphHashBigInt

    • 实际负责生成模块图的哈希。
    • 使用模块 ID、是否异步、重写源类型和模块导出信息来更新 hash。
    • 最终生成十六进制字符串并转为 BigInt 存储。
js 复制代码
/**
 * 添加模块的运行时需求。
 * @param {Module} module 要添加的模块
 * @param {RuntimeSpec} runtime 运行时环境
 * @param {Set<string>} items 要添加的运行时需求集合(所有权在 transferOwnership 不为 false 时将转移给 ChunkGraph)
 * @param {boolean} transferOwnership 是否转移 items 的所有权(默认为 true)
 */
addModuleRuntimeRequirements(
	module,
	runtime,
	items,
	transferOwnership = true
) {
	const cgm = this._getChunkGraphModule(module); // 获取模块对应的 ChunkGraphModule
	const runtimeRequirementsMap = cgm.runtimeRequirements; // 获取模块的 runtime -> requirements 映射表

	if (runtimeRequirementsMap === undefined) {
		const map = new RuntimeSpecMap(); // 创建新的 RuntimeSpecMap 映射
		// TODO: 避免复制集合,使用所有权转移方式提升性能
		map.set(runtime, transferOwnership ? items : new Set(items)); // 设置 runtime 对应的需求集合
		cgm.runtimeRequirements = map; // 存入 ChunkGraphModule 中
		return;
	}

	// 更新已有 runtimeRequirementsMap 中对应 runtime 的需求集合
	runtimeRequirementsMap.update(runtime, runtimeRequirements => {
		if (runtimeRequirements === undefined) {
			// 当前 runtime 尚无需求,直接设为 items(如果转移所有权)或复制新集合
			return transferOwnership ? items : new Set(items);
		} else if (!transferOwnership || runtimeRequirements.size >= items.size) {
			// 如果不转移所有权,或已有集合更大,合并 items 到已有集合中
			for (const item of items) runtimeRequirements.add(item);
			return runtimeRequirements;
		}

		// 否则将已有集合内容合并进 items 并返回 items(使用新的更大集合替换)
		for (const item of runtimeRequirements) items.add(item);
		return items;
	});
}

/**
 * 添加 chunk 的运行时需求。
 * @param {Chunk} chunk 要添加的 chunk
 * @param {Set<string>} items 所需运行时需求(所有权将转移给 ChunkGraph)
 */
addChunkRuntimeRequirements(chunk, items) {
	const cgc = this._getChunkGraphChunk(chunk); // 获取 Chunk 对应的 ChunkGraphChunk
	const runtimeRequirements = cgc.runtimeRequirements; // 获取当前 chunk 的需求集合

	if (runtimeRequirements === undefined) {
		cgc.runtimeRequirements = items; // 若不存在,直接赋值
	} else if (runtimeRequirements.size >= items.size) {
		// 如果已有集合较大,将 items 添加进已有集合
		for (const item of items) runtimeRequirements.add(item);
	} else {
		// 否则合并已有集合到 items,并替换引用
		for (const item of runtimeRequirements) items.add(item);
		cgc.runtimeRequirements = items;
	}
}

/**
 * 添加 chunk 树级别的运行时需求。
 * @param {Chunk} chunk 当前 chunk
 * @param {Iterable<string>} items 要添加的需求项
 */
addTreeRuntimeRequirements(chunk, items) {
	const cgc = this._getChunkGraphChunk(chunk); // 获取 Chunk 对应的 ChunkGraphChunk
	const runtimeRequirements = cgc.runtimeRequirementsInTree; // 获取该 chunk 所在的 chunk tree 的运行时需求集合
	for (const item of items) runtimeRequirements.add(item); // 逐个添加
}

/**
 * 获取模块在某个 runtime 下的运行时需求。
 * @param {Module} module 模块
 * @param {RuntimeSpec} runtime 对应的运行时
 * @returns {ReadOnlyRuntimeRequirements} 返回运行时需求集合,若无则返回空集合
 */
getModuleRuntimeRequirements(module, runtime) {
	const cgm = this._getChunkGraphModule(module); // 获取 ChunkGraphModule 实例
	const runtimeRequirements =
		cgm.runtimeRequirements && cgm.runtimeRequirements.get(runtime); // 获取指定 runtime 的需求集合
	return runtimeRequirements === undefined ? EMPTY_SET : runtimeRequirements; // 若不存在则返回空集合
}

/**
 * 获取 chunk 的运行时需求集合。
 * @param {Chunk} chunk chunk 实例
 * @returns {ReadOnlyRuntimeRequirements} 只读运行时需求集合
 */
getChunkRuntimeRequirements(chunk) {
	const cgc = this._getChunkGraphChunk(chunk); // 获取 ChunkGraphChunk 实例
	const runtimeRequirements = cgc.runtimeRequirements; // 获取需求集合
	return runtimeRequirements === undefined ? EMPTY_SET : runtimeRequirements;
}

/**
 * 获取模块在指定 runtime 下的模块图 hash(字符串形式)。
 * @param {Module} module 模块
 * @param {RuntimeSpec} runtime 运行时
 * @param {boolean} withConnections 是否包含连接信息
 * @returns {string} 返回模块图 hash 的十六进制字符串
 */
getModuleGraphHash(module, runtime, withConnections = true) {
	const cgm = this._getChunkGraphModule(module); // 获取模块对应的 ChunkGraphModule
	return withConnections
		? this._getModuleGraphHashWithConnections(cgm, module, runtime) // 包含连接信息的 hash
		: this._getModuleGraphHashBigInt(cgm, module, runtime).toString(16); // 否则返回 bigint 的十六进制字符串
}

/**
 * 获取模块在指定 runtime 下的模块图 hash(BigInt 形式)。
 * @param {Module} module 模块
 * @param {RuntimeSpec} runtime 运行时
 * @param {boolean} withConnections 是否包含连接信息
 * @returns {bigint} 模块图的 hash 值(BigInt 格式)
 */
getModuleGraphHashBigInt(module, runtime, withConnections = true) {
	const cgm = this._getChunkGraphModule(module); // 获取模块对应的 ChunkGraphModule
	return withConnections
		? BigInt(
				`0x${this._getModuleGraphHashWithConnections(cgm, module, runtime)}`
			) // 若包含连接,先用 string 表示再转 BigInt
		: this._getModuleGraphHashBigInt(cgm, module, runtime); // 否则调用内部实现直接获取 BigInt
}

/**
 * 内部方法:真正计算模块的模块图 hash(BigInt 形式)。
 * @param {ChunkGraphModule} cgm ChunkGraphModule 实例
 * @param {Module} module 模块
 * @param {RuntimeSpec} runtime 运行时
 * @returns {bigint} 返回计算出的 hash 值
 */
_getModuleGraphHashBigInt(cgm, module, runtime) {
	if (cgm.graphHashes === undefined) {
		cgm.graphHashes = new RuntimeSpecMap(); // 初始化 graphHashes 映射
	}
	const graphHash = cgm.graphHashes.provide(runtime, () => {
		const hash = createHash(this._hashFunction); // 创建 hash 实例(可能是 md4、sha256 等)
		hash.update(`${cgm.id}${this.moduleGraph.isAsync(module)}`); // 将模块 ID 和异步标识加入 hash 中

		const sourceTypes = this._getOverwrittenModuleSourceTypes(module); // 获取该模块的重写源类型
		if (sourceTypes !== undefined) {
			for (const type of sourceTypes) hash.update(type); // 将每个 source type 更新进 hash
		}

		this.moduleGraph.getExportsInfo(module).updateHash(hash, runtime); // 将导出信息更新到 hash 中

		return BigInt(`0x${/** @type {string} */ (hash.digest("hex"))}`); // 最终输出为十六进制 hash 字符串转为 BigInt
	});
	return graphHash;
}
相关推荐
小薛博客18 分钟前
3、整合前端基础交互页面
java·前端·ai·交互
@蓝莓果粒茶22 分钟前
LeetCode第158题_用Read4读取N个字符 II
前端·c++·python·算法·leetcode·职场和发展·c#
天天扭码24 分钟前
【硬核教程】从入门到入土!彻底吃透 JavaScript 中 this 关键字这一篇就够了
前端·javascript·面试
Mintopia1 小时前
计算机图形学学习指南
前端·javascript·计算机图形学
Mintopia1 小时前
three.js 中的动画(animation)
前端·javascript·three.js
AI大模型顾潇1 小时前
[特殊字符] Prompt如何驱动大模型对本地文件实现自主变更:Cline技术深度解析
前端·人工智能·llm·微调·prompt·编程·ai大模型
小小小小宇1 小时前
React中 useEffect和useLayoutEffect源码原理
前端
AlexJee1 小时前
在vue3中使用vue-cropper完成头像裁剪上传图片功能
前端
清晨細雨1 小时前
uniapp微信小程序:WIFI设备配网之TCP/UDP开发AP配网
前端·物联网·小程序·uni-app
阿廖沙10241 小时前
Rust核心概念
前端