webpack 核心编译器 十三 节

这个 createHash() 方法可以总结为 Webpack 打包过程中的核心方法之一,它负责为模块、chunk、以及整个 compilation 生成内容相关的 hash,确保构建产物的缓存有效性和一致性。

  • 初始化主 Hash 对象

    • 使用配置中的 hashFunction(如 md4, sha256 等)
    • 如果有设置 hashSalt,先将其加入 hash 中
  • 加入子 Compilation / 警告 / 错误的 hash

    • 确保这些信息变动也能影响最终 hash
  • 排序 Chunks

    • 将所有 chunk 分为:

      • runtimeChunks:含 runtime 的入口 chunk
      • otherChunks:普通 chunk
    • 保证 hash 生成的顺序稳定

  • 处理 Runtime Chunk 的依赖关系

    • 构建引用图
    • 解决 runtime chunk 的依赖顺序(甚至可能检测出循环引用)
  • 遍历每个 chunk,生成模块和 chunk 的 hash

    • 如果模块 hash 不存在,则生成
    • 对 chunk 本身调用 updateHash() 计算 chunk hash
    • 判断是否为 fullHash 模块(依赖整个 compilation 的 hash)
  • 处理 fullHash 模块

    • 这些模块必须在 fullHash 生成后才能计算 hash
    • 所以最后重新计算模块 hash,并重新生成所属 chunk 的 hash
  • 生成最终 compilation 的 fullHash 与 hash

    • 使用主 hash 对象生成结果,hashfullHash 的截断
  • 返回待生成代码的模块任务(codeGenerationJobs)

    • 为下一阶段(CodeGeneration)准备模块处理列表
