CC 4.0 授權

本節內容源自以下連結的內容,並受 CC BY 4.0 授權條款約束。

除非另有說明,否則以下內容可假設為基於原始內容修改和刪除的結果。

程式碼分割

Rspack 支援程式碼分割,允許將程式碼分割成其他區塊。您可以完全控制產生資源的大小和數量,這讓您在載入時間方面獲得效能提升。

在這裡,我們引入一個稱為 Chunk 的概念,它表示瀏覽器需要載入的資源。

動態導入

Rspack 使用符合 ECMAScript 動態導入提案的 import() 語法。

與 webpack 不一致的行為

Rspack 不支援 require.ensure

index.js 中,我們透過 import() 動態導入兩個模組,從而分離成一個新的區塊。

index.js
import('./foo.js');
import('./bar.js');
foo.js
import './shared.js';
console.log('foo.js');
bar.js
import './shared.js';
console.log('bar.js');

現在我們建置此專案,我們會得到 3 個區塊,src_bar_js.jssrc_foo_js.jsmain.js,如果您看到它們,您會發現 shared.js 同時存在於 src_bar_js.jssrc_foo_js.js 中,我們將在後續章節中移除重複的模組。

資訊

雖然 shared.js 存在於 2 個區塊中,但它只會執行一次,您不必擔心多個實例的問題。

進入點

這是分割程式碼最簡單且最直覺的方式。但是,這種方法需要我們手動配置 Rspack。讓我們從研究如何從多個進入點分割多個區塊開始。

rspack.config.js
/**
 * @type {import('@rspack/core').Configuration}
 */
const config = {
  mode: 'development',
  entry: {
    index: './src/index.js',
    another: './src/another-module.js',
  },
  stats: 'normal',
};

module.exports = config;
index.js
import './shared';
console.log('index.js');
another-module.js
import './shared';
console.log('another-module');

這將產生以下建置結果

... 資源大小 區塊 區塊名稱 another.js 1.07 KiB another [已發出] another index.js 1.06 KiB index [已發出] index 進入點 another = another.js 進入點 index = index.js [./src/index.js] 41 個位元組 {another} {index} [./src/shared.js] 24 個位元組 {another} {index}

同樣地,如果您檢查它們,您會發現它們都包含重複的 shared.js

SplitChunksPlugin

上面提到的程式碼分段非常直觀,但大多數現代瀏覽器都支援並行網路請求。如果我們將 SPA 應用程式的每個頁面劃分為單一區塊,並且當使用者切換頁面時,他們請求一個較大的區塊,這顯然沒有充分利用瀏覽器處理並行網路請求的能力。因此,我們可以將區塊分解成更小的區塊。當我們需要請求此區塊時,我們會同時請求這些較小的區塊,這將使瀏覽器的請求更有效率。

Rspack 預設會分割 node_modules 目錄中的檔案和重複的模組,將這些模組從它們的原始區塊提取到一個單獨的新區塊中。那麼為什麼我們的範例中的 shared.js 仍然在多個區塊中重複出現呢?這是因為我們範例中的 shared.js 大小非常小。如果將一個非常小的模組分割成一個單獨的區塊供瀏覽器載入,實際上可能會減慢載入過程。

我們可以將最小分割大小設定為 0,以允許單獨提取 shared.js

rspack.config.js
/**
 * @type {import('@rspack/core').Configuration}
 */
const config = {
  entry: {
    index: './src/index.js',
  },
+  optimization: {
+    splitChunks: {
+      minSize: 0,
+    }
+  }
};

module.exports = config;

當重新建置時,您會發現 shared.js 已被單獨提取,並且產品中還有一個額外的區塊包含 shared.js

強制分割某些模組

我們可以指定某些模組強制分組到單一區塊中,例如,以下配置

rspack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        test: /\/some-lib\//,
        name: 'lib',
      },
    },
  },
};

