快取

因此,我們使用 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