js 复制代码
createHash() {
	// 开始初始化 hash 过程
	this.logger.time("hashing: initialize hash");
	const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph);
	const runtimeTemplate = this.runtimeTemplate;
	const outputOptions = this.outputOptions;
	const hashFunction = outputOptions.hashFunction;
	const hashDigest = outputOptions.hashDigest;
	const hashDigestLength = outputOptions.hashDigestLength;

	// 创建主 hash 对象(控制整个 compilation 的 hash)
	const hash = createHash(/** @type {Algorithm} */(hashFunction));
	if (outputOptions.hashSalt) {
		// 如果设置了 hashSalt,将其更新到 hash 中
		hash.update(outputOptions.hashSalt);
	}
	this.logger.timeEnd("hashing: initialize hash");

	// 如果存在子 Compilation,把子 Compilation 的 hash 也加进来
	if (this.children.length > 0) {
		this.logger.time("hashing: hash child compilations");
		for (const child of this.children) {
			hash.update(/** @type {string} */(child.hash));
		}
		this.logger.timeEnd("hashing: hash child compilations");
	}

	// 把所有警告信息加入 hash(确保警告变化会影响最终 hash)
	if (this.warnings.length > 0) {
		this.logger.time("hashing: hash warnings");
		for (const warning of this.warnings) {
			hash.update(`${warning.message}`);
		}
		this.logger.timeEnd("hashing: hash warnings");
	}

	// 同样处理错误信息
	if (this.errors.length > 0) {
		this.logger.time("hashing: hash errors");
		for (const error of this.errors) {
			hash.update(`${error.message}`);
		}
		this.logger.timeEnd("hashing: hash errors");
	}

	// 排序 chunks,确保顺序稳定
	this.logger.time("hashing: sort chunks");

	/** 将 chunks 分为 runtimeChunks 和 其他chunks */
	const unorderedRuntimeChunks = [];
	const otherChunks = [];
	for (const c of this.chunks) {
		if (c.hasRuntime()) {
			unorderedRuntimeChunks.push(c);
		} else {
			otherChunks.push(c);
		}
	}
	// 先根据 ID 排序,保证顺序稳定
	unorderedRuntimeChunks.sort(byId);
	otherChunks.sort(byId);

	/** 构建 runtimeChunk 的引用图 */
	const runtimeChunksMap = new Map();
	for (const chunk of unorderedRuntimeChunks) {
		runtimeChunksMap.set(chunk, {
			chunk,
			referencedBy: [],
			remaining: 0
		});
	}

	let remaining = 0;
	// 统计每个 runtime chunk 被哪些异步入口引用
	for (const info of runtimeChunksMap.values()) {
		for (const other of new Set(
			Array.from(info.chunk.getAllReferencedAsyncEntrypoints()).map(
				e => e.chunks[e.chunks.length - 1]
			)
		)) {
			const otherInfo = runtimeChunksMap.get(other);
			otherInfo.referencedBy.push(info);
			info.remaining++;
			remaining++;
		}
	}

	// 找出不被依赖的 runtime chunk 作为起点
	const runtimeChunks = [];
	for (const info of runtimeChunksMap.values()) {
		if (info.remaining === 0) {
			runtimeChunks.push(info.chunk);
		}
	}

	// 处理 runtime chunk 的引用链,确保 hash 顺序合理
	if (remaining > 0) {
		const readyChunks = [];
		for (const chunk of runtimeChunks) {
			const hasFullHashModules =
				chunkGraph.getNumberOfChunkFullHashModules(chunk) !== 0;
			const info = runtimeChunksMap.get(chunk);
			for (const otherInfo of info.referencedBy) {
				if (hasFullHashModules) {
					chunkGraph.upgradeDependentToFullHashModules(otherInfo.chunk);
				}
				remaining--;
				if (--otherInfo.remaining === 0) {
					readyChunks.push(otherInfo.chunk);
				}
			}
			if (readyChunks.length > 0) {
				readyChunks.sort(byId);
				for (const c of readyChunks) runtimeChunks.push(c);
				readyChunks.length = 0;
			}
		}
	}

	// 如果还有剩余引用,说明有循环依赖
	if (remaining > 0) {
		const circularRuntimeChunkInfo = [];
		for (const info of runtimeChunksMap.values()) {
			if (info.remaining !== 0) {
				circularRuntimeChunkInfo.push(info);
			}
		}
		circularRuntimeChunkInfo.sort(compareSelect(i => i.chunk, byId));
		const err =
			new WebpackError(`Circular dependency between chunks with runtime (${Array.from(
				circularRuntimeChunkInfo,
				c => c.chunk.name || c.chunk.id
			).join(", ")})
This prevents using hashes of each other and should be avoided.`);
		err.chunk = circularRuntimeChunkInfo[0].chunk;
		this.warnings.push(err);
		for (const i of circularRuntimeChunkInfo) runtimeChunks.push(i.chunk);
	}
	this.logger.timeEnd("hashing: sort chunks");

	// 准备工作数据结构
	const fullHashChunks = new Set();
	const codeGenerationJobs = [];
	const codeGenerationJobsMap = new Map();
	const errors = [];

	// === 主体模块处理逻辑 ===
	const processChunk = chunk => {
		this.logger.time("hashing: hash runtime modules");
		const runtime = chunk.runtime;

		// 遍历 chunk 中的模块,给还没生成 hash 的模块创建 hash
		for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
			if (!chunkGraph.hasModuleHashes(module, runtime)) {
				const hash = this._createModuleHash(
					module,
					chunkGraph,
					runtime,
					hashFunction,
					runtimeTemplate,
					hashDigest,
					hashDigestLength,
					errors
				);
				let hashMap = codeGenerationJobsMap.get(hash);
				if (hashMap) {
					const moduleJob = hashMap.get(module);
					if (moduleJob) {
						moduleJob.runtimes.push(runtime);
						continue;
					}
				} else {
					hashMap = new Map();
					codeGenerationJobsMap.set(hash, hashMap);
				}
				const job = {
					module,
					hash,
					runtime,
					runtimes: [runtime]
				};
				hashMap.set(module, job);
				codeGenerationJobs.push(job);
			}
		}
		this.logger.timeAggregate("hashing: hash runtime modules");

		// 生成 chunk 自身的 hash
		try {
			this.logger.time("hashing: hash chunks");
			const chunkHash = createHash(/** @type {Algorithm} */(hashFunction));
			if (outputOptions.hashSalt) {
				chunkHash.update(outputOptions.hashSalt);
			}
			chunk.updateHash(chunkHash, chunkGraph);

			this.hooks.chunkHash.call(chunk, chunkHash, {
				chunkGraph,
				codeGenerationResults: this.codeGenerationResults,
				moduleGraph: this.moduleGraph,
				runtimeTemplate: this.runtimeTemplate
			});
			const chunkHashDigest = chunkHash.digest(hashDigest);
			hash.update(chunkHashDigest);
			chunk.hash = chunkHashDigest;
			chunk.renderedHash = chunk.hash.slice(0, hashDigestLength);

			const fullHashModules = chunkGraph.getChunkFullHashModulesIterable(chunk);
			if (fullHashModules) {
				fullHashChunks.add(chunk);
			} else {
				this.hooks.contentHash.call(chunk);
			}
		} catch (err) {
			this.errors.push(
				new ChunkRenderError(chunk, "", err)
			);
		}
		this.logger.timeAggregate("hashing: hash chunks");
	};

	// 处理非 runtime 的 chunks
	for (const chunk of otherChunks) processChunk(chunk);
	// 处理 runtime chunks
	for (const chunk of runtimeChunks) processChunk(chunk);

	// 如果过程中有模块 hash 出错,则记录 error
	if (errors.length > 0) {
		errors.sort(compareSelect(err => err.module, compareModulesByIdentifier));
		for (const error of errors) {
			this.errors.push(error);
		}
	}

	// 结束 hash 过程
	this.logger.timeAggregateEnd("hashing: hash runtime modules");
	this.logger.timeAggregateEnd("hashing: hash chunks");

	this.logger.time("hashing: hash digest");
	this.hooks.fullHash.call(hash);
	this.fullHash = hash.digest(hashDigest);
	this.hash = this.fullHash.slice(0, hashDigestLength);
	this.logger.timeEnd("hashing: hash digest");

	// 处理依赖 chunk.hash 的模块(full hash modules)
	this.logger.time("hashing: process full hash modules");
	for (const chunk of fullHashChunks) {
		for (const module of chunkGraph.getChunkFullHashModulesIterable(chunk)) {
			const moduleHash = createHash(/** @type {Algorithm} */(hashFunction));
			module.updateHash(moduleHash, {
				chunkGraph,
				runtime: chunk.runtime,
				runtimeTemplate
			});
			const moduleHashDigest = moduleHash.digest(hashDigest);
			const oldHash = chunkGraph.getModuleHash(module, chunk.runtime);
			chunkGraph.setModuleHashes(
				module,
				chunk.runtime,
				moduleHashDigest,
				moduleHashDigest.slice(0, hashDigestLength)
			);
			codeGenerationJobsMap.get(oldHash).get(module).hash = moduleHashDigest;
		}

		// 重新生成 chunk 的 hash,因为其包含模块 hash 更新了
		const chunkHash = createHash(/** @type {Algorithm} */(hashFunction));
		chunkHash.update(chunk.hash);
		chunkHash.update(this.hash);
		const chunkHashDigest = chunkHash.digest(hashDigest);
		chunk.hash = chunkHashDigest;
		chunk.renderedHash = chunk.hash.slice(0, hashDigestLength);
		this.hooks.contentHash.call(chunk);
	}
	this.logger.timeEnd("hashing: process full hash modules");

	// 返回待 codeGeneration 的模块任务
	return codeGenerationJobs;
}
相关推荐
百锦再1 分钟前
React编程高级主题:错误处理(Error Handling)
前端·javascript·react.js·前端框架·vue·web·angular
阿里巴巴首席技术官1 分钟前
CSS 高级用法
前端·css
墨绿色的摆渡人20 分钟前
论文笔记(七十五)Auto-Encoding Variational Bayes
前端·论文阅读·chrome
今晚吃什么呢?41 分钟前
前端面试题之CSS中的box属性
前端·css
我是大龄程序员43 分钟前
Babel工作理解
前端
CopyLower1 小时前
提升 Web 性能:使用响应式图片优化体验
前端
南通DXZ1 小时前
Win7下安装高版本node.js 16.3.0 以及webpack插件的构建
前端·webpack·node.js
Mintopia2 小时前
深入理解 Three.js 中的 Mesh:构建 3D 世界的基石
前端·javascript·three.js
前端太佬2 小时前
暂时性死区(Temporal Dead Zone, TDZ)
前端·javascript·node.js
Mintopia2 小时前
Node.js 中 http.createServer API 详解
前端·javascript·node.js