前言
当使用 webpack 来构建打包输出文件时,默认输出的 js\css 文件是没有版本号的,为了解决浏览器缓存问题,一般会使用 Webpack hash 方式,给输出文件自动加上哈希值。
然后就可以给服务器资源设置长期强缓存,可大幅度提高该网站的 HTTP 缓存能力,从而大幅度提高网站的二次加载性能。
Webpack hash
hash 有三种类型:hash
chunkhash
contenthash
这里写个简单例子,测试下不同 hash 的效果。
js
// src/index.js 入口文件
import './index.css'; // 引用了css,为了测试输出的css文件
console.log('index');
// 动态import,模拟子模块懒加载,测试输出的chunk文件
(() => {
import(/* webpackChunkName: "lazy1" */ './lazy1').then(_ => { ... })
import(/* webpackChunkName: "lazy2" */ './lazy2').then(_ => { ... })
})();
// src/lazy1.js
import './lazy1.css';
console.log('lazy1')
// src/lazy2.js
console.log('lazy2')
// src/index2.js 另一个入口文件
import './index2.css';
console.log('index2');
output.filename、output.chunkFilename 定义
- output.filename:定义入口文件名。
- output.chunkFilename:定义非入口文件名,比如动态import、code split chunks 输出的文件。
第一种类型 hash
js
module.exports = {
entry: {
index: './src/index.js',
index2: './src/index2.js',
},
output: {
path: path.resolve(__dirname, 'public'),
filename: '[name].[hash:8].js', // 这里截取了前8位
chunkFilename: '[name].[hash:8].js',
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[hash:8].css",
chunkFilename: "[name].[hash:8].css",
}),
]
build后,发现所有输出文件的hash字符串都是同一个值。
然后修改下 lazy1.js
,再次build,发现所有输出文件的hash值都变了,而且同样都是同一个值。
结果
通过以上结果,推测内部原理是此hash值是全局的,仅有一份,并且只要任何一个文件改变,重新build时这个hash值则会变。
这样就会有个问题,有时候我们只更改了一个子模块文件,就把所有文件的hash都变了,这样客户端刷新时,就得重新请求所有静态文件,所有缓存都会失效。这时候可以用另外两个hash类型解决这个问题。
第二种类型 chunkhash
js
output: {
filename: '[name].[chunkhash:8].js',
chunkFilename: '[name].[chunkhash:8].js',
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[chunkhash:8].css",
chunkFilename: "[name].[chunkhash:8].css",
}),
]
build后,发现每个入口文件以及动态引用的文件的hash值都不同,同一个入口文件、或者同一个动态引入文件下,hash值是相同的。
此时修改下 lazy1.js
,再次build。
发现只有index和lazy1两个文件hash值改变了,lazy1变了符合预期,但是index为什么也变了呢?因为lazy1是在index里动态引用的,lazy1的hash变了,index里也得改下对应hash,对比下前后index文件change:
结果
通过以上结果可以推测下内部原理,chunkhash
是根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的hash值。对于非入口的chunk,比如动态import引用的文件,也会被当做一个chunk,也会生成对应hash值。而且每个chunk独立,如果文件没有更改则不会变化hash值。
这样如果只改了一个chunk,则构建后只有这个chunk下文件(js\css)version变化,则需要浏览器重新请求,符合预期。但是现在还有一个问题,如果只改了css内容,也会改变整个chunk下是所有文件hash值,就会更新js的hash值,如何只改变css文件的hash值呢?
第三种类型 contenthash
js
output: {
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].js',
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[contenthash:8].css",
chunkFilename: "[name].[contenthash:8].css",
}),
]
build后,发现每个构建生成文件的hash值都不一样。
经测试:
- 更改
lazy1.js
:输出的index.js
lazy1.js
hash值变化; - 更改
index.js
:输出的index.js
hash值变化; - 更改
lazy1.css
:输出的lazy1.css
hash值变化; - 更改
index2.js
:输出的index2.js
hash值变化; - 更改
index2.css
:输出的index2.css
hash值变化;
结果
通过以上结果,可以看到js和css独立开了,说明 contenthash
是根据文件内容生成的hash值,即通过构建生成的文件,如果内容没有变化,则生成的hash值也不会变。
总结
- hash:全局hash,所有输出文件hash值都相同。
- chunkhash:根据不同的入口文件(或非入口文件)进行依赖文件解析,构建对应的chunk,生成对应的hash值。
- contenthash:根据构建生成文件的内容生成hash值。
根据以上结果,看起来 contenthash
更符合预期,但是本文只是以一个简单例子分析三种hash的不同作用和原理,具体用哪个或哪些hash类型,根据不同项目代码构建需求来定的。
如果项目很小,可以直接选择用 contenthash
;如果项目比较大,复杂,比如有用到非入口的chunk(比如动态引入,按需加载等),或者有其它 code split chunks 需求的,则需要更仔细研究下三种hash原理了,可能需要区分 output.filename
output.chunkFilename
使用不同类型,无脑用 contenthash
可能并不是最佳方案。
另外其它类型资源文件的构建,比如图片、字体文件等,一般会选用 contenthash
,这些文件一般不会变化。
js
module: {
rules: [
{
test: /\.(png|jpg|jpeg|gif|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
}
},
generator: {
filename: 'images/[name].[contenthash:8][ext]',
},
},