webpack中fullhash、chunkhash和contenthash的区别

3,792 阅读4分钟

hash通常被作为前端静态资源实现增量更新的方案,通过在文件名中带上一串hash字符串,告诉浏览器该文件是否发生更新,从而决定是否要使用缓存机制。

webpack打包时的hash有三种:fullhash(webpack4.x版本及之前为 hash,webpack5.x中使用 fullhash 和 hash 均可)、chunkhashcontenthash ,那么他们之间有什么区别呢,下面我们一起来看下。

项目结构

我们有如下的一个目录结构:

hash
├─ package.json
├─ src
│  ├─ add.js
│  ├─ index.css
│  ├─ index.js
│  └─ sub.js
├─ webpack.config.js
└─ yarn.lock

webpack.config.js

webpack.config.js 配置如下:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
/**
 * @type {import('webpack').Configuration}
 */
module.exports = {
  entry: {
    index: './src/index.js',
    add: './src/add.js',
    sub: './src/sub.js',
  },
  mode: 'production',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: '[name].[fullhash].js',
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'compare hash',
    }),
  ],
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
    minimizer: [
      new TerserPlugin({
        extractComments: false,
      }),
      new MiniCssExtractPlugin({
        filename: '[name].[fullhash].css',
      }),
    ],
  },
};

index.js

index.js 文件内容如下,其引用了 add.js、sub.js 和 index.css,以及第三方库 lodash。

import _ from 'lodash';
import { add } from './add';
import { sub } from './sub';
import './index.css';

const addResult = add(56 + 211);
const subResult = sub(213 - 53);
console.log(_.join([addResult, subResult], '---'));

const box = document.createElement('div');
box.innerText = 'compare hash';
document.body.append(box);

add.js

add.js 中是一个简单的求和函数

export function add(a, b) {
  return a + b;
}

sub.js

sub.js 中是一个简单的求差函数

export function sub(a, b) {
  return a - b;
}

index.css

index.css 中对字体颜色做了设置

div {
  color: coral;
}

打包结果

fullhash

顾名思义,fullhash是全量的hash,是整个项目级别的。只要项目中有任何一个的文件内容发生变动,打包后所有文件的hash值都会发生改变。

上面的 webpack.config.js 中,已经将 js 和 css 文件打包后名称中带了 fullhash 选项,我们先执行 npx webpack 命令,对项目执行一次打包,打包后的文件如下,可以看到,所有文件的文件名中的 hash 值都是一样的:

image.png

然后我们对 add.js 进行一点修改:

export function add(a, b) {
+ console.log(a);
  return a + b;
}

再次执行 npx webpack ,可以看到,所有文件的 hash 值都发生了改变:

image.png

我们只改变了 add.js,受到影响的文件只有 add.js 文件本身和依赖它的文件 index.js,其他的文件我们显然是没必要更新 hash 的,那么如何规避这种情况呢,这就提到了下面的 chunkhash。

chunkhash

chunkhash根据不同的入口文件(entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值。当某个文件内容发生变动时,再次执行打包,只有该文件以及依赖该文件的文件的打包结果 hash 值会发生改变

我们将 webpack.config.js 中的 fullhash 修改为 chunkhash :

// ...
module.exports = {
  output: {
    path: path.resolve(__dirname, './dist'),
-   filename: '[name].[fullhash].js',
+   filename: '[name].[chunkhash].js',
    clean: true,
  },
  // ...
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
    minimizer: [
      new MiniCssExtractPlugin({
-       filename: '[name].[fullhash].css',
+       filename: '[name].[chunkhash].css',
      }),
    ],
  },
};

然后将 add.js 改回最初状态:

export function add(a, b) {
- console.log(a);
  return a + b;
}

再次执行 npx webpack 命令,打包结果如下:

image.png 可以看到 index.js 和 index.css 打包的 hash 值是一样的,这是因为在 index.js 中引用了 index.css ,打包后他们属于一个模块。

我们再次修改 add.js :

export function add(a, b) {
+ console.log(a);
  return a + b;
}

执行 npx webpack 打包, 结果如下:

image.png

可以看到虽然公共库 lodash 和 sub.js 文件的打包值这次没有改变,但是除了 add.js 和 index.js 的打包值发生了变动之外, index.css 的打包 hash 值也随之 index.js 的打包hash值发生了改变,这并不是我们期望的。

这就有了 contenthash 的用武之地。

contenthash

contenthash 是只有当文件自己的内容发生改变时,其打包的 hash 值才会发生变动。

我们修改 webpack.config.js 中 MiniCssExtractPlugin 插件对 css 文件的打包名称,将 chunkhash 修改为 contenthash:

// ...
module.exports = {
  // ...
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
    minimizer: [
      new MiniCssExtractPlugin({
-       filename: '[name].[chunkhash].css',
+       filename: '[name].[contenthash].css',
      }),
    ],
  },
};

然后执行 npx webpack 重新打包一下,打包结果如下:

image.png

可以看到此次 index.js 和 index.css 的打包结果的 hash 不再相同。我们再次修改一下 add.js 的内容:

export function add(a, b) {
- console.log(a);
  return a + b;
}

执行npx webpack,打包结果如下:

image.png

此次发现改变的 hash 只有 add.js 和 index.js 的打包结果,是符合我们预期的。

最佳实践

在生产环境下,我们对 output 中打包的文件名一般采用 chunkhash,对于 css 等样式文件,采用 contenthash,这样可以使得各个模块最小范围的改变打包 hash 值。

一方面,可以最大程度地利用浏览器缓存机制,提升用户的体验;另一方面,合理利用 hash 也减少了 webpack 再次打包所要处理的文件数量,提升了打包速度。

关于 webpack 中 fullhash、chunkhash 和 contenthash 的理解就到这里了,希望大家有所收获,有所疏漏之处也欢迎大家评论提点。