Create-React-Appのプロジェクトをビルドする際、プロジェクトが大きいとWebpackでバンドルされたファイルも大きくなってしまいます。
バンドルファイルのサイズが大きいと表示パフォーマンスに影響がでてしまい、ReactでSPAを作成した意味が薄れてしまいます。
そんな問題を解決するために、コード分割(Code Splitting)という方法が使われているということを知りました。
今のところ現場で使うことはなさそうなのですが、今後の仕事のために備忘録としてまとめました。
#Create-React-Appプロジェクトのビルド
Create-React-Appで生成したプロジェクトでビルドを実行すると、buildフォルダにいろんなファイルが出力されます。
publicフォルダの内容がbuildフォルダにコピーされているのですが、build/index.html
の中身をみると、public/index.html
には記述されていなかったscriptタグがbodyタグの末尾に挿入されています。
つまり、ウェブサーバにbuildフォルダを配置したとき、なにかしらのjsファイルが読み込まれReactのコンポーネントが表示されるようになっています。
読み込まれるjsファイル(static/js/
の中身)には以下のものがあります。
-
[number].[hash].chunk.js
: srcディレクトリ配下でインポートしたnode_modulesのコードが含まれます。 -
main.[hash].chunk.js
: App.jsなどのsrcディレクトリ配下のアプリケーションのコード含まれます。
dev-toolのNetworkタブをみてみると、こんな感じで読み込まれています。
#コード分割(Code Splitting)
コード分割は、バンドルファイルを分けてページロードを早くするための手法です。
コード分割を行っていない場合、ページを開いたときにプロジェクトのすべてのコード(=表示しないページを含んだコード)が含まれたバンドルファイルを読み込んでいます。
つまり、表示に関係のある部分のコードを別ファイルに分割して読み込めれば、その分表示が早くなることになります。
#React.lazy
コード分割を行うために、React.lazy関数を使用します。
この関数を使うことで、動的インポートを通常のコンポーネントとしてレンダーすることができます。
//動的インポート
import HomePage from './pages/homepage/homepage.component';
//React.lazy
const HomePage = lazy(() => import('./pages/homepage/homepage.component'));
例えば、以下の'/'パスを開いてみると、React.lazyのHomePage
の場合では、レンダー時点でHomePage
を除いたバンドルファイル(main.chunk.js)を読み込みます。
その後に、<Route exact path="/" component={HomePage} />
でHomePage
のバンドルファイル(0.chunk.js
)が読み込まれます。
return (
<div>
<GlobalStyle />
<Header />
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/shop" component={ShopPage} />
<Route exact path="/checkout" component={CheckoutPage} />
<Route
exact
path="/signin"
render={() =>
props.currentUser ? (
<Redirect to="/" />
) : (
<SignInAndSignUpPage />
)
}
/>
</Switch>
</div>
);
ただ、React.lazy関数(Promiseを返す)をそのまま読み込もうとするとエラーがでます。
そのため、HomePage
を取得するまでの間はloading
を出すように、Suspense
でラッピングしてあげます。
<Suspense fallback={<div>...loading</div>}>
<Route exact path="/" component={HomePage} />
</Suspense>
また、HomePage
以外のページについても分割するときは、Suspense
をラッピングする範囲を変えれば大丈夫です。
const HomePage = lazy(() => import('./pages/homepage/homepage.component'));
const ShopPage = lazy(() => import('./pages/shop/shop.component'));
const SignInAndSignUpPage = lazy(() =>
import('./pages/sign-in-and-sign-up/sign-in-and-sign-up.component')
);
const CheckoutPage = lazy(() => import('./pages/checkout/checkout.component'));
return (
<div>
<GlobalStyle />
<Header />
<Switch>
<ErrorBoundary>
<Suspense fallback={<div>...loading</div>}>
<Route exact path="/" component={HomePage} />
<Route path="/shop" component={ShopPage} />
<Route exact path="/checkout" component={CheckoutPage} />
<Route
exact
path="/signin"
render={() =>
props.currentUser ? (
<Redirect to="/" />
) : (
<SignInAndSignUpPage />
)
}
/>
</Suspense>
</ErrorBoundary>
</Switch>
</div>
);
#Error boundary
以上のコードでSuspense
をさらにErrorBoundary
でラッピングしていますが、これでコンポーネント内で発生したJavaScriptエラーが起こったときに、フォールバック用のUIを表示させることができます。
#おわりに
むやみやたらとコード分割をするのは悪手で、dev-toolなどで現状のパフォーマンスを計測してから最適化は行うべきじゃないとのことです。
#参考資料
https://ja.reactjs.org/docs/code-splitting.html