一文搞懂webpack hash持久化

1,096 阅读15分钟

理解 module、chunk 和 bundle

  • module 就是我们通过 import 引入的各种模块

  • chunk 是 webpack 根据功能拆分出来的模块,包括入口文件, 动态 import,lazy 等的文件以及 splitChunks 拆分出来的代码,chunk 可能包含多个 module

  • bundle 就是 webpack 打包之后的各个文件,于 chunk 一般一一对应

hash 的分类

  • hash:the hash of the module identifier(根据 module_id 序列的变化而变化)

  • chunkHash:the hash of the chunk content(chunkHash,根据每一个 chunk 内容的变化而变化)

  • contentHash:the hash of extracted content(根据内容变化而变化)

hash

  • compilation

    • webpack 的 hash 是根据 compilation 计算出来的,compilation 对象代表某个版本的资源对应的编译进程,当我们的文件发生改变的时候, 进而能够针对改动生产全新的编译文件。compilation 对象包含当前模块资源、待编译文件、有改动的文件和监听依赖的所有信息,如果我们修改某一个文件,那么此时整个项目的 hash 都会改变
  • compiler

    • compiler 对象代表的是配置完备的 Webpack 环境。 compiler 对象只在 Webpack 启动时构建一次,由 Webpack 组合所有的配置项构建生成,compiler 对象代表的是不变的 webpack 环境,compilation 是针对随时可变的项目文件
  • module_id

    • webpack 通过给每一个模块一个 module_id 来处理各个模块之间的依赖关系,而默认的 id 命名规则是根据模块引入的顺序赋予一个整数(1,2,3),所以任意的增添或者删除一个模块的依赖,都会对整个的 ID 序列产生影响,最后影响 hash 值,这些模块会被 runtime 和 manifest 和引用到
  • 对于图片、字体、PDF 等资源该 hash 还是可以生成一个唯一值的

    • 此时我们配置 webpack 的 output 为 hash

      //  此时项目的
      mode: 'production',
      entry: {
          app: [path.resolve(__dirname, '../src/index.js')],
      },
      output: {
          filename: 'js/[name].[hash].js',
          hashDigestLength: 7,
          path: path.resolve(__dirname, '../dist'),
          publicPath: './',
      },
      
    • 项目依赖打包情况如下,我们可以看到所有的 hash 的值都是一样的

                      Asset       Size  Chunks                                Chunk Names
                css/app.2f3933e.css   52 bytes       0  [emitted] [immutable]         app
               css/list.2f3933e.css    1.5 KiB       1  [emitted] [immutable]         list
            css/vendors.2f3933e.css   71.2 KiB       2  [emitted] [immutable]         vendors
         css/vendors.2f3933e.css.gz   7.85 KiB          [emitted]
                         index.html   1.33 KiB          [emitted]
                  js/app.2f3933e.js   6.63 KiB       0  [emitted] [immutable]         app
                 js/list.2f3933e.js   50.9 KiB       1  [emitted] [immutable]         list
         js/list.2f3933e.js.LICENSE   120 bytes         [emitted]
              js/list.2f3933e.js.gz     15 KiB          [emitted]
              js/vendors.2f3933e.js    340 KiB       2  [emitted] [immutable]  [big]  vendors
      js/vendors.2f3933e.js.LICENSE  423 bytes          [emitted]
           js/vendors.2f3933e.js.gz   91.8 KiB          [emitted]
                 js/work.2f3933e.js  188 bytes       3  [emitted] [immutable]         work
      