透過以上配置,所有路徑中包含 some-lib 目錄的檔案都可以提取到一個名為 lib 的單一區塊中。如果 some-lib 中的模組很少變更,則此區塊將持續命中使用者的瀏覽器快取,因此像這樣經過深思熟慮的配置可以提高快取命中率。

但是,將 some-lib 分隔為獨立的區塊也可能會有缺點。假設一個區塊僅依賴 some-lib 中非常小的檔案,但由於 some-lib 的所有檔案都分割為單一區塊,因此此區塊必須依賴整個 some-lib 區塊,導致載入量較大。因此,當使用 cacheGroups.{cacheGroup}.name 時,需要仔細考慮。

這裡有一個範例顯示 cacheGroup 的 name 配置的效果。

預先擷取/預先載入模組

在宣告導入時使用這些內聯指示詞,允許 Rspack 輸出「資源提示」,告知瀏覽器對於

  • prefetch:資源可能在未來用於某些導覽
  • preload:資源也將在目前的導覽期間需要

一個範例是具有 HomePage 元件,其呈現 LoginButton 元件,然後在點擊後按需載入 LoginModal 元件。

LoginButton.js
//...
import(/* webpackPrefetch: true */ './path/to/LoginModal.js');

這將導致 <link rel="prefetch" href="login-modal-chunk.js"> 附加在頁面的 head 中,這將指示瀏覽器在閒置時間預先擷取 login-modal-chunk.js 檔案。

資訊

Rspack 將在父區塊載入後新增預先擷取提示。

與預先擷取相比,預先載入指示詞有許多差異

  • 預先載入的區塊與父區塊並行開始載入。預先擷取的區塊在父區塊完成載入後開始。
  • 預先載入的區塊具有中等優先順序,並立即下載。預先擷取的區塊在瀏覽器閒置時下載。
  • 預先載入的區塊應由父區塊立即請求。預先擷取的區塊可以在未來隨時使用。
  • 瀏覽器支援度不同。

一個範例可以是具有一個 Component,其始終依賴應位於單獨區塊中的大型程式庫。

讓我們想像一個 ChartComponent 元件,其需要一個大型 ChartingLibrary。它在呈現時會顯示一個 LoadingIndicator,並立即按需導入 ChartingLibrary

ChartComponent.js
//...
import(/* webpackPreload: true */ 'ChartingLibrary');

當請求使用 ChartComponent 的頁面時,也會透過 <link rel="preload"> 請求 charting-library-chunk。假設 page-chunk 較小且完成速度較快,則頁面將顯示 LoadingIndicator,直到已請求的 charting-library-chunk 完成。由於它只需要一次來回行程而不是兩次,這將稍微縮短載入時間。尤其是在高延遲環境中。

資訊

不正確地使用 webpackPreload 實際上可能會損害效能,因此使用時請務必小心。

有時您需要自行控制預載。例如,任何動態引入的預載都可以透過非同步腳本完成。在串流伺服器端渲染的情況下,這會很有用。

const lazyComp = () =>
  import('DynamicComponent').catch(error => {
    // Do something with the error.
    // For example, we can retry the request in case of any net error
  });

如果腳本載入在 Rspack 自行開始載入該腳本之前失敗(如果該腳本不在頁面上,Rspack 會建立一個 script 標籤來載入其程式碼),則該 catch 處理常式將不會啟動,直到 chunkLoadTimeout 超時。這種行為可能令人意想不到。但這是可以解釋的 — Rspack 無法拋出任何錯誤,因為 Rspack 不知道腳本失敗了。Rspack 將會在錯誤發生後立即為該腳本添加 onerror 處理常式。

為了避免這種問題,您可以添加自己的 onerror 處理常式,在發生任何錯誤時移除該腳本。

<script
  src="https://example.com/dist/dynamicComponent.js"
  async
  onerror="this.remove()"
></script>

在這種情況下,出錯的腳本將被移除。Rspack 將建立自己的腳本,並且任何錯誤都將在沒有超時的情況下被處理。