webpack + React16.6以降(Suspense + React.lazy) + TypeScriptな環境で
Route-based code splittingをするためのTips。
ハマりどころに着目し、 // Point:
コメントを振ってあります。
公式:
https://reactjs.org/docs/code-splitting.html#route-based-code-splitting
ルーティング部分
ページごとにコード分割を図るべく、ページのエンドポイントとなるコンポーネントをdynamic import。
React16.7より登場の Suspense
、React.lazy()
を使う。
import * as React from 'react'
import { Route, Switch } from 'react-router-dom'
import App from './path/to/App'
// Point: static importではなくdynamic importをReact.lazyでラップする
const Page1 = React.lazy(() => import('./path/to/Page1'))
export default (
<App>
{/* Point: React.Suspenseでルーティング部分をラップ。
fallbackにはPage1コンポーネントが読み込まれるまでの間表示するコンポーネントを指定 */}
<React.Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/page1" component={Page1} />
</Switch>
</React.Suspense>
</App>
)
webpack
分割されたコードはoutput.publicPath配下に生成される。
また、ExtractTextPlugin
を利用している場合はallChunks
オプションを指定することで
分割されたコンポーネント内でimportされているCSSも対象にする。
const webpack = require('webpack')
const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: ['babel-polyfill', './src/index.tsx'],
output: {
path: path.join(__dirname, 'public/assets'),
// Point: ${publicPath}${chunkFilename}に分割後のファイルが配置される
publicPath: '/assets/',
filename: '[name].bundle.js',
chunkFilename: '[name].bundle.js'
},
...
resolve: {
modules: ['node_modules'],
extensions: ['.ts', '.tsx', '.js', '.pcss', 'css']
},
plugins: [
// Point: dynamic importするコンテンツがcssをimportする場合は、allChunks: trueオプションが必要
new ExtractTextPlugin({ filename: '[name].bundle.css', allChunks: true }),
new webpack.HotModuleReplacementPlugin(),
new ForkTsCheckerWebpackPlugin(),
new HtmlWebpackPlugin({
hash: true,
filename: path.join(__dirname, './public/index.html'),
template: './src/index.template.ejs',
inject: 'body'
})
]
})
tsconfig
moduleオプションにはcommonjs
、amd
、system
、umd
、es2015
またはesnext
が指定できるが、
dynamic importが有効なバージョンを指定する必要がある。
{
"compilerOptions": {
"baseUrl": ".",
"lib": ["dom", "es7"],
"outDir": "public",
"module": "esnext", // Point: moduleを esnext にすることでdynamic importを有効化する
"target": "es6",
"sourceMap": true,
"inlineSourceMap": false,
"inlineSources": false,
"experimentalDecorators": true,
"noUnusedParameters": true,
"noUnusedLocals": true,
"noEmitOnError": false,
"keyofStringsOnly": true,
"jsx": "react",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true
}
}
まとめ
Code Splittingによってメインのバンドルサイズが小さくなるので、初回ロード速度が大幅改善、データ転送量が減るのがメリットですが、コンポーネントの分割を行うためにコードが煩雑化しがちでした。
しかし、React16.6以降ではSuspenseとReact.lazyを駆使することで単純なコードで実現可能です。
TypeScriptでの実現も容易ですので、今後のReactプロジェクトにカジュアルに導入していくことができそうです。
(create-react-appで作った設定なら、特に意識せずそのままdynamic importも使えます!)
他にもつまづきポイントがあったら随時追記していきたいと思いますので、コメントもお待ちしております。
では よきCode Splittingライフを!