將程式碼分割 JavaScript

載入大型 JavaScript 資源會對網頁速度造成大幅影響。分割中 將 JavaScript 分成較小的區塊,然後只下載 通常能改善網頁在啟動期間運作的網頁 反應,這將有助於改善您網頁與下一頁的互動情形 塗料 (INP)

隨著網頁下載、剖析及編譯大型 JavaScript 檔案, 在一段時間內沒有回應。網頁元素清晰可見 部分採用網頁初始 HTML 格式,且由 CSS 設定樣式。但由於 JavaScript 讓這些互動式元素及 網頁上的 — 可能會剖析和執行 JavaScript,讓網頁正常運作。 讓使用者覺得 甚至完全損壞

之所以會發生這項錯誤,通常是因為主要執行緒遭到封鎖,原因是系統剖析了 JavaScript 並透過主執行緒加以編譯如果處理時間過長,您可以 網頁元素對使用者輸入的回應速度可能不夠快。有一個救濟措施 是僅載入網頁運作所需的 JavaScript 透過名為 本單元著重介紹這兩種技巧的後者。

透過程式碼分割,減少啟動期間的 JavaScript 剖析和執行次數

如果 JavaScript 執行時間超過 2,Lighthouse 會擲回警告 並在超過 3.5 秒時失敗。JavaScript 過多 剖析和執行都是網頁在「任何」位置的潛在問題 因為可能會增加互動的輸入延遲時間 使用者與網頁互動的時間點是否恰當 負責處理和執行 JavaScript 的主執行緒工作 備用資源

而且,過度執行及剖析 JavaScript 這是網頁初始載入期間的問題 使用者與網頁互動的機率。事實上 「總封鎖時間」(TBT) 是一種負載回應指標,與高度相關 表示使用者很有可能嘗試與 INP 互動 初始載入期間。

回報每個 JavaScript 檔案執行時間的 Lighthouse 稽核 以便您明確找出 指令碼可能可使用程式碼分割功能。接著 使用 Chrome 開發人員工具的涵蓋率工具找出 網頁的 JavaScript 會在載入網頁時用到未使用的。

程式碼分割是能減少網頁初始 JavaScript 的實用技術 酬載。可讓您將 JavaScript 套件分割成兩個部分:

  • 網頁載入時所需的 JavaScript,因此無法在任何其他位置載入 讓應用程式從可以最快做出回應的位置 回應使用者要求
  • 其餘可於日後載入的 JavaScript,通常是 使用者與特定互動式元素互動的時間點 該網頁。

您可使用動態 import() 語法進行程式碼分割。這個 語法:與要求指定 JavaScript 資源的 <script> 元素不同 並在稍後的時間點,向 JavaScript 資源發出要求

document.querySelectorAll('#myForm input').addEventListener('blur', async () => {
  // Get the form validation named export from the module through destructuring:
  const { validateForm } = await import('/validate-form.mjs');

  // Validate the form:
  validateForm();
}, { once: true });

在上述 JavaScript 程式碼片段中,validate-form.mjs 模組是 只有在使用者將任何表單的模糊處理時,才會下載、剖析及執行 <input> 欄位。在此情況下,主要負責 只有在網頁產生表單時,才會影響表單的驗證邏輯 挑選出最有可能使用的產品

webpackParcelRollupesbuild 等 JavaScript 整合程式可以是 並設定每次都會將 JavaScript 套件分割成較小的區塊 就會在原始碼中遇到動態的 import() 呼叫。這些工具大多 但特別需要您選擇啟用 最佳化

關於程式碼分割的實用注意事項

雖然程式碼分割是減少主執行緒爭用情形的有效方法 第一次載入網頁時,如果您決定 稽核 JavaScript 原始碼,找出程式碼分割的機會。

如果可以,請使用套裝組合工具

開發人員在活動期間使用 JavaScript 模組是常見的做法, 開發流程這不僅是開發人員的優異體驗。 提升程式碼的可讀性和可維護性。不過 傳送 JavaScript 時可能產生的效能特性不彰 實際工作環境的工具

最重要的是,您應使用整合工具來處理來源和進行最佳化 包括您打算程式碼分割的模組套裝組合能大幅提升 在 JavaScript 原始碼中應用最佳化設定時 對平衡效能考量很有效,例如套裝組合大小 除以壓縮率隨著套件大小增加,壓縮效率 但套裝組合工具也會盡量確保套裝組合不會太大 執行耗時較長的工作

Bundler 也能避免要運送大量未組合模組的問題 經由網路使用 JavaScript 模組的架構通常為 複雜的模組樹狀結構取消封裝模組樹狀結構時,每個模組都代表 獨立的 HTTP 要求,如果您發現網頁應用程式的互動情形,也可能會延遲 請勿封裝模組雖然您可以使用 <link rel="modulepreload"> 資源提示:盡早載入大型模組樹狀結構 會盡可能降低 JavaScript 套件載入效能

不要不小心停用串流編譯

Chromium 的 V8 JavaScript 引擎提供許多立即可用的最佳化功能 確保實際執行的 JavaScript 程式碼能夠盡可能有效率地載入。 「串流編譯」是其中一種最佳化做法,例如 對串流至瀏覽器的 HTML 進行漸進式剖析 — 編譯串流的 當 JavaScript 來自網路時。

