LoginSignup
7
4

More than 5 years have passed since last update.

React16.7 Suspense & React.lazy + TypeScriptでRoute-based code splittingする

Last updated at Posted at 2019-03-13

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より登場の SuspenseReact.lazy() を使う。

routes.tsx

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も対象にする。

webpack.config.js
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オプションにはcommonjsamdsystemumdes2015または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ライフを!

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4