怎么给 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]',
            },
        },
相关推荐
小纯洁w11 小时前
Webpack 的 require.context 和 Vite 的 import.meta.glob 的详细介绍和使用
前端·webpack·node.js
海盗强18 小时前
Webpack打包优化
前端·webpack·node.js
祈澈菇凉1 天前
如何优化 Webpack 的构建速度?
前端·webpack·node.js
懒羊羊我小弟1 天前
常用 Webpack Plugin 汇总
前端·webpack·npm·node.js·yarn
祈澈菇凉2 天前
Webpack的持久化缓存机制具体是如何实现的?
前端·webpack·gulp
懒羊羊我小弟3 天前
Webpack 基础入门
前端·webpack·rust·node.js·es6
刽子手发艺3 天前
Selenium+OpenCV处理滑块验证问题
opencv·selenium·webpack
懒羊羊我小弟3 天前
常用Webpack Loader汇总介绍
前端·webpack·node.js
真的很上进5 天前
【1.8w字深入解析】从依赖地狱到依赖天堂:pnpm 如何革新前端包管理?
java·前端·vue.js·python·webpack·node.js·reactjs
SuperherRo7 天前
信息收集-Web应用&JS架构&URL提取&数据匹配&Fuzz接口&WebPack分析&自动化
javascript·webpack·自动化·fuzz