Component-based Code Splitting

Lazy Load React Component with React-Loadable

天下大勢,分久必合合久必分

在後端 MVC 統治天下的時代,一個頁面對應一個 JS Static File 是很正常不過的事情;而現今網站多以 SPA 前後端分離為主流的開發模式,通常碰到網路效能瓶頸在於第一次載入 JS 資源過於龐大。在 Webpack 1 以及 React-Router 3 時已經有 Code Split 的設定方法,但是多半是在 Routing 上的討論,很直覺得會 By 頁面切分,例如設定多個 Entry Points 來打包的 JS 檔。

在我以前專案中並沒有用過 Code Split,原因很簡單:專案複雜度會提昇不少。例如在寫 React-Router 的時候光看就很麻煩,Webpack 好像也要多些有的沒的設定。但在這次的導入過程中,先簡單地選擇其中一個優化空間較大的元件做嘗試,原來比想象的簡單許多,其他元件也應該這樣做,也許你應該也要試試看!


Create-React-App

麻煩事沒人想重複做,Create-React-App 很棒的幫你把開發上的設定通通做掉,意思是專案不需要再去追 Dev-Tools 套件的更新,更可以專注在開發上,例如這次 v0.9 到 v1.0 基本上是將近無痛升級的,並且多了 Webpack 2、PWA 等等的 Features。

Optimize Bundle Size

在開始 Code Split 前你應該先做的事,看看問題可能出在哪,這時候可以使用 Source-map-explorer 來幫你找出你的 Bundle JS 到底打包了什麼東西:

$ source-map-explorer build/static/js/main.*

我發現 Recharts 這個 Library 事實上是出現在很深層的頁面,在一般使用下,不太可能會在第一時間看到圖表,因此是一個可以優化的地方。

Dynamic import with React-Loadable

React-scripts 1.0.0 版本後終於內建 Webpack 2 了,除了專案省下很多設定上的麻煩,也多了 Dynamic import 這個 Feature,現在載入 Module 可以是 Async 的了:

// import(name) -> Promise
import('./ReactComponent')
.then(Component => /* ... */)
.cache(err => /* ... */);

透過 React Lifecycle 可以很簡單實現元件模組異步載入,而這件事在 React-Loadable 已經幫你處理好了,詳細的介紹可以看作者 James Kyle 的文章,其中最主要的差異在於這邊是做 Component-based 的 Code Splitting 而不是我第一段所說的 Route-based。

經過一小部分的程式碼修改後,可以將本來 Sync 載入的 React Component,改成 Async 版本,並且加上 Loading 的效果:

導入 React-Loadable 的 Git Diff

Create-React-App 在 Building 的輸出會告訴你 Bundle JS/CSS 的檔案大小,特別注意這邊會幫你計算成 Gzip 的 Size。從下右圖 Bundle Size 看到原本的一大包 JS 檔案(原為 1.3 MB)被分為兩包,分別為 main (867 KB) 、chunk (330 KB)。最後的呈現結果為左圖,當異步載入之前會使用轉圈圈的 Loading 效果,直到載入後才真的出現折線圖表:

React-Loadable loading DEMO

後記

如果你有走過開發工具的設定歷程,一定會知道 Create-React-App 的美好,關於更多其他此 OSS 專案中 Tools/Library 的選擇可以參考我的另一篇文章:Build A Web App in MediaTek

*完整 Pull Request 在 MCS-Lite/mcs-lite#327,如果你喜歡這系列文章,關於 Michael 在 OSS 的專案開發心得,別忘了可以點個 ❤️ 讓我知道喔!