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

127 阅读5分钟
  • getChunkConditionMap(chunk, filterFn)

    • 遍历 chunk 所引用的所有 chunk,应用过滤函数并返回 {chunkId: 是否通过} 映射。
  • hasModuleInGraph(chunk, filterFn, filterChunkFn)

    • 广度优先遍历 chunk 所在 chunkGroup 及其子组,查找是否存在符合模块过滤条件的模块。
  • compareChunks(chunkA, chunkB)

    • 用于排序比较两个 chunk:

      • 模块数量多的优先;
      • 如果数量相同,按模块标识符排序后比较模块顺序。
  • getChunkModulesSize(chunk)

    • 返回 chunk 中所有模块的大小总和(使用缓存获取)。
  • getChunkModulesSizes(chunk)

    • 返回 chunk 中所有模块按不同源类型(sourceType)分类的大小。
  • getChunkRootModules(chunk)

    • 返回 chunk 中的所有根模块(没有被依赖的模块),并按标识符排序。
  • getChunkSize(chunk, options)

    • 返回 chunk 的总估算大小,包含:

      • 模块大小;
      • 固定开销;
      • 如果是初始 chunk,还会乘以一个倍数(默认 10 倍)。
  • getIntegratedChunksSize(chunkA, chunkB, options)

    • 返回将两个 chunk 合并后的总大小:

      • 所有模块去重后求大小;
      • 如果有初始 chunk,乘倍数。
  • canChunksBeIntegrated(chunkA, chunkB)

    • 判断两个 chunk 是否可以合并,合并条件包括:

      • 两者都没有设置禁止合并;
      • 两者 runtime 情况一致,或者其中一个可复用另一个;
      • 都不是入口 chunk。
/**
 * 获取 chunk 所引用的所有 chunk 的条件映射
 * @param {Chunk} chunk - 起始 chunk
 * @param {ChunkFilterPredicate} filterFn - 用于判断 chunk 是否符合条件的函数
 * @returns {Record<string|number, boolean>} - 以 chunk id 为键、是否符合条件为值的映射对象
 */
getChunkConditionMap(chunk, filterFn) {
	const map = Object.create(null); // 创建一个无原型的纯净对象作为映射容器
	for (const c of chunk.getAllReferencedChunks()) { // 遍历 chunk 所有引用的 chunk(包括自身和间接引用)
		map[/** @type {ChunkId} */ (c.id)] = filterFn(c, this); // 对每个 chunk 执行过滤函数,结果存入映射表中
	}
	return map; // 返回构建的映射对象
}

/**
 * 判断是否在 chunk 所属的 chunk graph 中存在符合条件的模块
 * @param {Chunk} chunk - 起始 chunk
 * @param {ModuleFilterPredicate} filterFn - 模块筛选函数
 * @param {ChunkFilterPredicate=} filterChunkFn - 可选,chunk 筛选函数
 * @returns {boolean} - 如果图中存在符合条件的模块,则返回 true
 */
hasModuleInGraph(chunk, filterFn, filterChunkFn) {
	const queue = new Set(chunk.groupsIterable); // 初始化遍历队列,包含 chunk 所属的所有 chunkGroup
	const chunksProcessed = new Set(); // 用于记录已经处理过的 chunk,避免重复

	for (const chunkGroup of queue) { // 遍历 chunkGroup 队列
		for (const innerChunk of chunkGroup.chunks) { // 遍历每个 chunkGroup 中的 chunk
			if (!chunksProcessed.has(innerChunk)) { // 若该 chunk 未处理过
				chunksProcessed.add(innerChunk); // 标记为已处理
				if (!filterChunkFn || filterChunkFn(innerChunk, this)) { // 如果没有过滤函数或 chunk 通过过滤
					for (const module of this.getChunkModulesIterable(innerChunk)) { // 遍历该 chunk 中所有模块
						if (filterFn(module)) { // 若模块符合条件
							return true; // 返回 true,表示存在该模块
						}
					}
				}
			}
		}
		for (const child of chunkGroup.childrenIterable) { // 将 chunkGroup 的子 group 加入队列递归处理
			queue.add(child);
		}
	}
	return false; // 遍历完成未找到符合条件模块,返回 false
}

/**
 * 比较两个 chunk 的模块数量与内容,用于排序
 * @param {Chunk} chunkA - 第一个 chunk
 * @param {Chunk} chunkB - 第二个 chunk
 * @returns {-1|0|1} - 排序值:chunkA 应排在前返回 -1,排在后返回 1,相等返回 0
 */
compareChunks(chunkA, chunkB) {
	const cgcA = this._getChunkGraphChunk(chunkA); // 获取 chunkA 的内部图数据
	const cgcB = this._getChunkGraphChunk(chunkB); // 获取 chunkB 的内部图数据
	if (cgcA.modules.size > cgcB.modules.size) return -1; // 模块数多的优先
	if (cgcA.modules.size < cgcB.modules.size) return 1;
	cgcA.modules.sortWith(compareModulesByIdentifier); // 若数量相同,则按标识符排序
	cgcB.modules.sortWith(compareModulesByIdentifier);
	return compareModuleIterables(cgcA.modules, cgcB.modules); // 逐项比较模块顺序决定优先级
}

