HonoでAPI付き雑React SPA最小
laisoさんが書いてたのほぼなんだけど
Honoは文字列でもStreamでもなんでも返せるからサーバーサイドもReactで書けるし、tsconfig.json
で適切に設定すればJSXなんでもいけるし、Viteのdev-serverがあるから、サーバーもクライントも同時に開発、ビルドできて、もちろんAPIを生やすのが得意で、雑React SPA環境(API付き!)作るのに向いてるよ。
作り方解説します。めんどい人はここにプロジェクト作ってるからclone、ダウンロードしてください。
まず、create-honoして、cloudflare-pages
のテンプレートを選ぶ。bun
をパッケージマネージャーに使ってる。
bun create hono my-app
React関連の依存を入れる。
bun add react react-dom
bun add -D @types/react @types/react-dom
適当にReact用にファイルを書き換える。まずはTSの設定。ポイントはjsxImportSource
でhono/jsx
だったのをreact
にしてる件。これでプロジェクトで書いたJSXはReactになりまーす。
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"lib": [
"ESNext",
"DOM",
"DOM.Iterable"
],
"types": [
"@cloudflare/workers-types",
"vite/client"
],
"jsx": "react-jsx",
"jsxImportSource": "react"
},
}
次にvite.config.ts
。おまじないだと思って、ssr.external
でReactのライブラリを指定しましょう。
import pages from '@hono/vite-cloudflare-pages'
import devServer from '@hono/vite-dev-server'
import { defineConfig } from 'vite'
export default defineConfig(({ mode }) => {
if (mode === 'client') {
return {
build: {
rollupOptions: {
input: './src/client.tsx',
output: {
entryFileNames: 'static/client.js'
}
}
}
}
} else {
return {
ssr: {
external: ['react', 'react-dom']
},
plugins: [
pages(),
devServer({
entry: 'src/index.tsx'
})
]
}
}
})
これで設定は終わり。laisoさんが書いてたコードがそのまま動きます。ちょっとコピペさせてもらうと(問題あったら教えて下さい)...
クライントはそのまま。
import { createRoot } from 'react-dom/client'
import { useState } from 'react'
function App() {
return (
<>
<h1>Hello, Hono with React!</h1>
<h2>Example of useState()</h2>
<Counter />
<h2>Example of API fetch()</h2>
<ClockButton />
</>
)
}
function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>You clicked me {count} times</button>
}
const ClockButton = () => {
const [response, setResponse] = useState<string | null>(null)
const handleClick = async () => {
const response = await fetch('/api/clock')
const data = await response.json()
const headers = Array.from(response.headers.entries()).reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
const fullResponse = {
url: response.url,
status: response.status,
headers,
body: data
}
setResponse(JSON.stringify(fullResponse, null, 2))
}
return (
<div>
<button onClick={handleClick}>Get Server Time</button>
{response && <pre>{response}</pre>}
</div>
)
}
const domNode = document.getElementById('root')!
const root = createRoot(domNode)
root.render(<App />)
ふつーのReact。サーバー側は、renderToString()
を使うのがポイント。
import { Hono } from 'hono'
import { renderToString } from 'react-dom/server'
const app = new Hono()
app.get('/api/clock', (c) => {
return c.json({
time: new Date().toLocaleTimeString()
})
})
app.get('*', (c) => {
return c.html(
renderToString(
<html>
<head>
<meta charSet="utf-8" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css" />
{import.meta.env.PROD ? (
<script type="module" src="/static/client.js"></script>
</>
) : (
<script type="module" src="/src/client.tsx"></script>
</>
)}
</head>
<body>
<div id="root"></div>
</body>
</html>
)
)
})
export default app
あとはbun run dev
つまり、
vite dev
で開発できます。
ビルドしたい時はクライアントを先にしないと、dist/_worker.js
が消えるので、ちょっとコツがいるんですが、これでいいです。
vite build --mode client && vite build
あとはCloudflare PagesへのDeployも実際のコマンドはこれでいける。
wrangler pages deploy dist
KOREDAKE!
ファイル構造もめっちゃシンプル。
.
├── bun.lockb
├── package.json
├── public
│ └── static
│ └── style.css
├── src
│ ├── client.tsx
│ └── index.tsx
├── tsconfig.json
└── vite.config.ts
クライアントのルーターのことは何も書いてないんだけどそれはよしなに。
以上、API付きの雑React SPA環境でした。
Discussion
Cloudflareについて勉強中なのですが、deploy後の
/api/clock
が出力したログを見ることはできるでしょうか?Workerであればコンソールやwrangler tailで見れることは分かったのですが、この記事の形式だとどうなるのかと思い。とりあえず下記で見たいものは見られました。
index.tsxのフラグメントが閉じていない箇所があるみたいです。