普通文組 2.5

捨棄 create-react-app 之餘還架了個 astro blog 昭告天下:程式碼分塊

除了透過 webpack 的 optimization.splitChunks 來進行分塊外,也可使用 React 提供的 lazySuspense 來根據每一次 import 做分塊處理。

為什麼會需要這麼做呢?因為隨著一個 React app 的內容越來越多,你可能不會希望使用者開啟網站時一口氣把所有的內容都抓下來。因為這包內容可能很大、需要抓很久,使用者或許也不會探索這個網站的所有內容(抓了也沒用到)。

因此今天就來聊聊「如何根據專案路由來進行分塊」(`・∀・)

設定部分

React 元件

首先在路由層自 react 引用 lazySuspense 功能,並使用 lazy 來對每一條路由會用到的元件進行懶載入。此時可選擇加上 webpack magic comment 來指定分塊後的名稱以及 prefetch/preload 狀態。

import React, { lazy, Suspense } from "react";
const Product = lazy(
() =>
import(
/* webpackChunkName: "product" */
/* webpackMode: "lazy" */
/* webpackPrefetch: true */
"@Component/Page/Product"
),
);
const Landing = lazy(
() =>
import(
/* webpackChunkName: "landing" */
/* webpackMode: "lazy" */
/* webpackPrefetch: true */
/* webpackPreload: true */
"@Component/Page/Landing"
),
);
const NotFound = lazy(
() =>
import(
/* webpackChunkName: "not-found" */
/* webpackMode: "lazy" */
/* webpackPrefetch: true */
"@Component/Page/NotFound"
),
);

補充:在 webpack 5.87.0 後甚至開始支援 webpackFetchPriority 功能,可指定分塊的載入優先序

而你的路由元件大致上會長這樣子:

// ...上面是透過懶載入引用的元件
function ReactRouteLayer(): React.ReactElement {
/* Main */
return (
<BrowserRouter>
<Suspense fallback={<Loading />}>
<Switch>
<Route path="/" exact>
<Landing />
</Route>
<Route path="/product" exact>
<Product />
</Route>
<Route path="/404">
<NotFound />
</Route>
<Route path="*">
<Redirect to="/404" />
</Route>
</Switch>
</Suspense>
</BrowserRouter>
);
}

放在 Suspense.fallback 中的 <Loading /> 元件代表「畫面正在載入時,要顯示的替代內容」:

react: Suspense: An alternate UI to render in place of the actual UI if it has not finished loading.

備註:React 的第一版官方文件有比較多關於分塊的說明。

webpack

什麼都不用改。第 13 天介紹的設定內容一個字都不用動,直接執行 make build 來看看加上懶載入的打包結果吧!

成果

使用懶載入:

Terminal window
./build
├── static
├── css
├── product-31743c5a.fa288847.chunk.css
├── landing-15ac0598.766c0cdf.chunk.css
├── main-31743c5a.8244227e.css
└── not-found.c46f968c.chunk.css
└── js
├── ...其他 60 個分塊
├── product-17ac9733.b9ca3a17.js
├── product-88d3322f.6af21003.js
├── product-d91a9049.6bd193d8.js
├── landing-31743c5a.c50d3631.js
├── landing-443a0b6e.d0a45c76.js
├── landing-e96e9bea.3a9047a7.js
├── main-84781932.397ae544.js
├── main-c92480b7.d9863f01.js
├── main-d91a9049.28df7033.js
└── not-found.b83ea821.js
└── index.html

沒有使用懶載入:

Terminal window
./build
├── static
├── css
└── main-31743c5a.358f495d.css
└── js
├── ...其他 105 個分塊
├── main-03bc91d9.778a2136.js
├── main-0575efbd.3fe42463.js
├── main-17ac9733.9ae86415.js
├── main-1c176814.70827397.js
├── main-28095760.e2d645d6.js
├── main-28c0c43c.4af8e1f2.js
├── main-3a8144f2.faf9c54b.js
├── main-402b4630.157af26e.js
├── main-443a0b6e.dd433636.js
├── main-5ad2027e.148cad22.js
├── main-6b882012.9a243b76.js
├── main-6f34ed7a.4db55c82.js
├── main-7ae1fe87.367186f4.js
├── main-7c85fd9e.e3c95ef0.js
├── main-84781932.9b204099.js
├── main-87afd78e.9f1e9bc9.js
├── main-a3af9828.92b83948.js
├── main-a5409c8c.acca68b3.js
├── main-bc4009a1.443e7d0f.js
├── main-e0f427da.bcb1dc56.js
└── main-f16aa68b.cc8b68ba.js
└── index.html

可以看到 css 與 js 根據路由(landing / product / not-found)進行分塊了。

總結

當 React app 內容越來越多時,可考慮搭配 React 提供的 lazy / Suspense 來執行分塊,避免使用者需要一口氣下載超大包的內容。

如果以上設定有讓你困惑、或是不正確的部分歡迎留言交流 ( ´∀`)