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 处理完成后
	);
}
相关推荐
mldong3 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排4 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60614 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角5 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!5 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰6 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi7 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen8 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马8 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒9 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端