您可以透過幾種方式確保 Chromium 中的網頁應用程式:

  • 轉換實際工作環境程式碼,避免使用 JavaScript 模組。套裝組合 能夠根據編譯目標轉換 JavaScript 原始碼,且 而且通常是特定環境的特定項目V8 將套用串流 編譯不使用模組的任何 JavaScript 程式碼,然後您可以 設定 Bundler,將 JavaScript 模組程式碼轉換為語法 未使用 JavaScript 模組及其功能
  • 如果想將 JavaScript 模組推出至正式版,請使用 .mjs無論實際工作環境的 JavaScript 是否使用模組, 沒有使用模組與 JavaScript 的 JavaScript 特殊內容類型 但它不會顯示而 V8 擔心這個問題時,實際上是停用直播功能 使用 .js 在實際工作環境中傳送 JavaScript 模組時的編譯作業 。如果針對 JavaScript 模組使用 .mjs 擴充功能,V8 可以 確保無法針對以模組為基礎的 JavaScript 程式碼進行串流編譯 毀損。

別讓這些考量導致您無法使用程式碼分割。編程 分割是減少使用者初始 JavaScript 酬載的有效方法 但透過使用整合工具 並瞭解如何保留 V8 的串流 編譯行為,即可確保實際運作的 JavaScript 程式碼是 最快的速度

動態匯入示範

Webpack

webpack 隨附名為 SplitChunksPlugin 的外掛程式,讓 設定 Bundler 分割 JavaScript 檔案的方式。webpack 可辨識 動態 import() 和靜態 import 陳述式。當 如要修改 SplitChunksPlugin,請在其中指定 chunks 選項 設定:

  • chunks: async 是預設值,指的是動態 import() 呼叫。
  • chunks: initial 是指靜態 import 呼叫。
  • chunks: all 同時涵蓋動態 import() 和靜態匯入作業,可讓您 在 asyncinitial 匯入作業之間共用區塊。

根據預設,每當 Webpack 收到動態的 import() 陳述式時,就會產生該陳述式。該資料來源 為該模組建立單獨的區塊:

/* main.js */

// An application-specific chunk required during the initial page load:
import myFunction from './my-function.js';

myFunction('Hello world!');

// If a specific condition is met, a separate chunk is downloaded on demand,
// rather than being bundled with the initial chunk:
if (condition) {
  // Assumes top-level await is available. More info:
  // https://v8.dev/features/top-level-await
  await import('/form-validation.js');
}

上述程式碼片段的預設 webpack 設定會產生兩個 分為兩部分:

  • main.js 區塊 (Webpack 分類為 initial 區塊) 包含 main.js./my-function.js 模組。
  • async 區塊,只包含 form-validation.js (內含 資源名稱中的檔案雜湊 (如有設定)。僅下載這個區塊 if 以及 conditiontruthy 時。

這項設定可延後載入 form-validation.js 區塊,直到 但他們其實需要的藉由減少指令碼數量,改善載入回應的速度 評估時間。指令碼下載與評估 就會發生 form-validation.js 區塊。一旦滿足指定條件, 動態匯入模組就會直接下載舉例來說 單獨下載 polyfill 的特定瀏覽器,或如為 先前範例—需要匯入的模組才能與使用者互動。

另一方面,變更 SplitChunksPlugin 設定來指定 chunks: initial 可確保程式碼只會分割在初始區塊。這些 靜態匯入的區塊,或列於 webpack 的 entry 屬性。看看上述範例,產生的區塊會是 在單一指令碼檔案中定義 form-validation.js「和」main.js 的組合。 可能會降低初始網頁載入效能

您也可以將 SplitChunksPlugin 的選項設為分隔較大的 指令碼轉成多個較小的指令碼,例如使用「maxSize選項 如果 Webpack 的格式超過 由 maxSize 指定。將大型指令碼檔案分成多個較小的檔案, 加快載入速度,因為在某些情況下,評估會耗用大量 CPU 的指令碼 進而分割為小工作 長時間執行討論

此外,產生較大的 JavaScript 檔案也表示指令碼 快取撤銷的機率較高。舉例來說,假設你即將推出 架構和第一方應用程式程式碼的大型指令碼 如果只更新架構,若只有架構更新,則無法將套裝組合失效 組合資源

另一方面,較小的指令碼檔案也能提高 訪客從快取擷取資源,加快 重複造訪。不過,相較於更大的檔案,較小的檔案較不值得壓縮 可能有助於在未經背景工作的情況下,增加網頁載入網頁的時間 瀏覽器快取。必須謹慎以在快取之間取得平衡 效率、壓縮效果和指令碼評估時間。

Webpack 示範

webpack SplitChunksPlugin 示範

學以致用

執行程式碼時會使用哪一種 import 陳述式類型 分割?

動態 import()
答對了!
靜態 import
請再試一次。

哪種 import 陳述式必須位於頂端 但無法在其他位置載入?

動態 import()
請再試一次。
靜態 import
答對了!

在 webpack 中使用 SplitChunksPlugin 時: async區塊與 initial 個區塊?

已使用動態 import() 載入 async 個區塊 和 initial 個區塊是透過靜態資料載入 import
答對了!
async 個區塊是透過靜態 import 載入 並以動態方式載入 initial 個區塊 import()
請再試一次。

下一項:延遲載入圖片和 <iframe> 元素

雖然 JavaScript 的資源通常十分昂貴,但 JavaScript 並不是 只能延遲載入圖片和 <iframe> 個元素 則可能須投入大量昂貴資源與 JavaScript 類似 可以透過延遲載入,延後載入圖片和 <iframe> 元素 相關資訊,詳情請參閱本課程的下一個單元。