react-scripts を使った SPA なしクライアントサイドレンダリングの例
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ react-scripts はエントリポイントはひとつしかできないみたい
◆ index.js で動的に 1 つのモジュールをロードしてそのコンポーネントを表示
◆ index.js で動的に 1 つのモジュールをロードしてそのコンポーネントを表示
前の SPA にしないクライアントサイドレンダリングの話の続きです
lit-html とかでやるなら 単純にそのページの画面を作るだけで迷うところもないです
JavaScript 空間はそのページ専用なのでグローバルにページ用のデータを置いてしまっても困りません
他のモジュールのロードは ES Modules で行います
ですが React だともう少し考えることが増えそうです
自分で Webpack の設定を書いてエントリポイントにページの一覧を書いていくのも面倒です
色々楽にするための react-scripts を使うとエントリポイントを複数作れないみたいな問題があった気もします
ただ Code Splitting 機能を使って最初にどれか 1 つを動的にインポートすればできそうと思ったのでやってみました
ページの URL の pathname に応じてページ用の .js ファイルをインポートしています
ここではページの URL に .js をつけたものをそのページの内容を作る JavaScript ファイルにしてます
対応させる必要は特に無いのですが 今回は単純に URL をそのまま使う感じにしました
この場合だとやりたいことは
なのですが 事前に Webpack するので静的にファイルが分かる必要があります
文字列でひとつひとつ指定しないといけません
ただ サーバのルートに対応してると思うので この switch 部分はサーバ側のルート定義を使って自動生成もできると思います
インポートしてる foo.js などは単純な React のコンポーネントです
必要な情報は asset-manifest.json に出力されているのでこれを使います
こういう部分があるので この entrypoints の .js ファイルをすべて埋め込んだ HTML ファイルを用意します
この HTML ファイルを各ページにアクセスしてきたときに返せば React で作ったページが見れます
あとは JSON 埋め込み部分を作って サーバ側の処理でページを表示するための情報を埋め込みます
サーバ側
クライアントサイドの index.js に書く switch 部分が面倒ですが 思ったよりは簡単にできました
lit-html とかでやるなら 単純にそのページの画面を作るだけで迷うところもないです
JavaScript 空間はそのページ専用なのでグローバルにページ用のデータを置いてしまっても困りません
他のモジュールのロードは ES Modules で行います
ですが React だともう少し考えることが増えそうです
自分で Webpack の設定を書いてエントリポイントにページの一覧を書いていくのも面倒です
色々楽にするための react-scripts を使うとエントリポイントを複数作れないみたいな問題があった気もします
ただ Code Splitting 機能を使って最初にどれか 1 つを動的にインポートすればできそうと思ったのでやってみました
動的インポート
index.js をこういう感じにしますimport ReactDOM from "react-dom"
let loading
switch (location.pathname) {
case "/foo":
loading = import("./pages/foo.js")
break
case "/bar":
loading = import("./pages/bar.js")
break
case "/foo/bar":
loading = import("./pages/foo/bar.js")
break
}
loading.then(mod => {
const Page = mod.default
const root = document.createElement("div")
document.body.append(root)
ReactDOM.render(<Page />, root)
})
ページの URL の pathname に応じてページ用の .js ファイルをインポートしています
ここではページの URL に .js をつけたものをそのページの内容を作る JavaScript ファイルにしてます
対応させる必要は特に無いのですが 今回は単純に URL をそのまま使う感じにしました
この場合だとやりたいことは
import("./pages" + location.pathname + ".js")
なのですが 事前に Webpack するので静的にファイルが分かる必要があります
文字列でひとつひとつ指定しないといけません
ただ サーバのルートに対応してると思うので この switch 部分はサーバ側のルート定義を使って自動生成もできると思います
インポートしてる foo.js などは単純な React のコンポーネントです
export default () => {
return <div>foo</div>
}
サーバから使う
react-scripts では public/index.html が要求され それがエントリポイントになるように書き換えられますが このファイルは使いません必要な情報は asset-manifest.json に出力されているのでこれを使います
"entrypoints": [
"static/js/runtime-main.ccecf24a.js",
"static/js/2.02139942.chunk.js",
"static/js/main.8aabd300.chunk.js"
]
こういう部分があるので この entrypoints の .js ファイルをすべて埋め込んだ HTML ファイルを用意します
<!doctype html>
<script src="/static/js/runtime-main.ccecf24a.js"></script>
<script src="/static/js/2.02139942.chunk.js"></script>
<script src="/static/js/main.8aabd300.chunk.js"></script>
この HTML ファイルを各ページにアクセスしてきたときに返せば React で作ったページが見れます
あとは JSON 埋め込み部分を作って サーバ側の処理でページを表示するための情報を埋め込みます
<!doctype html>
<script type="application/json" id="data">
{{JSON}}
</script>
<script src="/static/js/runtime-main.ccecf24a.js"></script>
<script src="/static/js/2.02139942.chunk.js"></script>
<script src="/static/js/main.8aabd300.chunk.js"></script>
サーバ側
const renderPage = (obj) => {
return template_html.replace("{{JSON}}", JSON.stringify(obj))
}
router.get("/foo", () => {
return renderPage(getPageValue())
})
router.get("/bar", () => {
return renderPage(getPageValue())
})
// ...
クライアントサイドの index.js に書く switch 部分が面倒ですが 思ったよりは簡単にできました