webpack 核心编译器 十五 节

getAssets()

📌 功能

返回一个所有已生成资源(assets)的只读数组。

js 复制代码
getAssets() {
	/** @type {Readonly<Asset>[]} */
	const array = [];
	for (const assetName of Object.keys(this.assets)) {
		if (Object.prototype.hasOwnProperty.call(this.assets, assetName)) {
			array.push({
				name: assetName,
				source: this.assets[assetName],
				info: this.assetsInfo.get(assetName) || EMPTY_ASSET_INFO
			});
		}
	}
	return array;
}

getAsset(name)

📌 功能

根据名称获取一个特定的资源(asset),如果没有找到则返回 undefined

js 复制代码
getAsset(name) {
	if (!Object.prototype.hasOwnProperty.call(this.assets, name)) return;
	return {
		name,
		source: this.assets[name],
		info: this.assetsInfo.get(name) || EMPTY_ASSET_INFO
	};
}

clearAssets()

📌 功能

清空所有 chunk 中的主文件与辅助文件列表。

js 复制代码
clearAssets() {
	for (const chunk of this.chunks) {
		chunk.files.clear();
		chunk.auxiliaryFiles.clear();
	}
}

createModuleAssets()

📌 功能

从模块的构建信息中提取出资源并注册到对应的 chunk 中,同时调用钩子。

js 复制代码
createModuleAssets() {
	const { chunkGraph } = this;
	for (const module of this.modules) {
		const buildInfo = /** @type {BuildInfo} */ (module.buildInfo);
		if (buildInfo.assets) {
			const assetsInfo = buildInfo.assetsInfo;
			for (const assetName of Object.keys(buildInfo.assets)) {
				const fileName = this.getPath(assetName, {
					chunkGraph: this.chunkGraph,
					module
				});
				for (const chunk of chunkGraph.getModuleChunksIterable(module)) {
					chunk.auxiliaryFiles.add(fileName);
				}
				this.emitAsset(
					fileName,
					buildInfo.assets[assetName],
					assetsInfo ? assetsInfo.get(assetName) : undefined
				);
				this.hooks.moduleAsset.call(module, fileName);
			}
		}
	}
}

getRenderManifest(options)

📌 功能

生成给定 chunk 的渲染清单(RenderManifest),供后续生成实际的文件。

js 复制代码
getRenderManifest(options) {
	return this.hooks.renderManifest.call([], options);
}

createChunkAssets(callback)

为所有 chunk 生成最终的资源文件(即构建产物),包括代码块、source map、辅助文件等,并存储在 this.assets 中。

📌 功能

这是生成所有 chunk 的最终产物(assets)的主流程。支持缓存、冲突检测和异步并发控制。

