in packages/plugin-long-term-caching/src/index.ts [13:167]
export default (api: PluginAPI, options: PluginOptions) => {
api.on('onChainWebpack', async (config: Chain, env: Evnrioment) => {
const opts = { ...options };
const { longTermCaching = false, version } = opts;
// 如果是 ssr 构建关闭长效缓存的构建
if (!longTermCaching || process.env.IS_SSR === 'true') {
return;
}
const isProductionBuild = (env.buildType & BuildType.Prod) > 0;
const isCloudBuild = env.buildType === BuildType.Prod_Cloud;
let {
// @ts-ignore
preferHashedModuleId,
// @ts-ignore
preferHashedNamelessChunkId,
// @ts-ignore
cacheableHtmlFilename,
// @ts-ignore
outputFilename,
// @ts-ignore
outputChunkFilename,
// @ts-ignore
extractedCssFilename,
// @ts-ignore
extractedCssChunkFilename,
// @ts-ignore
vendors,
// @ts-ignore
commonChunk
} = normalize(longTermCaching);
// set output
//
// 在 dev 模式下使用 [hash], [contenthash] 或 [chunkhash]
// 会造成一些难以排查的副作用和问题, 类如内存泄露
// ref:
// - https://github.com/webpack/webpack-dev-server/issues/377#issuecomment-241258405
//
config.output.filename(outputFilename).chunkFilename(outputChunkFilename);
config.when(!isProductionBuild, config => {
config.output.filename('[name].js').chunkFilename('[name].js');
});
setOptimization({ config, vendors, commonChunk });
const miniCssExtractPlugin = config.plugin('extract-css');
// 对原有的 MiniCssExtractPlugin 进行替换, 这个替换规则将优先于用户的原有配置
// 以避免出现缓存冲突的情况出现, 当指定 experiment.longTermCaching 之后使用用户设定的配置或默认配置进行强制覆盖
if (isProductionBuild && miniCssExtractPlugin) {
// 只进行替换操作, 避免默认的 merge 操作将配置中的数组进行 concat 的行为
miniCssExtractPlugin.tap(args => [
Object.assign({}, ...args, {
filename: extractedCssFilename,
chunkFilename: extractedCssChunkFilename
})
]);
} else {
config.plugins.delete('extract-css');
}
const htmlPlugin = config.plugin('HtmlPlugin');
if (
// 检查功能开关
cacheableHtmlFilename &&
// 只在构建模式开启
// dev server 不开启 html 版本号来保证调试的便利性
isProductionBuild &&
// 只在云构建模式开启
// 本地构建的结果有时候需要放到 server 容器中检查可用性, 不开启 html 版本号来保证调试的便利性
isCloudBuild &&
// 检查源配置中是否已经声明 HtmlWebpackPlugin, 如果没有声明则忽略 html 版本化
htmlPlugin
) {
const htmlFilename = getHtmlFilename(cacheableHtmlFilename, version);
htmlPlugin.tap(args => [
Object.assign({}, ...args, { filename: htmlFilename })
]);
}
//
// 使用 4 位的 base64 hash 值代替模块 id
// 避免由于关联模块的修改导致不应发生变化的模块 id 发生变化导致缓存失效
// ref:
// - https://webpack.js.org/plugins/hashed-module-ids-plugin/
//
if (
preferHashedModuleId &&
// 只在生产环境开启 hashed module id
// dev-server 在 watch 模式下计算 hash 会导致内存堆栈溢出
// https://github.com/webpack/webpack/issues/1914
isProductionBuild &&
webpack &&
webpack.HashedModuleIdsPlugin
) {
config.plugin('hashdModuleIds').use(webpack.HashedModuleIdsPlugin);
}
// 使用 4 位的 hash 值代替 chunk id
// 避免由于新增 chunk 后, webpack 在重新计算 chunk id 后的变化导致缓存失效
//
// TODO(xingda.xd): 将该部分抽取为可复用的插件
//
if (
preferHashedNamelessChunkId &&
// 只在生产环境计算 hashed chunk id
// dev-server 在 watch 模式下计算 hash 会导致内存堆栈溢出
// https://github.com/webpack/webpack/issues/1914
isProductionBuild &&
webpack &&
webpack.NamedChunksPlugin
) {
const hashNamelessChunk = () => {
// 记录已经生成的 hashed chunk id, 避免冲突
const usedIds = new Set();
return (chunk: any) => {
if (chunk.name) {
return chunk.name;
}
if (chunk.getModules) {
const modules = chunk.getModules();
const moduleIds = modules
.map((m: any) => m.id)
.sort()
.join(';');
const hash = crypto.createHash('sha256');
hash.update(moduleIds);
const hashId = hash.digest('hex');
let hashIdLen = 4;
let exactId;
do {
// 取出 hash 前 n 位, 和已经生成过的 chunk id 数据集进行比对
// 如果出现了冲突, 则再取 hash 前 n + 1 位再次进行比对, 直到冲突解决为止
exactId = hashId.substr(0, hashIdLen);
hashIdLen += 1;
} while (usedIds.has(exactId));
usedIds.add(exactId);
return exactId;
}
// 什么都没取到, 手动再见
return null;
};
};
config
.plugin('hashdNamelessChunkId')
.use(webpack.NamedChunksPlugin, [hashNamelessChunk()]);
}
});
};