Skip to content

Instantly share code, notes, and snippets.

@mizchi
Last active April 29, 2022 06:19
Show Gist options
  • Save mizchi/c3089ad4d150c6bbd827a41d55aa7342 to your computer and use it in GitHub Desktop.
Save mizchi/c3089ad4d150c6bbd827a41d55aa7342 to your computer and use it in GitHub Desktop.

某所のための資料

やりたいこと

サーバーサイドのアプリケーションから使われるために 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