runtime 和 manifest

  • webpack 通过 runtime 和 manifest 来管理所有模块的交互

  • runtime

    • runtime,以及伴随的 manifest 数据,主要是指:在浏览器运行过程中,webpack 用来连接模块化应用程序所需的所有代码。它包含:在模块交互时,连接模块所需的加载和解析逻辑。包括:已经加载到浏览器中的连接模块逻辑,以及尚未加载模块的延迟加载逻辑
  • manifest

    • 当 compiler 开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 "manifest",当完成打包并发送到浏览器时,runtime 会通过 manifest 来解析和加载模块。无论你选择哪种 模块语法,那些 import 或 require 语句现在都已经转换为 webpack_require 方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够检索这些标识符,找出每个标识符背后对应的模块
  • runtime 和 manifest 是一个每次打包都可能变化的不稳定的因素,所以他会导致一些问题,比如,我们对整个项目的文章在做一次打包,打包结果如下,我们发现,我们什么也没有改动但是 hash 全部发生了变化,原因就是 runtime 和 manifest 这些所谓的样板文件

                    Asset       Size  Chunks                                Chunk Names
                 css/app.2f3933e.css   52 bytes       0  [emitted] [immutable]         app
                css/list.2f3933e.css    1.5 KiB       1  [emitted] [immutable]         list
             css/vendors.2f3933e.css   71.2 KiB       2  [emitted] [immutable]         vendors
          css/vendors.2f3933e.css.gz   7.85 KiB          [emitted]
                          index.html   1.33 KiB          [emitted]
                   js/app.2f3933e.js   6.63 KiB       0  [emitted] [immutable]         app
                  js/list.2f3933e.js   50.9 KiB       1  [emitted] [immutable]         list
          js/list.2f3933e.js.LICENSE  120 bytes          [emitted]
                js/list.2f3933e.js.gz     15 KiB          [emitted]
                js/vendors.2f3933e.js    340 KiB       2  [emitted] [immutable]  [big]  vendors
        js/vendors.2f3933e.js.LICENSE  423 bytes          [emitted]
              js/vendors.2f3933e.js.gz   91.8 KiB          [emitted]
                    js/work.2f3933e.js  188 bytes       3  [emitted] [immutable]         work
    
  • 如何解决这个问题

    • 我们可以把 runtime 和 manifest 提取出来,去掉这两个不稳定因素,然后打包发现 hash 并未改变,但是我们多了一个 mainfest 文件
    optimization: {
        runtimeChunk: {
            name: 'manifest',
        },
    }
    
    • 再次打包代码,不断的打包 hash 都不会改变

                  Asset       Size  Chunks                                Chunk Names
                css/app.c870f3f.css        52 bytes        0  [emitted] [immutable]         app
               css/list.c870f3f.css        1.5 KiB         1  [emitted] [immutable]         list
            css/vendors.c870f3f.css        71.2 KiB        3  [emitted] [immutable]         vendors
         css/vendors.c870f3f.css.gz        7.85 KiB           [emitted] index.html 1.4 KiB [emitted]
                  js/app.c870f3f.js        3.62 KiB        0  [emitted][immutable] app
                 js/list.c870f3f.js        50.9 KiB        1  [emitted][immutable] list
         js/list.c870f3f.js.LICENSE        120 bytes          [emitted]
              js/list.c870f3f.js.gz        15 KiB             [emitted]
             js/manifest.c870f3f.js        3.07 KiB         2 [emitted][immutable] manifest
              js/vendors.c870f3f.js        340 KiB          3 [emitted][immutable] [big] vendors
      js/vendors.c870f3f.js.LICENSE       423 bytes          [emitted]
           js/vendors.c870f3f.js.gz       91.8 KiB           [emitted]
                 js/work.c870f3f.js       188 bytes        4 [emitted][immutable] work
      