/**
 * 获取 chunk 所有模块的总大小
 * @param {Chunk} chunk - 目标 chunk
 * @returns {number} - 模块大小总和
 */
getChunkModulesSize(chunk) {
	const cgc = this._getChunkGraphChunk(chunk); // 获取 chunk 的图结构数据
	return cgc.modules.getFromUnorderedCache(getModulesSize); // 使用缓存获取模块总大小(避免重复计算)
}

/**
 * 获取 chunk 中所有模块按 sourceType 分类后的大小
 * @param {Chunk} chunk - 目标 chunk
 * @returns {Record<string, number>} - 各 sourceType 的大小映射
 */
getChunkModulesSizes(chunk) {
	const cgc = this._getChunkGraphChunk(chunk); // 获取图数据
	return cgc.modules.getFromUnorderedCache(getModulesSizes); // 从缓存中获取每个源类型的模块大小
}

/**
 * 获取 chunk 中的根模块(没有任何入边依赖的模块)
 * @param {Chunk} chunk - 目标 chunk
 * @returns {Module[]} - 按标识符排序的根模块数组
 */
getChunkRootModules(chunk) {
	const cgc = this._getChunkGraphChunk(chunk); // 获取 chunk 的图结构
	return cgc.modules.getFromUnorderedCache(this._getGraphRoots); // 使用缓存和内部方法获取根模块列表
}

/**
 * 获取 chunk 的综合大小(模块大小 + 开销 + 条件倍增因子)
 * @param {Chunk} chunk - 目标 chunk
 * @param {ChunkSizeOptions} options - 配置选项,包含开销和乘数
 * @returns {number} - chunk 的估算大小
 */
getChunkSize(chunk, options = {}) {
	const cgc = this._getChunkGraphChunk(chunk); // 获取 chunk 图数据
	const modulesSize = cgc.modules.getFromUnorderedCache(getModulesSize); // 获取模块总大小
	const chunkOverhead =
		typeof options.chunkOverhead === "number" ? options.chunkOverhead : 10000; // chunk 本身的开销,默认 10000
	const entryChunkMultiplicator =
		typeof options.entryChunkMultiplicator === "number"
			? options.entryChunkMultiplicator
			: 10; // 初始 chunk 的大小乘数,默认 10

	return (
		chunkOverhead +
		modulesSize * (chunk.canBeInitial() ? entryChunkMultiplicator : 1) // 如果 chunk 是初始 chunk,则乘以乘数
	);
}

/**
 * 获取两个 chunk 合并后的总大小
 * @param {Chunk} chunkA - 第一个 chunk
 * @param {Chunk} chunkB - 第二个 chunk
 * @param {ChunkSizeOptions} options - 配置项(开销、乘数)
 * @returns {number} - 合并后总大小
 */
getIntegratedChunksSize(chunkA, chunkB, options = {}) {
	const cgcA = this._getChunkGraphChunk(chunkA); // 获取 chunkA 图数据
	const cgcB = this._getChunkGraphChunk(chunkB); // 获取 chunkB 图数据
	const allModules = new Set(cgcA.modules); // 创建 Set 保存合并模块
	for (const m of cgcB.modules) allModules.add(m); // 添加 chunkB 的模块到合并集合中

	const modulesSize = getModulesSize(allModules); // 获取合并模块的总大小
	const chunkOverhead =
		typeof options.chunkOverhead === "number" ? options.chunkOverhead : 10000;
	const entryChunkMultiplicator =
		typeof options.entryChunkMultiplicator === "number"
			? options.entryChunkMultiplicator
			: 10;

	return (
		chunkOverhead +
		modulesSize *
			(chunkA.canBeInitial() || chunkB.canBeInitial() ? entryChunkMultiplicator : 1)
	); // 如果任一 chunk 是初始 chunk,则大小乘倍数
}

/**
 * 判断两个 chunk 是否可以被合并
 * @param {Chunk} chunkA - 第一个 chunk
 * @param {Chunk} chunkB - 第二个 chunk
 * @returns {boolean} - 返回 true 表示可以合并
 */
canChunksBeIntegrated(chunkA, chunkB) {
	if (chunkA.preventIntegration || chunkB.preventIntegration) {
		// 显式禁止合并的 chunk 不允许整合
		return false;
	}

	const hasRuntimeA = chunkA.hasRuntime(); // 是否拥有 runtime
	const hasRuntimeB = chunkB.hasRuntime();

	if (hasRuntimeA !== hasRuntimeB) {
		// 如果 runtime 存在不一致,只有 runtime 可被另一个复用时才能合并
		if (hasRuntimeA) {
			return isAvailableChunk(chunkA, chunkB);
		} else if (hasRuntimeB) {
			return isAvailableChunk(chunkB, chunkA);
		}
		return false;
	}

	if (
		this.getNumberOfEntryModules(chunkA) > 0 ||
		this.getNumberOfEntryModules(chunkB) > 0
	) {
		// 若任意一个 chunk 是入口 chunk,则不允许合并
		return false;
	}

	return true; // 上述条件都满足,允许合并
}