缓存

因此,我们使用 webpack 来捆绑我们的模块化应用程序,从而生成可部署的 /dist 目录。将 /dist 的内容部署到服务器后,客户端(通常是浏览器)将访问该服务器以获取网站及其资产。最后一步可能很耗时,这就是浏览器使用称为 缓存 的技术的原因。这允许网站以更快的速度加载,并减少不必要的网络流量。但是,当您需要获取新代码时,它也会导致麻烦。

本指南重点介绍确保 webpack 编译产生的文件能够保持缓存的配置,除非其内容已更改。

输出文件名

我们可以使用 output.filename 替换 设置来定义输出文件的名称。Webpack 提供了一种使用方括号字符串(称为 **替换**)来模板化文件名的方法。[contenthash] 替换将添加一个基于资产内容的唯一哈希。当资产的内容更改时,[contenthash] 也会更改。

让我们使用入门指南中的示例,并结合输出管理中的plugins来设置我们的项目,这样我们就不必手动维护我们的index.html文件了。

项目

webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
|- /dist
|- /src
  |- index.js
|- /node_modules

webpack.config.js

  const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: './src/index.js',
    plugins: [
      new HtmlWebpackPlugin({
-       title: 'Output Management',
+       title: 'Caching',
      }),
    ],
    output: {
-     filename: 'bundle.js',
+     filename: '[name].[contenthash].js',
      path: path.resolve(__dirname, 'dist'),
      clean: true,
    },
  };

使用此配置运行我们的构建脚本npm run build,应该会产生以下输出。

...
                       Asset       Size  Chunks                    Chunk Names
main.7e2c49a622975ebd9b7e.js     544 kB       0  [emitted]  [big]  main
                  index.html  197 bytes          [emitted]
...

如您所见,捆绑包的名称现在反映了其内容(通过哈希)。如果我们在不进行任何更改的情况下运行另一个构建,我们预计文件名将保持不变。但是,如果我们再次运行它,我们可能会发现情况并非如此。

...
                       Asset       Size  Chunks                    Chunk Names
main.205199ab45963f6a62ec.js     544 kB       0  [emitted]  [big]  main
                  index.html  197 bytes          [emitted]
...

这是因为 webpack 在入口块中包含了某些样板代码,特别是运行时和清单。

提取样板代码

正如我们在代码拆分中所学到的,SplitChunksPlugin可用于将模块拆分为单独的捆绑包。Webpack 提供了一个优化功能,可以使用optimization.runtimeChunk选项将运行时代码拆分为单独的块。将其设置为single以创建所有块的单个运行时捆绑包。

webpack.config.js

  const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: './src/index.js',
    plugins: [
      new HtmlWebpackPlugin({
      title: 'Caching',
      }),
    ],
    output: {
      filename: '[name].[contenthash].js',
      path: path.resolve(__dirname, 'dist'),
      clean: true,
    },
+   optimization: {
+     runtimeChunk: 'single',
+   },
  };

让我们运行另一个构建以查看提取的runtime捆绑包。

Hash: 82c9c385607b2150fab2
Version: webpack 4.12.0
Time: 3027ms
                          Asset       Size  Chunks             Chunk Names
runtime.cc17ae2a94ec771e9221.js   1.42 KiB       0  [emitted]  runtime
   main.e81de2cf758ada72f306.js   69.5 KiB       1  [emitted]  main
                     index.html  275 bytes          [emitted]
[1] (webpack)/buildin/module.js 497 bytes {1} [built]
[2] (webpack)/buildin/global.js 489 bytes {1} [built]
[3] ./src/index.js 309 bytes {1} [built]
    + 1 hidden module

将第三方库(例如lodashreact)提取到单独的vendor块中也是一个好习惯,因为它们不太可能像我们的本地源代码那样发生更改。此步骤将允许客户端从服务器请求更少的内容以保持最新。这可以通过使用cacheGroups选项来完成,该选项是SplitChunksPlugin的选项,如SplitChunksPlugin 的示例 2中所示。让我们添加optimization.splitChunks,其中包含cacheGroups,并使用以下参数进行构建。

webpack.config.js

  const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: './src/index.js',
    plugins: [
      new HtmlWebpackPlugin({
      title: 'Caching',
      }),
    ],
    output: {
      filename: '[name].[contenthash].js',
      path: path.resolve(__dirname, 'dist'),
      clean: true,
    },
    optimization: {
      runtimeChunk: 'single',
+     splitChunks: {
+       cacheGroups: {
+         vendor: {
+           test: /[\\/]node_modules[\\/]/,
+           name: 'vendors',
+           chunks: 'all',
+         },
+       },
+     },
    },
  };

让我们运行另一个构建以查看我们的新vendor捆绑包。