chunkhash

  • chunk 就是模块。chunkhash 也就是根据模块内容计算出的 hash 值,很显然,hash 并不适合做本地持久化,所以我们使用 chunkhash 此时修改 webpack 的配置

    ```javascript
    optimization: webpackBase.optimization,
    mode: 'production',
    entry: {
        app: [path.resolve(__dirname, '../src/index.js')],
    }
    ```
    
  • 修改配置之后打包的结果是(CSS 的结果还是一样的,我们稍后处理)

                Asset       Size  Chunks                                Chunk Names
                css/app.8b9de76.css   71.3 KiB       0  [emitted] [immutable]         app
             css/app.8b9de76.css.gz   7.88 KiB          [emitted]
             css/vendors.8b9de76.css    1.5 KiB       3  [emitted] [immutable]         vendors
                          index.html   1.27 KiB          [emitted]
                   js/app.0df5dd7.js    340 KiB       0  [emitted] [immutable]  [big]  app
           js/app.0df5dd7.js.LICENSE  423 bytes          [emitted]
                 js/app.0df5dd7.js.gz   92.5 KiB          [emitted]
                   js/list.111956e.js   2.48 KiB       1  [emitted] [immutable]         list
               js/manifest.aa8eb6d.js   3.13 KiB       2  [emitted] [immutable]         manifest
                js/vendors.49e3e7f.js   48.5 KiB       3  [emitted] [immutable]         vendors
        js/vendors.49e3e7f.js.LICENSE  120 bytes          [emitted]
             js/vendors.49e3e7f.js.gz   13.8 KiB          [emitted]
                   js/work.1b2fd82.js  188 bytes       4  [emitted] [immutable]         work
    
  • 这个时候修改 list.js,然后继续打包,css 的 hash 变了,正常因为它使用的是 hash 不是 chunkhash,list 的 hash 也变了,正常因为我们修改了这个文件,work 的 hash 并没有变化,完全正常

    ```javascript
            Asset       Size  Chunks                                Chunk Names
                     css/app.1a93a35.css   71.3 KiB       0  [emitted] [immutable]         app
                  css/app.1a93a35.css.gz   7.88 KiB          [emitted]
                  css/vendors.1a93a35.css    1.5 KiB       3  [emitted] [immutable]         vendors
                               index.html   1.27 KiB          [emitted]
                        js/app.0df5dd7.js    340 KiB       0 [emitted] [immutable]  [big]  app
                js/app.0df5dd7.js.LICENSE  423 bytes         [emitted]
                     js/app.0df5dd7.js.gz   92.5 KiB         [emitted]
                       js/list.5b187e3.js   2.48 KiB       1 [emitted] [immutable]         list
                   js/manifest.3752b77.js   3.13 KiB       2 [emitted] [immutable]         manifest
                    js/vendors.49e3e7f.js   48.5 KiB       3 [emitted] [immutable]         vendors
            js/vendors.49e3e7f.js.LICENSE   120 bytes        [emitted]
                 js/vendors.49e3e7f.js.gz   13.8 KiB         [emitted]
                       js/work.1b2fd82.js   188 bytes      4 [emitted] [immutable] work
    ```
    
  • 这个时候我们为 list.js 引入一个新的 js,css 改变我们暂且不论,这个时候发现 vendors.js, app.js, work.js 竟然全部改变了, 这不符合我们的预期,这是因为每个 module.id 会基于默认的解析顺序(resolve order)进行增量(类似于没有指定 key 的 react 的组件的渲染)。也就是说,当解析顺序发生变化,ID 也会随之改变,所以我们需要自己命名这个 moduleid

    ```javascript
            Asset       Size  Chunks                                Chunk Names
                 css/app.636f1cd.css   52 bytes       0  [emitted] [immutable]         app
                css/list.636f1cd.css    1.5 KiB       1  [emitted] [immutable]         list
             css/vendors.636f1cd.css   71.2 KiB       3  [emitted] [immutable]         vendors
          css/vendors.636f1cd.css.gz   7.85 KiB          [emitted]
                          index.html    1.4 KiB          [emitted]
                   js/app.0b4c163.js   3.62 KiB       0  [emitted][immutable] app
                  js/list.d02fa6a.js     51 KiB       1  [emitted][immutable] list
          js/list.d02fa6a.js.LICENSE  120 bytes          [emitted]
                js/list.d02fa6a.js.gz     15 KiB          [emitted]
               js/manifest.3a9ff17.js   3.09 KiB       2  [emitted][immutable]
       manifest js/vendors.a1bfd17.js    340 KiB       3  [emitted][immutable] [big] vendors
        js/vendors.a1bfd17.js.LICENSE  423 bytes          [emitted]
             js/vendors.a1bfd17.js.gz   91.8 KiB          [emitted]
                   js/work.f70d2d8.js  188 bytes         4 [emitted][immutable] work
    ```
    
  • 我们自己命名这个 ID 把,命名的方式如下

    // 将默认的数字 id 命名规则换成路径的方式。webpack 4 中当 mode 为 development 会默认启动
    optimization: {
        namedModules: true
    }
    // 但是如果把路径作为ID难免太长,所以我们使用HashedModuleIdsPlugin来生成hash
    plugins: [
        new webpack.HashedModuleIdsPlugin(),
    ],
    
    // 此时进行打包的结果是
    
        Asset       Size  Chunks                                Chunk Names
                  css/app.d36a7df.css   52 bytes       0  [emitted] [immutable]         app
                 css/list.d36a7df.css    1.5 KiB       1  [emitted] [immutable]         list
              css/vendors.d36a7df.css   71.2 KiB       3  [emitted] [immutable]         vendors
           css/vendors.d36a7df.css.gz   7.85 KiB          [emitted]
                           index.html    1.4 KiB          [emitted]
                    js/app.5aef12b.js   3.74 KiB      0  [emitted] [immutable]         app
                   js/list.9dbf1d7.js   51.5 KiB      1  [emitted] [immutable]         list
           js/list.9dbf1d7.js.LICENSE  120 bytes         [emitted]
                js/list.9dbf1d7.js.gz   15.7 KiB         [emitted]
               js/manifest.cf2b1ee.js   3.09 KiB      2  [emitted] [immutable]         manifest
                js/vendors.612571f.js    343 KiB      3  [emitted] [immutable]  [big]  vendors
        js/vendors.612571f.js.LICENSE  423 bytes         [emitted]
             js/vendors.612571f.js.gz   97.7 KiB         [emitted]
                   js/work.3d8d43d.js  196 bytes      4  [emitted] [immutable]         work
    
  • 此时为 list 再次 import 一个文件,打包之后 hash 的值是,此时我们发现 app.js 的值没有变,list 的值改变了,vendors 和 work 都没变完全符合我们的预期,至此 js hash 的过程已经完全结束

    ```javascript
        Asset       Size  Chunks                                Chunk Names
                css/app.39db041.css   52 bytes       0  [emitted] [immutable]         app
                css/list.39db041.css    1.5 KiB       1  [emitted] [immutable]         list
             css/vendors.39db041.css   71.2 KiB       3  [emitted] [immutable]         vendors
          css/vendors.39db041.css.gz   7.85 KiB          [emitted]
                          index.html    1.4 KiB          [emitted]
                   js/app.5aef12b.js   3.74 KiB       0  [emitted] [immutable]         app
                  js/list.a0c9911.js   51.5 KiB       1  [emitted] [immutable]         list
          js/list.a0c9911.js.LICENSE  120 bytes          [emitted]
                js/list.a0c9911.js.gz   15.7 KiB          [emitted]
               js/manifest.14406a1.js   3.09 KiB       2  [emitted] [immutable]         manifest
                js/vendors.612571f.js    343 KiB       3  [emitted] [immutable]  [big]  vendors
        js/vendors.612571f.js.LICENSE  423 bytes          [emitted]
             js/vendors.612571f.js.gz   97.7 KiB          [emitted]
                    js/work.3d8d43d.js  196 bytes       4  [emitted] [immutable]         work
    ```
    

