某所のための資料
サーバーサイドのアプリケーションから使われるために 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 で module
を ESNext
にする。
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
のような数字の連番になる。スタックトレースを追いづらい。
画面ごとのルート要素を、この App 要素に置き換え、該当する URL コンテキストで発動するようにすり合わせる。
十分に数が揃ったら、 ReactRouterDOM.Link
要素を使ってクライアント同士でルーティングできるようにする。
(なんやかんやでセッション情報を共有して)スタンドアローンアプリケーションとして切り出す。
終わり。