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

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

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

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

設定部分

React 元件

首先在路由層自 react 引用 lazy 與 Suspense 功能,並使用 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 來看看加上懶載入的打包結果吧!

成果

使用懶載入:

./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

沒有使用懶載入:

./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 來執行分塊,避免使用者需要一口氣下載超大包的內容。

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