contentHash

  • 之前我们还有一个遗留问题,就是 css 的 hash 每次都会产生变化,是因为我们之前配置了抽离的 css 是 hash,根据上面的文章,我们修改为 chunkhash

    ```javascript
    // 之前的配置
    miniCssExtract: new MiniCssExtractPlugin({
        filename: 'css/[name].[hash].css',
        chunkFilename: 'css/[name].[hash].css',
        ignoreOrder: false,
    });
    // 修改之后的配置
    miniCssExtract: new MiniCssExtractPlugin({
        filename: 'css/[name].[chunkhash].css',
        chunkFilename: 'css/[name].[chunkhash].css',
        ignoreOrder: false,
    });
    ```
    
  • 修改为 chunkhash 之后,当然 css 的值就不会每次都发生变化了,此时我们对项目进行打包,然后修改 work.js 我们会发现 css 的 hash 并没有发生(此处不在尝试) 任何变化,完全符合我们的预期,但是我们却发现,我们是以 chunk 做 hash,所以导致了一个问题,list.js 和 list.css 的 hash 值一摸一样,因为他们属于同一个 chunk

    ```javascript
        Asset       Size  Chunks                                Chunk Names
                 css/app.5aef12b.css   52 bytes       0  [emitted] [immutable]         app
                css/list.a0c9911.css    1.5 KiB       1  [emitted] [immutable]         list
             css/vendors.612571f.css   71.2 KiB       3  [emitted] [immutable]         vendors
          css/vendors.612571f.css.gz   7.85 KiB          [emitted]
                          index.html    1.4 KiB          [emitted]
                   js/app.5aef12b.js   3.74 KiB       0  [emitted] [immutable]         app
                  js/list.a0c9911.js   51.5 KiB       1  [emitted] [immutable]         list
          js/list.a0c9911.js.LICENSE  120 bytes          [emitted]
                js/list.a0c9911.js.gz   15.7 KiB          [emitted]
               js/manifest.171619f.js   3.12 KiB       2  [emitted] [immutable]         manifest
                js/vendors.612571f.js    343 KiB       3  [emitted] [immutable]  [big]  vendors
        js/vendors.612571f.js.LICENSE  423 bytes          [emitted]
              js/vendors.612571f.js.gz   97.7 KiB          [emitted]
                    js/work.3d8d43d.js  196 bytes       4  [emitted] [immutable]         work
    ```
    
  • 对 app.css 做修改,然后重新打包,打包结果如下,我们发现,app.css 的 hash 发生了变化,但是 app.js 的 hash 也发生了变化,这就是因为 app.css 和 app.js 属于同一个 chunk,所以这个时候我们就必须对 css 单独处理让他根据自己的 content 去做 hash 而不是 chunk

    ```javascript
        Asset       Size  Chunks                                Chunk Names
                  css/app.131454e.css   52 bytes       0  [emitted] [immutable]         app
                 css/list.a0c9911.css    1.5 KiB       1  [emitted] [immutable]         list
              css/vendors.612571f.css   71.2 KiB       3  [emitted] [immutable]         vendors
           css/vendors.612571f.css.gz   7.85 KiB          [emitted]
                           index.html    1.4 KiB          [emitted]
                    js/app.131454e.js   3.74 KiB       0  [emitted] [immutable]         app
                   js/list.a0c9911.js   51.5 KiB       1  [emitted] [immutable]         list
           js/list.a0c9911.js.LICENSE  120 bytes          [emitted]
                js/list.a0c9911.js.gz   15.7 KiB          [emitted]
               js/manifest.171619f.js   3.12 KiB       2  [emitted] [immutable]         manifest
                js/vendors.612571f.js    343 KiB       3  [emitted] [immutable]  [big]  vendors
        js/vendors.612571f.js.LICENSE  423 bytes          [emitted]
             js/vendors.612571f.js.gz   97.7 KiB          [emitted]
                   js/work.3d8d43d.js  196 bytes       4  [emitted] [immutable]         work
    ```
    
  • 修改配置然后重新打包代码

    // 修改配置
    miniCssExtract: new MiniCssExtractPlugin({
        filename: 'css/[name].[contenthash].css',
        chunkFilename: 'css/[name].[contenthash].css',
        ignoreOrder: false,
    });
    
    /* 重新打包代码如下,可以看到app.js和app.css的hash不一致了 */
        Asset       Size  Chunks                                Chunk Names
                  css/app.15e0de3.css   52 bytes       0  [emitted] [immutable]         app
                 css/list.8298c67.css    1.5 KiB       1  [emitted] [immutable]         list
              css/vendors.353f491.css   71.2 KiB       3  [emitted] [immutable]         vendors
           css/vendors.353f491.css.gz   7.85 KiB          [emitted]
                           index.html    1.4 KiB          [emitted]
                    js/app.131454e.js   3.74 KiB       0  [emitted] [immutable]         app
                   js/list.a0c9911.js   51.5 KiB       1  [emitted] [immutable]         list
           js/list.a0c9911.js.LICENSE  120 bytes          [emitted]
                js/list.a0c9911.js.gz   15.7 KiB          [emitted]
               js/manifest.88160aa.js   3.12 KiB       2  [emitted] [immutable]         manifest
                js/vendors.612571f.js    343 KiB       3  [emitted] [immutable]  [big]  vendors
        js/vendors.612571f.js.LICENSE  423 bytes          [emitted]
             js/vendors.612571f.js.gz   97.7 KiB          [emitted]
                   js/work.3d8d43d.js  196 bytes       4  [emitted] [immutable]         work
    
  • 修改 app.css,然后再次打包代码,打包结果如下,我们发现 除了 app.css hash 改变,app.js 的 hash 一样的发生了改变,这又是为什么呢,通过试验是因为 CSS moduley 引起的问题,因为 css 文件的改变也会改变到 js,初步猜测是 css module 的问题,经过试验发现即使去掉 cssMOdule 还是有同样的问题

    ```javascript
                                    Asset       Size  Chunks                                Chunk Names
             css/app.29ae3c7.css   52 bytes       0  [emitted] [immutable]         app
             css/list.8298c67.css    1.5 KiB       1  [emitted] [immutable]         list
          css/vendors.353f491.css   71.2 KiB       3  [emitted] [immutable]         vendors
       css/vendors.353f491.css.gz   7.85 KiB          [emitted]
                       index.html    1.4 KiB          [emitted]
                js/app.3bafc2a.js   3.74 KiB       0  [emitted] [immutable]         app
               js/list.a0c9911.js   51.5 KiB       1  [emitted] [immutable]         list
       js/list.a0c9911.js.LICENSE  120 bytes          [emitted]
            js/list.a0c9911.js.gz   15.7 KiB          [emitted]
           js/manifest.88160aa.js   3.12 KiB       2  [emitted] [immutable]         manifest
            js/vendors.612571f.js    343 KiB       3  [emitted] [immutable]  [big]  vendors
    js/vendors.612571f.js.LICENSE  423 bytes          [emitted]
         js/vendors.612571f.js.gz   97.7 KiB          [emitted]
               js/work.3d8d43d.js  196 bytes       4  [emitted] [immutable]         work
    ```
    
  • 后来一想,其实跟上面的 app.js 和 app.css hash 一样是同样的问题,app.js 的改变,就是会改变 chunk 的值,所以把修改 webpack 的配置如下

            Asset       Size  Chunks                                Chunk Names
                css/app.44b7866.css   38 bytes       0  [emitted] [immutable]         app
               css/list.8298c67.css    1.5 KiB       1  [emitted] [immutable]         list
            css/vendors.353f491.css   71.2 KiB       3  [emitted] [immutable]         vendors
          css/vendors.353f491.css.gz   7.85 KiB          [emitted]
                          index.html    1.4 KiB          [emitted]
                   js/app.ca738c5.js   3.67 KiB       0  [emitted] [immutable]         app
                  js/list.1895c1a.js   51.5 KiB       1  [emitted] [immutable]         list
          js/list.1895c1a.js.LICENSE  120 bytes          [emitted]
               js/list.1895c1a.js.gz   15.7 KiB          [emitted]
               js/manifest.b7ee988.js   3.12 KiB       2  [emitted] [immutable]         manifest
                js/vendors.38fec86.js    343 KiB       3  [emitted] [immutable]  [big]  vendors
        js/vendors.38fec86.js.LICENSE  423 bytes          [emitted]
             js/vendors.38fec86.js.gz   97.7 KiB          [emitted]
                   js/work.ea3817c.js  196 bytes       4  [emitted] [immutable]         work
    
    /* 修改css之后然后再次打包,果然解决了之前的问题 */
    
        Asset       Size  Chunks                                Chunk Names
                 css/app.208b221.css   39 bytes       0  [emitted] [immutable]         app
                css/list.8298c67.css    1.5 KiB       1  [emitted] [immutable]         list
             css/vendors.353f491.css   71.2 KiB       3  [emitted] [immutable]         vendors
           css/vendors.353f491.css.gz   7.85 KiB          [emitted]
                           index.html    1.4 KiB          [emitted]
                    js/app.ca738c5.js   3.67 KiB       0  [emitted] [immutable]         app
                   js/list.1895c1a.js   51.5 KiB       1  [emitted] [immutable]         list
           js/list.1895c1a.js.LICENSE  120 bytes          [emitted]
                js/list.1895c1a.js.gz   15.7 KiB          [emitted]
               js/manifest.b7ee988.js   3.12 KiB       2  [emitted] [immutable]         manifest
                js/vendors.38fec86.js    343 KiB       3  [emitted] [immutable]  [big]  vendors
        js/vendors.38fec86.js.LICENSE  423 bytes          [emitted]
             js/vendors.38fec86.js.gz   97.7 KiB          [emitted]
                   js/work.ea3817c.js  196 bytes       4  [emitted] [immutable]         work
    
        /* 移除一个list引用的模块,再次打包,完全符合预期 */
                Asset       Size  Chunks                                Chunk Names
                  css/app.208b221.css   39 bytes       0  [emitted] [immutable]         app
                 css/list.8298c67.css    1.5 KiB       1  [emitted] [immutable]         list
              css/vendors.353f491.css   71.2 KiB       3  [emitted] [immutable]         vendors
           css/vendors.353f491.css.gz   7.85 KiB          [emitted]
                           index.html    1.4 KiB          [emitted]
                    js/app.ca738c5.js   3.67 KiB       0  [emitted] [immutable]         app
                   js/list.271a546.js   51.5 KiB       1  [emitted] [immutable]         list
           js/list.271a546.js.LICENSE  120 bytes          [emitted]
                js/list.271a546.js.gz   15.7 KiB          [emitted]
               js/manifest.a2e6ed1.js   3.12 KiB       2  [emitted] [immutable]         manifest
                js/vendors.38fec86.js    343 KiB       3  [emitted] [immutable]  [big]  vendors
        js/vendors.38fec86.js.LICENSE  423 bytes          [emitted]
             js/vendors.38fec86.js.gz   97.7 KiB          [emitted]
                   js/work.ea3817c.js  196 bytes       4  [emitted] [immutable]         work
    

