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

137 阅读4分钟

🔄 integrateChunks(chunkA, chunkB)

  • 合并两个 chunk 的内容,保留 chunkA,清除 chunkB。

  • 决定合并后 chunk 的名称(基于 entry 和名称长度)。

  • 合并:

    • idNameHints
    • runtime(使用 mergeRuntime
    • 模块:从 chunkB 断开并连接到 chunkA
    • 入口模块及其所属 chunk group
    • 所有 chunkGroup 中的 chunkB 替换为 chunkA
  • 最后清除 chunkB 的所有图关系。


🔧 upgradeDependentToFullHashModules(chunk)

  • 将 chunk 中的 dependentHashModules 升级为 fullHashModules
  • 如果 fullHashModules 已存在,则进行集合合并。

🔍 isEntryModuleInChunk(module, chunk)

  • 判断某个模块是否为 chunk 的入口模块。
  • 通过 chunk.entryModules.has(module) 直接判断。

connectChunkAndEntryModule(chunk, module, entrypoint)

  • 建立 chunk 与入口模块的关系。
  • 记录模块在哪些 chunk 中是 entry(entryInChunks)。
  • 将模块及其入口点组记录到 chunk 的 entryModules

connectChunkAndRuntimeModule(chunk, module)

  • 建立 chunk 与 runtime 模块的关系。
  • 添加至模块的 runtimeInChunks 和 chunk 的 runtimeModules

addFullHashModuleToChunk(chunk, module)

  • 向 chunk 添加一个 full hash 的 runtime 模块。
  • 用于标记该模块在计算 chunk hash 时需要完整 hash。

addDependentHashModuleToChunk(chunk, module)

  • 向 chunk 添加一个 dependent hash 的 runtime 模块。
  • 模块依赖于其它模块的 hash,而不需要完整 chunk 的 hash。

disconnectChunkAndEntryModule(chunk, module)

  • 断开 chunk 与某个入口模块的连接。
  • 从模块的 entryInChunks 中移除该 chunk。
  • 若集合为空,清空引用。
  • 同时从 chunk 的 entryModules 中移除该模块。

disconnectChunkAndRuntimeModule(chunk, module)

  • 断开 chunk 与某个 runtime 模块的连接。
  • 从模块的 runtimeInChunks 中移除该 chunk。
  • 若集合为空,清空引用。
  • 同时从 chunk 的 runtimeModules 中移除该模块。
/**
 * 将 chunkB 的内容合并到 chunkA 中(包括模块、运行时代码、入口信息、group 等)
 * @param {Chunk} chunkA 要合并到的目标 chunk
 * @param {Chunk} chunkB 被合并的 chunk
 */
integrateChunks(chunkA, chunkB) {
	// 决定合并后 chunk 的名称(确保具有确定性)
	if (chunkA.name && chunkB.name) {
		if (
			this.getNumberOfEntryModules(chunkA) > 0 ===
			this.getNumberOfEntryModules(chunkB) > 0
		) {
			// 如果两个 chunk 都有(或都没有)入口模块,则选用较短的名称
			if (chunkA.name.length !== chunkB.name.length) {
				chunkA.name =
					chunkA.name.length < chunkB.name.length ? chunkA.name : chunkB.name;
			} else {
				// 若长度一致,则选择字典序更小的名称
				chunkA.name = chunkA.name < chunkB.name ? chunkA.name : chunkB.name;
			}
		} else if (this.getNumberOfEntryModules(chunkB) > 0) {
			// 如果 chunkB 有 entry 而 chunkA 没有,使用 chunkB 的名称
			chunkA.name = chunkB.name;
		}
	} else if (chunkB.name) {
		// 若 chunkA 无名但 chunkB 有名,则直接采用 chunkB 的名称
		chunkA.name = chunkB.name;
	}

	// 合并 chunkB 的名称提示(用于生成 chunk 名)
	for (const hint of chunkB.idNameHints) {
		chunkA.idNameHints.add(hint);
	}

	// 合并 runtime(如 runtime 名称或集合)
	chunkA.runtime = mergeRuntime(chunkA.runtime, chunkB.runtime);

	// 迁移 chunkB 中的模块到 chunkA
	for (const module of this.getChunkModules(chunkB)) {
		this.disconnectChunkAndModule(chunkB, module); // 从 chunkB 中断开模块
		this.connectChunkAndModule(chunkA, module);    // 将模块连接到 chunkA
	}

	// 迁移 chunkB 中的入口模块及其 chunkGroup
	for (const [module, chunkGroup] of Array.from(
		this.getChunkEntryModulesWithChunkGroupIterable(chunkB)
	)) {
		this.disconnectChunkAndEntryModule(chunkB, module); // 断开 chunkB 的 entry
		this.connectChunkAndEntryModule(
			chunkA,
			module,
			/** @type {Entrypoint} */ (chunkGroup)
		); // 将 entry 接入 chunkA
	}

	// 将 chunkB 所在的 chunkGroup 中替换为 chunkA
	for (const chunkGroup of chunkB.groupsIterable) {
		chunkGroup.replaceChunk(chunkB, chunkA);
		chunkA.addGroup(chunkGroup);       // 加入 chunkA 的 group 引用
		chunkB.removeGroup(chunkGroup);    // 移除 chunkB 的 group 引用
	}

	// 清理 chunkB 的所有 chunk graph 关系
	ChunkGraph.clearChunkGraphForChunk(chunkB);
}

/**
 * 将 chunk 中依赖 hash 的模块提升为完全依赖 full hash 的模块
 * @param {Chunk} chunk 要升级的 chunk
 */
upgradeDependentToFullHashModules(chunk) {
	const cgc = this._getChunkGraphChunk(chunk); // 获取 chunk 的 graph 节点
	if (cgc.dependentHashModules === undefined) return; // 没有 dependent 模块则返回
	if (cgc.fullHashModules === undefined) {
		// 若 fullHashModules 尚未定义,直接赋值
		cgc.fullHashModules = cgc.dependentHashModules;
	} else {
		// 否则合并集合
		for (const m of cgc.dependentHashModules) {
			cgc.fullHashModules.add(m);
		}
		cgc.dependentHashModules = undefined; // 清空 dependent 模块集合
	}
}

/**
 * 判断模块是否为某个 chunk 的入口模块
 * @param {Module} module 检查的模块
 * @param {Chunk} chunk 检查的 chunk
 * @returns {boolean} 是否为入口模块
 */
isEntryModuleInChunk(module, chunk) {
	const cgc = this._getChunkGraphChunk(chunk);
	return cgc.entryModules.has(module); // 直接查表
}

/**
 * 连接 chunk 与其入口模块,并指定对应的 chunkGroup(entrypoint)
 * @param {Chunk} chunk 要连接的 chunk
 * @param {Module} module 入口模块
 * @param {Entrypoint} entrypoint 所属入口点组
 */
connectChunkAndEntryModule(chunk, module, entrypoint) {
	const cgm = this._getChunkGraphModule(module);
	const cgc = this._getChunkGraphChunk(chunk);
	if (cgm.entryInChunks === undefined) {
		cgm.entryInChunks = new Set(); // 初始化 entryInChunks
	}
	cgm.entryInChunks.add(chunk);           // 记录该模块在哪些 chunk 作为入口
	cgc.entryModules.set(module, entrypoint); // 在 chunk 中标记该模块及其所属入口组
}

/**
 * 连接 chunk 与 runtime 模块
 * @param {Chunk} chunk 目标 chunk
 * @param {RuntimeModule} module runtime 模块
 */
connectChunkAndRuntimeModule(chunk, module) {
	const cgm = this._getChunkGraphModule(module);
	const cgc = this._getChunkGraphChunk(chunk);
	if (cgm.runtimeInChunks === undefined) {
		cgm.runtimeInChunks = new Set(); // 初始化 runtimeInChunks
	}
	cgm.runtimeInChunks.add(chunk);      // 模块关联到 chunk
	cgc.runtimeModules.add(module);      // chunk 关联到模块
}

/**
 * 添加需要完整 hash 的 runtime 模块
 * @param {Chunk} chunk 所在 chunk
 * @param {RuntimeModule} module 模块
 */
addFullHashModuleToChunk(chunk, module) {
	const cgc = this._getChunkGraphChunk(chunk);
	if (cgc.fullHashModules === undefined) cgc.fullHashModules = new Set();
	cgc.fullHashModules.add(module); // 添加到 fullHashModules 集合
}

/**
 * 添加仅依赖 hash 的 runtime 模块(非 full hash)
 * @param {Chunk} chunk 所在 chunk
 * @param {RuntimeModule} module 模块
 */
addDependentHashModuleToChunk(chunk, module) {
	const cgc = this._getChunkGraphChunk(chunk);
	if (cgc.dependentHashModules === undefined)
		cgc.dependentHashModules = new Set();
	cgc.dependentHashModules.add(module); // 添加到 dependent 集合
}

/**
 * 断开 chunk 与其入口模块的关系
 * @param {Chunk} chunk 要断开的 chunk
 * @param {Module} module 要断开的模块
 */
disconnectChunkAndEntryModule(chunk, module) {
	const cgm = this._getChunkGraphModule(module);
	const cgc = this._getChunkGraphChunk(chunk);
	(cgm.entryInChunks).delete(chunk); // 从模块的 entryInChunks 中移除
	if ((cgm.entryInChunks).size === 0) {
		cgm.entryInChunks = undefined; // 若已无引用,则置为 undefined
	}
	cgc.entryModules.delete(module); // 从 chunk 的 entryModules 中移除
}

/**
 * 断开 chunk 与其 runtime 模块的关系
 * @param {Chunk} chunk 目标 chunk
 * @param {RuntimeModule} module runtime 模块
 */
disconnectChunkAndRuntimeModule(chunk, module) {
	const cgm = this._getChunkGraphModule(module);
	const cgc = this._getChunkGraphChunk(chunk);
	(cgm.runtimeInChunks).delete(chunk); // 模块中移除 chunk 引用
	if ((cgm.runtimeInChunks).size === 0) {
		cgm.runtimeInChunks = undefined; // 无引用则置空
	}
	cgc.runtimeModules.delete(module); // chunk 中移除该模块
}