什么是 Webpack 的 Hash、Chunkhash 和 Contenthash?
在 Webpack 中,hash、chunkhash 和 contenthash 是用来给输出的文件(bundle)添加动态哈希值的变量。
它们的主要作用是实现强缓存(Long-term Caching)和缓存刷(Cache Busting):当文件内容未改变时,文件名保持不变,浏览器继续使用本地缓存;当文件内容改变时,文件名随之改变,浏览器强制下载最新的文件。
这三者的区别在于计算哈希值时所基于的“作用域/粒度”不同。
1. hash(在 Webpack 5 中推荐写作 fullhash)
- 作用域:整个项目(每次编译 Compilation)。
- 计算机制:基于所有构建文件的内容计算出来的。只要项目中有任何一个文件发生了修改,重新构建后,所有输出文件的
hash值都会统一改变。 - 缺点:缓存命中率极低。即使你只修改了一行 CSS,打包出来的 JS 文件的文件名也会发生变化,导致浏览器无法利用之前的 JS 缓存。
- 适用场景:通常只在开发环境(Development)中使用,或者用于不需要精细化缓存控制的场景。
注意:Webpack 5 已将
[hash]重命名为[fullhash](但[hash]依然兼容)。
2. chunkhash
- 作用域:根据 Webpack 的 Chunk(代码块) 来计算。
- 计算机制:每个 Entry(入口文件)在 Webpack 中都会被构建为一个或多个 Chunk。
chunkhash是基于该 Chunk 本身及其依赖的文件计算的。只要该 Chunk 内部的代码不改变,其chunkhash就不变,其他独立的 Chunk 改变不会影响它。 - 优势:实现了入口文件级别的缓存隔离(例如:修改了
app.js,不会影响独立打包的vendor.js(第三方库)的哈希值)。 - 缺陷:如果在一个 Chunk 中同时引入了 JS 和 CSS(通过
MiniCssExtractPlugin抽离 CSS),当你只修改了 JS 时,因为它们属于同一个 Chunk,抽离出来的 CSS 文件的chunkhash也会跟着改变,导致 CSS 缓存失效。
3. contenthash
- 作用域:基于单个文件本身的内容。
- 计算机制:只要文件自身的内容不发生改变,其
contenthash就绝对不会变。 - 优势:最高粒度的缓存控制。完美的解决了
chunkhash的问题。例如:在同一个 Chunk 中,如果你只修改了 JS 代码,没有修改 CSS,那么打包后 JS 的contenthash会变,而抽离出的 CSS 的contenthash保持不变,CSS 文件依然可以完美命中浏览器缓存。 - 适用场景:生产环境(Production)的最佳实践。
总结对比表
| 哈希类型 | 影响范围 (Scope) | 触发变更的条件 | 推荐使用场景 |
|---|---|---|---|
| hash / fullhash | 整个项目 | 只要项目中任何文件改变 | 开发环境 |
| chunkhash | 同一个 Chunk (入口及依赖) | 同一 Chunk 内的代码发生改变 | 生产环境的多入口/代码分割 JS |
| contenthash | 仅当前文件本身 | 仅当前文件的内容改变 | 生产环境的 CSS 及所有静态资源 |
最佳实践配置示例 (Webpack 5 生产环境)
在实际的生产环境(Production)配置中,统一推荐优先使用 contenthash:
javascript
// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
mode: 'production',
output: {
path: path.resolve(__dirname, 'dist'),
// JS 输出:使用 contenthash
filename: 'js/[name].[contenthash:8].js',
chunkFilename: 'js/[name].[contenthash:8].chunk.js',
clean: true, // 清理旧文件
},
plugins: [
// 抽离出的 CSS 文件:使用 contenthash
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
}),
],
module: {
rules: [
{
test: /\.(png|jpg|gif)$/i,
type: 'asset/resource',
generator: {
// 图片等静态资源:使用 contenthash
filename: 'images/[name].[contenthash:8][ext]',
},
},
],
},
};
(注::8 表示只截取哈希值的前 8 位,可以让文件名更短。)