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 处理完成后
);
}