Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

某所のための資料

やりたいこと

サーバーサイドのアプリケーションから使われるために React.Component を育ててきたが、十分育ってきたので、主従関係を逆転させて react-router に乗せて SPAにできるか実験したい。

一つのJSにまとめると巨大なので、画面ごとに必要なコードに分割したい。

今回は SSR はスコープ外とする。

手段

  • dynamic import + webpack chunk で import('...') で呼びされるものは別の .js ファイルとして分割する
  • React.lazy + React.Suspense で 非同期ロードされるコンポーネントを遅延させて読み込む
  • ReactRouterDOM.Switch を Suspense を囲って、現在のルーティングに対応するコンポーネントだけ読み込むようにする

設定

まず webpack で dynamic import を扱うため、 typescript での変換を止める必要があり、 tsconfig.json で moduleESNext にする。

webpack.config.js での entry の設定を entry: [path.join(__dirname, "src/main.tsx")] と、ルーターを握るコンポーネントだけにする。

実装

非同期ロードしたいコンポーネント

// src/Foo.tsx
import React from "react";

export default () => <div>Foo</div>

があるとする。

この Foo を呼び出す側は、次のようになる。

// src/main.tsx
import React, { Suspense } from "react";
import ReactDOM from "react-dom";
import { Route, Switch, BrowserRouter } from "react-router-dom";

const Foo = React.lazy(() => import(/* webpackChunkName: "foo" */ "./Foo"));

export function App() {
  return (
    <>
        <BrowserRouter>
          <>
            <Suspense fallback={<LoadingMessage />}>
              <Switch>
                <Route exact path="/" component={Index} />
                <Route exact path="/about" component={About} />
                <Route exact path="/foo" component={Foo} />
              </Switch>
            </Suspense>
          </>
        </BrowserRouter>
    </>
  );
}

function LoadingMessage() {
  return <div>Loading...</div>;
}

function Index() {
  return (
    <div>
      <h2>Index</h2>
    </div>
  );
}

function About() {
  return (
    <div>
      <h2>About</h2>
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector("#root"));

Optional: 出力を見やすくするため、 webpack の output に chunkFilenameを指定する。

  output: {
     filename: '[name].[hash].js',
     chunkFilename: '[name]-chunk.[hash].js'
     // ...
  }

このときの webpack の出力は、

dist/foo-chunk.[hash].js
dist/main.[hash].js

のようになる。

同期で読み込んでいる Index と About は main.js として bundle されていて、この2つを実行する段階では foo.js は読み込まれない。/foo として Foo を非同期でロードする時に、はじめて js として評価され、ロードが発生する。 Foo は現在非常に小さいが、コード量が多い場合にこの方法が有効。

/* webpackChunkName: "foo" */ は実はなくてもいいいのだが、指定しない場合この name は 0 のような数字の連番になる。スタックトレースを追いづらい。

段階的SPA化

画面ごとのルート要素を、この App 要素に置き換え、該当する URL コンテキストで発動するようにすり合わせる。

十分に数が揃ったら、 ReactRouterDOM.Link 要素を使ってクライアント同士でルーティングできるようにする。

(なんやかんやでセッション情報を共有して)スタンドアローンアプリケーションとして切り出す。

終わり。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.