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

前言

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

经测试:

  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,这些文件一般不会变化。

js 复制代码
module: {
    rules: [
        {
            test: /\.(png|jpg|jpeg|gif|svg)$/,
            type: "asset",
            parser: {
                dataUrlCondition: {
                    maxSize: 10 * 1024,
                }
            },
            generator: {
                filename: 'images/[name].[contenthash:8][ext]',
            },
        },
相关推荐
liangshanbo12152 天前
Webpack vs Vite 底层原理高频追问的问题
前端·webpack·node.js
liangshanbo12152 天前
面试题:AI 对话上下文超限怎么办?
面试·webpack·devops
liangshanbo12153 天前
Webpack 文件指纹面试题整理
前端·webpack·node.js
liangshanbo12153 天前
Webpack 常见 Plugin 面试题
前端·webpack·node.js
liangshanbo12153 天前
Webpack Tree Shaking 面试题整理
前端·webpack·node.js
陪我一起学编程3 天前
vue3笔记
前端·webpack·vue3·vite·脚手架·vue3笔记·组件式
liangshanbo12153 天前
Webpack的分包策略面试题
前端·webpack·node.js
liangshanbo12155 天前
面试题:如何优化 Webpack 的打包速度?
前端·webpack·node.js
晚风醉蝶6 天前
webpack 模块提取
前端·webpack·node.js·ast·逆向分析
晚风醉蝶16 天前
保姆级教程:用 AST 自动提取 webpack / rspack 打包后的模块
webpack·web逆向