webpack5

webpack5 对moduleIds & chunkIds的优化,不在是以数字作为id

optimization:{
    moduleIds:'deterministic',
    chunkIds:'deterministic'
},

如何使用 hash 做缓存呢?

很多人知道 hash,但是要不项目配置为 hash,不利于做长期缓存,要不前端配置好了,但是不知道如何配合后端做长期缓存,这就涉及到 http 缓存的

Etag - Last-Modified

  • 1、客户端请求一个页面 A

  • 2、服务器返回页面 A,并在给 A 加上一个 Last-Modified(Mon, 22 Mar 2018 10:10:10 GMT)和 ETag(2e681a-6-5d044840)

  • 3、客户端展现该页面,并将页面连同 Last-Modified/ETag 一起缓存

  • 4、客户再次请求页面 A,并将上次请求时服务器返回的 Last-Modified/ETag 一起传递给服务器,也就是说发送 If-None-Match 头,这个头的内容 就是 2e681a-6-5d044840,发送 If-Modified-Since(Mon, 22 Mar 2018 10:10:10 GMT)

  • 5、服务器判断发送过来的 Etag 和 Last-Modified 与本地匹配,如果没有修改,不返回 200,返回 304,直接返回响应 304 和一个空的响应体,当然响应头也会包含 Last-Modified(Mon, 22 Mar 2018 10:10:10 GMT)和 ETag(2e681a-6-5d044840)

