jyzwf / blog

在Issues里记录技术得点滴
17 stars 3 forks source link

hash、chunkhash、contenthash #81

Open jyzwf opened 5 years ago

jyzwf commented 5 years ago

现代前端打包,大多数是基于 webpack 来打包,打包就会涉及到文件的命名,文件的名称又会涉及到浏览器的缓存,所以如何命名文件很重要。

在打包过程中,我们常常会加上一个 hash 值来表示这个文件的唯一性。webpack 中提供了 3 种 hash,下面就来聊聊这些个 hash 。

准备工作

首先提供一个 react 项目,目录结构如下:

image

其中 webpack 配置如下:

const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const webpack = require("webpack");
const path = require("path");
module.exports = {
    mode: "production",
    entry: "./src/index.jsx",
    devtool: "inline-source-map",
    output: {
        filename: "[name].js",
        path: path.resolve(__dirname, "dist"),
                chunkFilename: "[name].js",
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: "babel-loader",
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            hmr: process.env.NODE_ENV === "development",
                        },
                    },
                    "css-loader",
                ],
            },
        ],

        noParse: /^(react|react-dom|lodash)$/,
    },
    devServer: {
        contentBase: "./dist",
        port: 3000,
        quiet: true,
    },
    resolve: {
        extensions: [".js", ".jsx", ".json"],
    },

    optimization: {
        splitChunks: {
            cacheGroups: {
                vendors: {
                    name: "chunk-vendors",
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10,
                    chunks: "initial",
                },
                common: {
                    name: "chunk-common",
                    minChunks: 2,
                    priority: -20,
                    chunks: "initial",
                    reuseExistingChunk: true,
                },
                commonAsync: {
                    name: "chunk-common-async",
                    minChunks: 2,
                    priority: -15,
                    chunks: "async",
                    reuseExistingChunk: true,
                },
            },
        },
    },

    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: "./src/index.ejs",
        }),
        new webpack.ProgressPlugin(),
        // new webpack.HotModuleReplacementPlugin(),
        new MiniCssExtractPlugin({
            filename: "[name].css",
        }),
    ],
};

关于 splitChunks 参见 聊一聊Code Splitting,上面我们使用了 mini-css-extract-plugin 来提取 css 文件。

不使用 hash

先来看下 不使用 hash 的情况打包结果: image

可以看出,文件名是固定的,这样如果我们修改了一些内容,如果使用了浏览器的缓存,那么下次请求这些文件的时候,会不去请求新的打包文件,从而造成文件不更新。

使用 hash

现在将上面的 filename 改成 [name].[hash].js/css,在看下打包情况:

image

可以看出,所有的文件使用了 同一个 hash 值,现在来修改 Hello/index.jsx 文件

image

hash 值变了,由于 hash 是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值都会更改,并且全部文件都共用相同的hash值。这样对于类如 chunk-vendors 文件来说是不可取的,因为这些文件是不会经常改变的,如果使用了 hash,那么浏览器的缓存对于 一些 npm 包文件来说是无效的。

chunkhash

上面讲到了 hash 会在每次打包的时候改变值,这样对于一些没有变的文件,类如 reactreact-dom ,完全起不到缓存效果。所以 webpack 又提供了一个 chunkhashchunkhash 根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。我们在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用chunkhash的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响。

将上面的 filename 改成 [name].[chunkhash].js/css

image

chunk-vendors 和 main 的hash 值不一样了,OK,我们实现了 公共库的一个 hash 不变,利于 浏览器对于 公共库的一个缓存。

现在继续修改 Hello/index.jsx 文件:

image

可以看出,即使我们修改了 代码,公共库的 hash 没有变。但我们也注意到了,css 的hash 和 main 的hash 一样,即使我只改变了 js 文件,css 文件也跟着改变了。这是由于主入口的 js 和 css 被打包在了 同一个 模块,所以公用了一个 hash 值。这样对于 css 来说也是不可取的,接下来就该 contenthash 出场了。

contenthash

我们可以使用 mini-css-extract-plugin 提供的功能来解决上述的问题,将上面的 filename 改成 [name].[contenthash].js/css

image

修改下 js 文件: image

ok,css 文件没有变,下面接着试下修改 css 文件:

image

css 与 js 的修改基本都不影响其他文件,great。

ps:如有不当,欢迎交流 😊😊

参考

webpack中的hash、chunkhash、contenthash区别 output.filename 缓存