怎么给 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]',
            },
        },
相关推荐
吃饺子不吃馅7 小时前
【八股汇总,背就完事】这一次再也不怕webpack面试了
前端·面试·webpack
萌萌哒草头将军12 小时前
尤雨溪宣布 oxfmt 即将发布!比 Prettier 快45倍 🚀🚀🚀
前端·webpack·vite
weixin_4050233712 小时前
webpack 学习
前端·学习·webpack
八月ouc3 天前
每日小知识点:10.14 webpack 有几种文件指纹
前端·webpack
街尾杂货店&3 天前
webpack - 单独打包指定JS文件(因为不确定打出的前端包所访问的后端IP,需要对项目中IP配置文件单独拿出来,方便运维部署的时候对IP做修改)
前端·javascript·webpack
jiangzhihao05154 天前
前端自动翻译插件webpack-auto-i18n-plugin的使用
前端·webpack·node.js
_孤傲_5 天前
webpack实现常用plugin
前端·webpack·node.js
Jonathan Star5 天前
Webpack 打包优化与骨架屏结合:双管齐下提升前端性能与用户体验
前端·webpack·ux
细节控菜鸡6 天前
Webpack 核心知识点详解:proxy、热更新、Loader与Plugin全解析
前端·webpack·node.js
dcloud_jibinbin6 天前
【uniapp】体验优化:开源工具集 uni-toolkit 发布
前端·webpack·性能优化·小程序·uni-app·vue