Cache-control

  • Cache-control 判断浏览器是否需要发送请求而不需要服务器对比,常见的取值有 private、no-cache、max-age、must- revalidate、no-store 等,默认为 private,Cache-control 值为“no-cache”时,访问此页面不会在 Internet 临时文章夹留下页面备份

  • 打开新窗口

    • 值为 private、no-cache、must-revalidate,那么打开新窗口访问时都会重新访问服务器。 而如果指定了 max-age 值,那么在此值内的时间里就不会重新访问服务器,例如: Cache-control: max-age=5(表示当访问此网页后的 5 秒 内再次访问不会去服务器)
  • 在地址栏回车

    • 值为 private 或 must-revalidate 则只有第一次访问时会访问服务器,以后就不再访问。 值为 no-cache,那么每次都会访问。 值为 max-age,则在过期之前不会重复访问
  • 按后退按扭

    • 值为 private、must-revalidate、max-age,则不会重访问, 值为 no-cache,则每次都重复访问
  • 按刷新按扭或者 F6

    • 无论为何值,都会重复访问

Expires

  • Expires 和 max-age 都可以用来指定文档的过期时间,但是也有不同

  • Expires 指定一个绝对的过期时间(GMT 格式)

  • max-age 指定的是从文档被访问后的存活时间,这个时间是个相对值(比如:3600s),相对的是文档第一次被请求时服务器记录的 Request_time(请求时间)

  • 有的服务器, max-age 是这样计算出来的,expires - request_time