...
                          Asset       Size  Chunks             Chunk Names
runtime.cc17ae2a94ec771e9221.js   1.42 KiB       0  [emitted]  runtime
vendors.a42c3ca0d742766d7a28.js   69.4 KiB       1  [emitted]  vendors
   main.abf44fedb7d11d4312d7.js  240 bytes       2  [emitted]  main
                     index.html  353 bytes          [emitted]
...

现在我们可以看到,我们的 main 包不包含来自 node_modules 目录的 vendor 代码,并且大小已缩减至 240 字节

模块标识符

让我们将另一个模块 print.js 添加到我们的项目中

项目

webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
|- /dist
|- /src
  |- index.js
+ |- print.js
|- /node_modules

print.js

+ export default function print(text) {
+   console.log(text);
+ };

src/index.js

  import _ from 'lodash';
+ import Print from './print';

  function component() {
    const element = document.createElement('div');

    // Lodash, now imported by this script
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+   element.onclick = Print.bind(null, 'Hello webpack!');

    return element;
  }

  document.body.appendChild(component());

运行另一个构建,我们预计只有我们的 main 包的哈希值会改变,但是...

...
                           Asset       Size  Chunks                    Chunk Names
  runtime.1400d5af64fc1b7b3a45.js    5.85 kB      0  [emitted]         runtime
  vendor.a7561fb0e9a071baadb9.js     541 kB       1  [emitted]  [big]  vendor
    main.b746e3eb72875af2caa9.js    1.22 kB       2  [emitted]         main
                      index.html  352 bytes          [emitted]
...

...我们可以看到所有三个都改变了。这是因为每个 module.id 默认情况下是根据解析顺序递增的。这意味着当解析顺序发生变化时,ID 也会发生变化。总结一下

  • main 包由于其新内容而发生了变化。
  • vendor 包发生了变化,因为它的 module.id 发生了变化。
  • 并且,runtime 包发生了变化,因为它现在包含对新模块的引用。

第一个和最后一个是预期的,我们想要修复的是 vendor 哈希值。让我们使用 optimization.moduleIds 以及 'deterministic' 选项

webpack.config.js

  const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: './src/index.js',
    plugins: [
      new HtmlWebpackPlugin({
        title: 'Caching',
      }),
    ],
    output: {
      filename: '[name].[contenthash].js',
      path: path.resolve(__dirname, 'dist'),
      clean: true,
    },
    optimization: {
+     moduleIds: 'deterministic',
      runtimeChunk: 'single',
      splitChunks: {
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all',
          },
        },
      },
    },
  };

现在,即使有任何新的本地依赖项,我们的 vendor 哈希值也应该在构建之间保持一致

...
                          Asset       Size  Chunks             Chunk Names
   main.216e852f60c8829c2289.js  340 bytes       0  [emitted]  main
vendors.55e79e5927a639d21a1b.js   69.5 KiB       1  [emitted]  vendors
runtime.725a1a51ede5ae0cfde0.js   1.42 KiB       2  [emitted]  runtime
                     index.html  353 bytes          [emitted]
Entrypoint main = runtime.725a1a51ede5ae0cfde0.js vendors.55e79e5927a639d21a1b.js main.216e852f60c8829c2289.js
...

让我们修改我们的 src/index.js 以暂时删除该额外的依赖项

src/index.js

  import _ from 'lodash';
- import Print from './print';
+ // import Print from './print';

  function component() {
    const element = document.createElement('div');

    // Lodash, now imported by this script
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
-   element.onclick = Print.bind(null, 'Hello webpack!');
+   // element.onclick = Print.bind(null, 'Hello webpack!');

    return element;
  }

  document.body.appendChild(component());

最后,再次运行我们的构建

...
                          Asset       Size  Chunks             Chunk Names
   main.ad717f2466ce655fff5c.js  274 bytes       0  [emitted]  main
vendors.55e79e5927a639d21a1b.js   69.5 KiB       1  [emitted]  vendors
runtime.725a1a51ede5ae0cfde0.js   1.42 KiB       2  [emitted]  runtime
                     index.html  353 bytes          [emitted]
Entrypoint main = runtime.725a1a51ede5ae0cfde0.js vendors.55e79e5927a639d21a1b.js main.ad717f2466ce655fff5c.js
...

我们可以看到,两个构建都在 vendor 包的文件名中产生了 55e79e5927a639d21a1b

结论

缓存可能很复杂,但它对应用程序或网站用户的好处使其值得付出努力。请参阅下面的“进一步阅读”部分以了解更多信息。

进一步阅读

12 位贡献者

okonetjouni-kantolaskipjackdannycjonesfadysamirsadekafontcurosavagesaiprasad2595EugeneHlushkoAnayaDesignaholznersnitin315