js 复制代码
createChunkAssets(callback) {
	const outputOptions = this.outputOptions;

	// 用于缓存 Source -> CachedSource 的映射,避免多次包装
	const cachedSourceMap = new WeakMap();

	// 检测是否有多个 chunk 输出了相同的文件名
	const alreadyWrittenFiles = new Map();

	// 并发处理每个 chunk,最多 15 个同时进行
	asyncLib.forEachLimit(
		this.chunks,
		15,
		(chunk, callback) => {
			let manifest;

			try {
				// 调用钩子获取当前 chunk 的渲染清单(renderManifest)
				manifest = this.getRenderManifest({
					chunk,
					hash: this.hash,
					fullHash: this.fullHash,
					outputOptions,
					codeGenerationResults: this.codeGenerationResults,
					moduleTemplates: this.moduleTemplates,
					dependencyTemplates: this.dependencyTemplates,
					chunkGraph: this.chunkGraph,
					moduleGraph: this.moduleGraph,
					runtimeTemplate: this.runtimeTemplate
				});
			} catch (err) {
				// 如果获取 manifest 失败,记录错误并继续下一个 chunk
				this.errors.push(new ChunkRenderError(chunk, "", err));
				return callback();
			}

			// 并发处理 manifest 中的每一项输出文件(比如 main.js, chunk.js 等)
			asyncLib.each(
				manifest,
				(fileManifest, callback) => {
					const ident = fileManifest.identifier;
					const usedHash = fileManifest.hash;

					// 资源缓存项(根据 identifier + hash 唯一定位)
					const assetCacheItem = this._assetsCache.getItemCache(ident, usedHash);

					// 从缓存中尝试获取 source(如 main.js 的内容)
					assetCacheItem.get((err, sourceFromCache) => {
						let filenameTemplate;
						let file;
						let assetInfo;

						// 统一处理错误:构建 ChunkRenderError 并调用回调
						const errorAndCallback = err => {
							const filename =
								file ||
								(typeof file === "string"
									? file
									: typeof filenameTemplate === "string"
										? filenameTemplate
										: "");
							this.errors.push(new ChunkRenderError(chunk, filename, err));
							return callback();
						};

						try {
							// 确定输出文件名和资源信息
							if ("filename" in fileManifest) {
								file = fileManifest.filename;
								assetInfo = fileManifest.info;
							} else {
								filenameTemplate = fileManifest.filenameTemplate;
								const pathAndInfo = this.getPathWithInfo(
									filenameTemplate,
									fileManifest.pathOptions
								);
								file = pathAndInfo.path;
								assetInfo = fileManifest.info
									? { ...pathAndInfo.info, ...fileManifest.info }
									: pathAndInfo.info;
							}

							if (err) return errorAndCallback(err);

							let source = sourceFromCache;

							// 文件名是否已由其他 chunk 使用?
							const alreadyWritten = alreadyWrittenFiles.get(file);
							if (alreadyWritten !== undefined) {
								// 文件名冲突,且 hash 不同(表示内容不同),报错
								if (alreadyWritten.hash !== usedHash) {
									return callback(new WebpackError(
										`Conflict: Multiple chunks emit assets to the same filename ${file}` +
										` (chunks ${alreadyWritten.chunk.id} and ${chunk.id})`
									));
								}
								// hash 相同,直接复用已生成的 source
								source = alreadyWritten.source;
							} else if (!source) {
								// 没有缓存,调用 render 函数生成源码
								source = fileManifest.render();

								// 强制缓存化处理,避免多次访问 source 成本过高
								if (!(source instanceof CachedSource)) {
									const cacheEntry = cachedSourceMap.get(source);
									if (cacheEntry) {
										source = cacheEntry;
									} else {
										const cachedSource = new CachedSource(source);
										cachedSourceMap.set(source, cachedSource);
										source = cachedSource;
									}
								}
							}

							// 注册资源(添加到 this.assets 中)
							this.emitAsset(file, source, assetInfo);

							// 将文件归类到 chunk 中:主文件还是辅助文件?
							if (fileManifest.auxiliary) {
								chunk.auxiliaryFiles.add(file);
							} else {
								chunk.files.add(file);
							}

							// 调用 chunkAsset 钩子
							this.hooks.chunkAsset.call(chunk, file);

							// 记录此文件名已被使用
							alreadyWrittenFiles.set(file, {
								hash: usedHash,
								source,
								chunk
							});

							// 如果 source 是新生成的,存入缓存
							if (source !== sourceFromCache) {
								assetCacheItem.store(source, err => {
									if (err) return errorAndCallback(err);
									return callback();
								});
							} else {
								callback();
							}
						} catch (err) {
							errorAndCallback(err);
						}
					});
				},
				callback // 所有 manifest 处理完成后
			);
		},
		callback // 所有 chunk 处理完成后
	);
}
相关推荐
web小白成长日记6 小时前
企业级 Vue3 + Element Plus 主题定制架构:从“能用”到“好用”的进阶之路
前端·架构
APIshop6 小时前
Python 爬虫获取 item_get_web —— 淘宝商品 SKU、详情图、券后价全流程解析
前端·爬虫·python
风送雨6 小时前
FastMCP 2.0 服务端开发教学文档(下)
服务器·前端·网络·人工智能·python·ai
XTTX1106 小时前
Vue3+Cesium教程(36)--动态设置降雨效果
前端·javascript·vue.js
LYFlied7 小时前
WebGPU与浏览器边缘智能:开启去中心化AI新纪元
前端·人工智能·大模型·去中心化·区块链
Setsuna_F_Seiei7 小时前
2025 年度总结:人生重要阶段的一年
前端·程序员·年终总结
model20058 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
han_8 小时前
从一道前端面试题,谈 JS 对象存储特点和运算符执行顺序
前端·javascript·面试
aPurpleBerry8 小时前
React 01 目录结构、tsx 语法
前端·react.js
jayaccc9 小时前
微前端架构实战全解析
前端·架构