HonoでReact SPA最小をBunに対応させる
Iaiso さんから端を発して Hono 作者の yusukebe さんが記事を書いていたので、そのリポジトリを元にして Bun.sh 対応を試みました
この記事の発端となったお二人の記事
これらの記事は Cloudflare Pages へのデプロイが前提となっていますが、今回はどのデプロイ先でも行けるような構成にしてみました
始め方
始め方は yusukebe さんの記事「HonoでAPI付き雑React SPA最小」 の記事が Bun をパッケージマネージャーとして使っているため、ほぼ流用できます。そのためここでは割愛します
Bun 上で実行するための差分
以下のコミットを参照してもらえると大枠把握できると思います
package.json
"scripts": {
- "dev": "vite",
- "build": "vite build --mode client && vite build",
- "preview": "wrangler pages dev dist",
- "deploy": "$npm_execpath run build && wrangler pages deploy dist"
+ "dev": "bunx vite",
+ "build": "bun run build:client #TODO: && bun run build:server",
+ "build:client": "bun --bun x vite build --mode client",
+ "build:server": "bun --bun x vite build",
+ "serve": "NODE_ENV=production bun --bun run bin/serve.ts"
.
.
.
"devDependencies": {
- "@cloudflare/workers-types": "^4.20240208.0",
- "@hono/vite-cloudflare-pages": "^0.2.4",
"@hono/vite-dev-server": "^0.8.0",
+ "@types/bun": "^1.0.8",
"@types/react": "^18.2.60",
"@types/react-dom": "^18.2.19",
"vite": "^5.0.12",
- "wrangler": "^3.25.0"
ビルドはクライアントだけで、サーバーサイドは Bun でそのまま実行する形にしています。Bun は TypeScript ファイルを直接実行でき、そのオーバーヘッドはわずかであると公式ドキュメントには記されています。
Is transpiling still necessary? — Because Bun can directly execute TypeScript, you may not need to transpile your TypeScript to run in production. Bun internally transpiles every file it executes (both .js and .ts), so the additional overhead of directly executing your .ts/.tsx source files is negligible.
そのため、今回は TypeScript ファイルを直接実行する形を取りました
bun dev
に --bun
オプションを付けていないのは、現在まだ対応していないためです
Yeah. It does not support running on Bun. It may run on Bun in the future, but for now, please run Vite on Node.js.
vite.config.ts
(多いので一部の差分のみ。全ての差分はコミットを参照してください)
-import pages from '@hono/vite-cloudflare-pages'
.
.
export default defineConfig(({ mode }) => {
if (mode === 'client') {
.
.
} else {
return {
.
.
plugins: [
- pages(),
devServer({
- entry: 'src/index.tsx'
+ entry: 'src/server.tsx'
})
]
主な変更点は、Hono 公式プラグインの @hono/vite-cloudflare-pages による設定を展開しつつも、Cloudflare 文脈のものを変更しました
本当のところを言うと、Bun で TypeScript を直接実行する今回の要件では、Vite は開発サーバー devServer
の設定と mode === 'client'
の設定だけで十分です
(そのため、差分は page()
の削除だけで良い)
ただ個人的な今後の課題として、Vite でサーバーもビルドしたい欲があるため、今回は設定を盛り込んだままにしました(つまり未完成の差分)
src/index.tsx
-app.get('*', (c) => {
+app.get('/', (c) => {
+ const isProduction =
+ process.env.NODE_ENV === "production" || import.meta.env.PROD;
.
.
.
- {import.meta.env.PROD ? (
+ {isProduction ? (
<script type="module" src="/static/client.js"></script>
) : (
<script type="module" src="/src/client.tsx"></script>
)}
本番環境のアセット配信のため、ルーティングを *
からルートパス /
へ変更しました。
今回の実装ではサーバー側が vite build
を経由していないため、import.meta.env.PROD
などを例とする Vite の恩恵を享受できません。そのため、src/server.ts
(yusukebe さんによる元リポジトリでは src/index.tsx
) の当該部分を、process.env.NODE_ENV === "production" || import.meta.env.PROD
と緩める形を取りました
bin/serve.ts
import { serve } from "bun";
import { serveStatic } from "hono/bun";
import app from "../src/server";
const port = process.env.PORT || 3000;
app.use("/static/client.js", serveStatic({ root: "dist" }));
app.use("/static/*", serveStatic({ root: "public" }));
console.log(`Listening on http://localhost:${port}`);
serve({
port,
fetch: app.fetch
})
本番環境用の起動スクリプトです。開発環境はサーバーも Vite で動かしたいため src/server.tsx
で export default app (Hono インスタンス)
するようにして、本番環境では別途スクリプトを用意するようにしました
ご覧の通り、アセット配信の設定が開発環境と若干異なるため、その差分を吸収する役割も担っています
まとめ
ファイル構造はまだシンプルですね!
.
├── README.md
├── bin
│ └── serve.ts
├── package.json
├── public
│ └── static
│ └── style.css
├── src
│ ├── client.tsx
│ └── server.tsx
├── tsconfig.json
└── vite.config.ts
ということで Hono の React SPA を Bun で動かすための構成でした!
Hono は Bun との相性が良いように感じているので、引き続き気付いた点があったらまとめていきます💪
Discussion