关于缓存这里不多说,如果有点遗忘的,可以通过 一文读懂前端缓存 这篇文章补补课。本文要说的是,我们如何通过合理配置 webpack 去实现每次上线发布的静态资源 (CSS, JS, 图片) 的文件名称都是独一无二的。
可能你会说,配 hash 咯,但是关于 hash,其实里面有不少小细节。本文主要回答两个问题:1)hash、chunkhash 和 contenthash 的区别;2)在 webpack 中如何固定 chunkhash。如果这两个问题你都已经能解答,那可以不用浪费时间往下读啦
hash
hash 模块识别码,用来处理模块之间的依赖关系。hash 是跟每一次 webpack 打包的过程有关,任意增添或删减一个模块的依赖,hash 值都会更改,并且全部文件都共用相同的 hash 值。
为什么图片也是采用 hash 值却和别人不一样呢? 因为 file-loader 的 hash 字段是这个 loader 自己定义的占位符,和 webpack 的内置 hash 字段并不一致。
chunkhash
chunkhash 根据不同的入口文件进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。只要我们不改动代码,就可以保证其哈希值不会受影响。
但是要保证 chunkhash 值在相关代码不变动的情况下也不变,还要做以下的事情:
1. 提取运行时代码
默认情况下,每个分离出来的 chunk 会包含 webpack 的 runtime 代码(用来解析和加载模块之类的运行时代码),所以即使该 chunk 没有改变,其他 chunk 改变了,chunkhash 值也会改变,所以需要提取这部分代码即:
optimization: {
runtimeChunk: {
name: 'manifest'
}
}
2. 固定moduleId
模块 id 默认是按引入的顺序排序的,所以即使文件内容没有变动,引入文件的顺序变动了,chunkhash 可能也会变动,所以模块 id 的排序规则也要改变。development 下默认采用路径的方式:
optimization: {
namedModules: true
}
生产环境使用全路径是有点太长所以可以使用 HashedModuleIdsPlugin 插件来根据路径生成 hash。
如上代码块,我们不改变 a.js 和 b.js 内的代码,只是调换两者引入的顺序,如果没有设置 namedModules, a.js 和 b.js的 chunkhash 值都会改变,打包效果对比如下图:
3. 固定chunkId
默认情况,生产环境下 chunks ID 是以自增的数字命名,所以我们增加 chunk 或者减少 chunk 的时候,也会导致顺序乱掉。所以,我们要固定 chunkId:
optimization: {
namedChunks: true // 默认情况下,开发环境为 true,生产环境为 false
}
这里有点小困惑,为什么默认情况下,开发环境为 true,生产环境为 false? 贴出git的解答。
简单来说就是固定 chunkId 会导致:1)打包大小增加一点点;2)chuck 名暴露
Tips:[chunkhash:8] 类似这种写法是截取 hash 值的前 8 位,在生产环境不要这么做!取完整值 [chunkhash]。
在 webpack@3 中如果 JS 和 CSS 文件都采用 chunkhash,CSS 文件改变,chunkhash 值不会改变。只有 JS 文件改变,chunkhash 值才会改变。而在 webpack@4 中,不管是 CSS 文件还是 JS 文件改变,chunkhash 值都会改变。
但这都不是我们想要的结果,所以 CSS 文件的 hash 值应该采用 contenthash,以区分 CSS 文件和 JS 文件的更新。
contenthash
contenthash 根据当前文件的内容,来计算 hash 值。
那 JS 文件可以用 contenthash 吗? 可以,webpack 4.3+ 后。因为 contenthash 一开始只是被一些例如 extract-text-webpack-plugin 的插件使用,但是 webpack 4.3+ 后,webpack 自身也支持了。
总结
- 生成稳定的 hash 文件名(图片、字体等资源使用 hash 或 contenthash, js资源使用 chunkhash,css 资源使用 contenthash)
- 提取 webpack 的 runtime 代码
- 固定 moduleId 和 chunkId
参考资料