利用程式碼分割功能減少 JavaScript 酬載

Houssein Djirdeh
Houssein Djirdeh

大多數網頁和應用程式都是由許多不同部分組成。與其在載入第一個網頁時,傳送構成應用程式的所有 JavaScript,不如將 JavaScript 分成多個區塊,這樣可以提升網頁效能。

本程式碼研究室說明如何使用程式碼分割,提升簡單應用程式的效能,該應用程式會排序三個數字。

瀏覽器視窗顯示名為「Magic Sorter」的應用程式,其中有三個輸入數字的欄位和一個排序按鈕。

測量

與往常一樣,請務必先評估網站成效,再嘗試進行任何最佳化。

  1. 如要預覽網站,請按下「查看應用程式」,然後按下「全螢幕」圖示 全螢幕
  2. 按下 `Control+Shift+J` 鍵 (在 Mac 上為 `Command+Option+J` 鍵) 開啟開發人員工具。
  3. 按一下 [網路] 分頁標籤。
  4. 選取「停用快取」核取方塊。
  5. 重新載入應用程式。

網路面板顯示 71.2 KB 的 JavaScript 組合。

在簡單的應用程式中排序幾個數字,就要用到 71.2 KB 的 JavaScript。What gives?

在原始碼 (src/index.js) 中,系統會匯入 lodash 程式庫,並在這個應用程式中使用。Lodash 提供許多實用的公用程式函式,但這裡只會使用套件中的單一方法。安裝及匯入整個第三方依附元件,但只使用其中一小部分,是常見的錯誤。

最佳化

您可以透過幾種方式縮減套件大小:

  1. 撰寫自訂排序方法,而非匯入第三方程式庫
  2. 使用內建的 Array.prototype.sort() 方法依數值排序
  3. 只從 lodash 匯入 sortBy 方法,而非整個程式庫
  4. 下載程式碼,只在使用者點選按鈕時排序

選項 1 和 2 都是縮減套件大小的合適方法 (對於實際應用程式而言,這兩種方法可能最合理)。不過,為了教學,本教學課程不會使用這些方法 😈。

選項 3 和 4 都有助於提升應用程式效能。本程式碼研究室的後續幾個章節將說明這些步驟。和任何程式碼教學課程一樣,請務必自行撰寫程式碼,不要複製並貼上。

只匯入所需內容

您需要修改幾個檔案,只從 lodash 匯入單一方法。 首先,請在 package.json 中替換這項依附元件:

"lodash": "^4.7.0",

使用:

"lodash.sortby": "^4.7.0",

現在在 src/index.js 中,匯入這個特定模組:

import "./style.css";
import _ from "lodash";
import sortBy from "lodash.sortby";

並更新值的排序方式:

form.addEventListener("submit", e => {
  e.preventDefault();
  const values = [input1.valueAsNumber, input2.valueAsNumber, input3.valueAsNumber];
  const sortedValues = _.sortBy(values);
  const sortedValues = sortBy(values);

  results.innerHTML = `
    <h2>
      ${sortedValues}
    </h2>
  `
});

重新載入應用程式、開啟開發人員工具,然後再次查看「Network」面板。

網路面板顯示 15.2 KB 的 JavaScript 組合。

這個應用程式的套件大小減少了 4 倍以上,但仍有進步空間。

程式碼分割

webpack 是目前最受歡迎的開放原始碼模組打包工具之一。簡單來說,Webpack 會將構成網頁應用程式的所有 JavaScript 模組 (以及其他資產) 組合成可供瀏覽器讀取的靜態檔案。

這個應用程式使用的單一套件可以分割成兩個獨立區塊:

  • 負責構成初始路徑的程式碼
  • 包含排序程式碼的次要區塊

使用動態匯入時,可以延遲載入次要區塊,或視需要載入。在這個應用程式中,只有在使用者按下按鈕時,才能載入組成區塊的程式碼。

首先,請移除 src/index.js 中排序方法的頂層匯入項目:

import sortBy from "lodash.sortby";

並在按鈕按下時觸發的事件監聽器中匯入:

form.addEventListener("submit", e => {
  e.preventDefault();
  import('lodash.sortby')
    .then(module => module.default)
    .then(sortInput())
    .catch(err => { alert(err) });
});

import() 功能是提案 (目前處於 TC39 流程的第 3 階段) 的一部分,旨在納入動態匯入模組的功能。webpack 已支援這項功能,並遵循提案中規定的相同語法。

import() 會傳回 Promise,並在解析時提供所選模組,該模組會分割成獨立區塊。模組傳回後,module.default 會用於參照 lodash 提供的預設匯出內容。這個 Promise 會與另一個 .then 串連,該 Promise 會呼叫 sortInput 方法,排序三個輸入值。在 Promise 鏈結結尾,.catch() 用於處理因錯誤而遭拒絕的 Promise。

最後一件事是在檔案結尾編寫 sortInput 方法。這必須是「傳回」函式的函式,該函式會從 lodash.sortBy 接收匯入的方法。巢狀函式隨後會排序這三個輸入值,並更新 DOM。

const sortInput = () => {
  return (sortBy) => {
    const values = [
      input1.valueAsNumber,
      input2.valueAsNumber,
      input3.valueAsNumber
    ];
    const sortedValues = sortBy(values);

    results.innerHTML = `
      <h2>
        ${sortedValues}
      </h2>
    `
  };
}

監控

最後一次重新載入應用程式,並再次密切注意「Network」(網路)面板。應用程式載入後,系統只會下載一小部分初始套件。

網路面板顯示 2.7 KB 的 JavaScript 組合。

按下按鈕排序輸入的數字後,系統會擷取並執行包含排序程式碼的區塊。

網路面板顯示 2.7 KB 的 JavaScript 套件,後面接著 13.9 KB 的 JavaScript 套件。

請注意,數字仍會排序!

結論

程式碼分割和延遲載入是極為實用的技術,可縮減應用程式的初始套件大小,直接大幅縮短網頁載入時間。不過,在應用程式中加入這項最佳化功能前,請先考量幾項重要事項。

延遲載入 UI

延遲載入特定程式碼模組時,請務必考量網路連線較弱的使用者體驗。使用者提交動作時,如果載入的程式碼區塊非常大,應用程式可能會看似停止運作,因此請考慮顯示某種載入指標。

延遲載入第三方節點模組

在應用程式中延遲載入第三方依附元件不一定是最佳做法,具體情況取決於您使用這些元件的位置。通常第三方依附元件會分割成個別的 vendor 組合,由於更新頻率不高,因此可以快取。進一步瞭解 SplitChunksPlugin 如何協助您完成這項操作。

使用 JavaScript 架構延遲載入

許多使用 webpack 的熱門架構和程式庫都提供抽象化功能,讓延遲載入比在應用程式中途使用動態匯入更簡單。

瞭解動態匯入的運作方式很有幫助,但請務必使用架構/程式庫建議的方法,延遲載入特定模組。

預先載入和預先擷取

盡可能利用 <link rel="preload"><link rel="prefetch"> 等瀏覽器提示,嘗試更快載入重要模組。webpack 支援這兩種提示,方法是在匯入陳述式中使用魔法註解。詳情請參閱「預先載入重要區塊」指南。

延遲載入程式碼以外的內容

圖片可能佔用應用程式的大部分空間。延遲載入摺疊區域下方或裝置可視區域外的圖片,可加快網站速度。詳情請參閱 Lazysizes 指南。