静态资源服务器的缓存

  • 如果是第一次访问,请求报文首部不会包含相关字段,服务端在发送文件前做如下处理

    • 如服务器支持 ETag,设置 ETag 头

    • 如服务器支持 Last-Modified,设置 Last-Modified 头

    • 设置 Expires 头 + 设置 Cache-Control 头(设置其 max-age 值)浏览器收到响应后会存下这些标记,并在下次请求时带上与 ETag 对应的请求首部 If-None-Match 或与 Last-Modified 对应的请求首部 If-Modified-Since

  • 如果是重复的请求

    • 浏览器判断缓存是否过期(通过 Cache-Control 和 Expires 确定, 两者都存在 Cache-Control为主)

      • 如果未过期,直接使用缓存内容,也就是强缓存命中,并不会产生新的请求

      • 如果已过期,会发起新的请求,并且请求会带上 If-None-Match 或 If-Modified-Since,或者兼具两者(两者都存在Etag 为主)

      • 服务器收到请求,进行缓存的新鲜度再验证:

        • 首先检查请求是否有 If-None-Match 首部,没有则继续下一步,有则将其值与文档的最新 ETag 匹配,失败则认为缓存不新鲜,成功则继续下一步

        • 接着检查请求是否有 If-Modified-Since 首部,没有则保留上一步验证结果,有则将其值与文档最新修改时间比较验证,失败则认为缓存不新鲜,成功则认为缓存新鲜

        • 当两个首部皆不存在或者验证结果是不新鲜时,发送 200 及最新文件,并在首部更新新鲜度。

        • 当验证结果是缓存仍然新鲜时(也就是弱缓存命中),不需发送文件,仅发送 304,并在首部更新新鲜度

max-age 配合 hash 做使用

在保持 hash 不变性的前提下,我们可以使用 max-age 来设置前端缓存

/* 具体设置多少,个人觉得要看升级的频率,在保证hash不变性的前提下,设置1y 比较合理https://expressjs.com/zh-cn/guide/using-middleware.html */
app.use(
  express.static(path.join(__dirname, "public"), {
    maxAge: "1y",
    expires: "1y",
    Etag: false,
    lastModified: false,
  })
);

参考文章