likes
comments
collection
share

怎么给 Webpack 构建生成的文件加version?

作者站长头像
站长
· 阅读数 10

前言

当使用 webpack 来构建打包输出文件时,默认输出的 js\css 文件是没有版本号的,为了解决浏览器缓存问题,一般会使用 Webpack hash 方式,给输出文件自动加上哈希值。

然后就可以给服务器资源设置长期强缓存,可大幅度提高该网站的 HTTP 缓存能力,从而大幅度提高网站的二次加载性能。

Webpack hash

hash 有三种类型:hash chunkhash contenthash

这里写个简单例子,测试下不同 hash 的效果。

// 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

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字符串都是同一个值。

怎么给 Webpack 构建生成的文件加version?

然后修改下 lazy1.js,再次build,发现所有输出文件的hash值都变了,而且同样都是同一个值。

怎么给 Webpack 构建生成的文件加version?

结果

通过以上结果,推测内部原理是此hash值是全局的,仅有一份,并且只要任何一个文件改变,重新build时这个hash值则会变。

这样就会有个问题,有时候我们只更改了一个子模块文件,就把所有文件的hash都变了,这样客户端刷新时,就得重新请求所有静态文件,所有缓存都会失效。这时候可以用另外两个hash类型解决这个问题。

第二种类型 chunkhash

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值是相同的。

怎么给 Webpack 构建生成的文件加version?

此时修改下 lazy1.js,再次build。

怎么给 Webpack 构建生成的文件加version?

发现只有index和lazy1两个文件hash值改变了,lazy1变了符合预期,但是index为什么也变了呢?因为lazy1是在index里动态引用的,lazy1的hash变了,index里也得改下对应hash,对比下前后index文件change:

怎么给 Webpack 构建生成的文件加version?

结果

通过以上结果可以推测下内部原理,chunkhash 是根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的hash值。对于非入口的chunk,比如动态import引用的文件,也会被当做一个chunk,也会生成对应hash值。而且每个chunk独立,如果文件没有更改则不会变化hash值。

这样如果只改了一个chunk,则构建后只有这个chunk下文件(js\css)version变化,则需要浏览器重新请求,符合预期。但是现在还有一个问题,如果只改了css内容,也会改变整个chunk下是所有文件hash值,就会更新js的hash值,如何只改变css文件的hash值呢?

第三种类型 contenthash

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值都不一样。

怎么给 Webpack 构建生成的文件加version?

经测试:

  1. 更改lazy1.js:输出的 index.js lazy1.js hash值变化;
  2. 更改index.js:输出的 index.js hash值变化;
  3. 更改lazy1.css:输出的 lazy1.css hash值变化;
  4. 更改index2.js:输出的 index2.js hash值变化;
  5. 更改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,这些文件一般不会变化。

module: {
    rules: [
        {
            test: /\.(png|jpg|jpeg|gif|svg)$/,
            type: "asset",
            parser: {
                dataUrlCondition: {
                    maxSize: 10 * 1024,
                }
            },
            generator: {
                filename: 'images/[name].[contenthash:8][ext]',
            },
        },