58
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Code SplittingでどれくらいReactアプリの初回ロード時間を減らせるか試してみる

Last updated at Posted at 2017-06-09

ということで、前々回書いた通りSSR(Server Side Rendering)したくない派ですが、CSRの問題は解決したいので今回は初期ロード時間対策でCode Splittingを試してみます。

基本的なことしか試さないので、一度も試したこと無い人向け程度の内容かと思います。

Code Splitting

この記事で言うCode Splittingはこのproposalにあるdynamic importを使ったCode Splittingのことです。react-routerを使った場合にrouteごとにjsファイルを分けることで、初期ロード時に1つの大きなバンドルされたjsファイルを読み込むのではなく、それぞれのrouteごとに必要最小限のjsファイルを読み込むことで初期ロード時間を低下させることを目的としたものです。(Routeは今回のデモのための例で、Route以外の用途にも使うことももちろん可能です)

ちなみにreact-routerのオフィシャルページだとdynamic importではなくbundle-loaderを使った方法が紹介されてます。今回はcreate-react-appを使いますが、bundle-loaderを使うにはejectするかイチからwebpackのファイルを作らないといけないので、bundle-loaderを使ったやり方はあとで試し次第追記します。

手順

手抜きですが前回書いたNetlifyの記事で作ったアプリの続きからやります。

create-react-appのページによると既にdynamic importは有効になっているので、特に何かを入れる必要はありません。

前述の通り公式だとdynamic importを使った方法は紹介されていないので、今回はこの記事を参考にさせて頂きました。

なお例によってコードはGitHubに上げました

非同期読み込み用のコンポーネントを作る

まずはこんな感じのコンポーネントを作ります。

AsyncContainer.js
import React from 'react';

export default (loader, collection) => (
  class AsyncContainer extends React.Component {
    constructor(props) {
      super(props);
      this.state = { Container: AsyncContainer.Container };
    }

    componentWillMount() {
      if (!this.state.Container) {
        loader().then((Container) => {
          this.setState({ Container });
        });
      }
    }

    render() {
      if (this.state.Container) {
        return (
          <this.state.Container { ...this.props } { ...collection } />
        )
      }
      return null;
    }
  }
);

それぞれのRouteを作る

普通にRouteを作れば良いだけです。基本的には前回と同じなので省略します。

それぞれのRouteを非同期読み込みをする

Routeを記載するところで以下のように読み込めばOKです。

App.js
import AsyncContainer from './containers/AsyncContainer';

const Home = AsyncContainer(() => import('./containers/Home')
  .then(module => module.default), { name: 'This is our Home page' });
const About = AsyncContainer(() => import('./containers/About')
  .then(module => module.default), { name: 'This is our About page' });
(省略)
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>

確認

ファイルサイズ確認

前回普通に作ったものと今回Splittingしたもののファイルサイズを比べてみます。

  • 前回

Screenshot from 2017-06-09 14-44-30.png

  • 今回

2.png

今回のほうが若干大きい(多分AsyncContainerの分)ですが、分割できてることは確認できました。

一部のRouteのみでmomentを読み込んでファイルサイズ確認

上記の結果だけだと分かりにくいので、比較的サイズの大きいライブラリとしてmomentを/aboutでのみ読み込んでサイズを比べてみます。

yarn add momentしてからAbout.jsで以下のようにてきとーにmomentを呼び出してみます。

import moment from 'moment';
(省略)
<span>moment().format()</span>

で、一旦AsyncContainerを外してbuildするとこんな感じのサイズになりました。

3.png

再びAsyncContainerを有効にしてbuildするとこんな感じでした。

4.png

Code Splittingしてない方はmainのjsしか生成されずmoment追加前と比べて50K増えています。Splittingした方はmainのjsのサイズはmoment追加前とほとんど変わらず、chunkファイルのうち1つが50K増えています。1つのファイルにバンドルされないので、初期ロード時間を低減することができそうです。

デプロイして確認

ということで、これをNetlifyにデプロイして確認してみます。
(てゆーか、Netlifyってデモだとめっちゃ便利。。。)

まずはルート(/)にアクセスしてDev Toolで確認すると

5.png

2つのjsファイルのみ読まれています。
次にmomentを使っている/aboutにアクセスすると

6.png

追加でもう1ファイル読まれました。

初期ページ用のjsファイルがgzipされた状態で60KB程度ならReactアプリとしては悪くないんじゃないでしょうか。
実際のアプリだともう少し増えたとしても100KB以下には抑えられる気がします。

Google Search Consoleで確認

一応Google Search Consoleでも確認してみますが、以下の通りちゃんと/aboutのページが正しく表示されています。

7.png

  • 追記(2017/06/28) - Search Consoleからの結果はプリレンダリングしないでもOKでした。また、reduxからネットワークリクエスト送ってから結果を表示するようなケースでも(プリレンダリング無しでも)大丈夫でした。以下のSlackで確認のところはもちろんプリレンダリング無いとダメです。

Slackで確認

スクショは前回と同じなので省略しますが、今回の対応で前回入れたOGP対応が壊れたりすることもありませんでした。

最後に

ということで、Code Splittingを試してみました。これで初期ロードに時間がかかる問題にもある程度は対応できそうです。
別記事に書いた(&書く予定の)SEO/OGP対応と合わせれば、従来のCSRで言われていた問題は回避できるので、自分のプロジェクトではアーキテクチャ的にあまり有り難くないSSRを選択する必要はなくなりそうです。

先に書いた通りbundle-loaderも試したら追記しようと思います。
また実戦投入して知見が溜まったら追記等したいと思います。

参考になれば幸いです。

58
36
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
58
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?