この記事は以前7月に自分で書いた「Hono + htmx + Cloudflare is a new stack」という記事を一部修正し、訳したものです。
https://blog.yusu.ke/hono-htmx-cloudflare/
以前、バックエンドエンジニアだった身からすれば、Reactは複雑だと感じることがあります。さらに(私はフレームワーク開発者なのですが)フレームワーク開発者にとってはハイドレーションの仕組みを作ることは厄介です。しかし、しばしばReactを使うことになります。
Reactの優位な点の一つは「JSX」です。最初見た時、JSXは奇妙に思えました。「なんでJavaScriptの中にHTMLのタグが入っているんだ!」。しかし、一度慣れると、JSXは柔軟で、書きやすいことに気づきました。
今日はこれから、JSXをサーバーサイドのテンプレートとして使う技術スタックを紹介します。これはつまりReactなしでJSXを使うということです。
Hono - エッジのためのJavaScriptのWebフレームワーク - はJSXの機能を有しています。クライアントではなくサーバーサイドのHTMLをJSXで書くことができます。HandlebarsやEJS、mustacheのようなテンプレートエンジンのように振る舞います。
const app=newHono()app.get('/',(c)=>{return c.html(<h1>Hello!</h1>)})HonoのアプリケーションはCloudflare WorkersやFastly Compute@Edge、Deno Deployなどのエッジサーバーで動きます。それは実に高速なサーバーサイドレンダリングをもたらします。さらに言えば、JavaScriptの「ハイドレーション」を行わず、SPA遷移なしで、ユーザー体験を損なわないのです。このエッジベースのSSRとハイドレートしない組み合わせはとても速いセットアップを可能にします。
htmxはJavaScriptを自分で書かずともAjaxを可能にするライブラリです。
<!-- have a button POST a click via AJAX --><buttonhx-post="/clicked"hx-swap="outerHTML"> Click Me</button>Ruby on Railsで使われているHotwireと似ています。htmxはReactをREST APIとともに使うのと違い、サーバーサイドのJSXと簡単に統合でき、インタラクティブな体験をシンプルにつくることができます。
スタックに含まれる全部のコンポーネントは以下です。
Cloudflare D1はSQLiteをCloudflareのエッジで動かすデータベースサービスです。現在は「ベータ」のステータスでプロダクションでの使用は推奨されていまんが、とても速く、PoCのためのプロジェクトには完璧に向いています。
これから紹介する例では、Zodを値の検証に使っています。HonoのZod ValidatorミドルウェアはHonoと統合されいてるためとても使いやすく、検証した値の型を簡単に取得することができます。

これはすごいです。エッジ上のD1データベースに実データを入れて削除するTODOリストのアプリケーションをたった100行(程度)で書くことできたのです!それはとても速く(100ms以内)とても小さいです(Gzippedで22KB)!
デモ

バンドルサイズ

いつもサンプルのコードを紹介する際、特徴的な行を選んでペーストします。しかし、このサンプルコードはたった100行程度なので、これから全部のコードをお見せしましょう。
import{ html}from'hono/html'import{ jsxRenderer}from'hono/jsx-renderer'exportconst renderer=jsxRenderer(({ children})=>{return html`<!DOCTYPEhtml><html><head><metaname="viewport"content="width=device-width, initial-scale=1.0"/><scriptsrc="https://unpkg.com/htmx.org@1.9.3"></script><scriptsrc="https://unpkg.com/hyperscript.org@0.9.9"></script><scriptsrc="https://cdn.tailwindcss.com"></script><title>Hono + htmx</title></head><body><divclass="p-4"><h1class="text-4xl font-bold mb-4"><ahref="/">Todo</a></h1> ${children}</div></body></html>`})exportconstAddTodo=()=>(<formhx-post="/todo"hx-target="#todo"hx-swap="beforebegin"_="on htmx:afterRequest reset() me"class="mb-4"><divclass="mb-2"><inputname="title"type="text"class="bg-gray-50 border border-gray-300 text-gray-900 rounded-lg p-2.5"/></div><buttonclass="text-white bg-blue-700 hover:bg-blue-800 rounded-lg px-5 py-2 text-center"type="submit"> Submit</button></form>)exportconstItem=({ title, id}:{ title:string; id:string})=>(<phx-delete={`/todo/${id}`}hx-swap="outerHTML"class="flex row items-center justify-between py-1 px-4 my-1 rounded-lg text-lg border bg-gray-100 text-gray-600 mb-2">{title}<buttonclass="font-medium">Delete</button></p>)import{Hono}from'hono'import{ z}from'zod'import{ zValidator}from'@hono/zod-validator'import{ renderer,AddTodo,Item}from'./components'typeBindings={DB:D1Database}typeTodo={ title:string id:string}const app=newHono<{ Bindings: Bindings}>()app.get('*', renderer)app.get('/',async(c)=>{const{ results}=await c.env.DB.prepare(`SELECT id, title FROM todo;`).all<Todo>()const todos= resultsreturn c.render(<div><AddTodo/>{todos.map((todo)=>{return<Itemtitle={todo.title}id={todo.id}/>})}<divid="todo"></div></div>)})app.post('/todo',zValidator('form', z.object({ title: z.string().min(1)})),async(c)=>{const{ title}= c.req.valid('form')const id= crypto.randomUUID()await c.env.DB.prepare(`INSERT INTO todo(id, title) VALUES(?, ?);`).bind(id, title).run()return c.html(<Itemtitle={title}id={id}/>)})app.delete('/todo/:id',async(c)=>{const id= c.req.param('id')await c.env.DB.prepare(`DELETE FROM todo WHERE id = ?;`).bind(id).run() c.status(200)return c.body(null)})exportdefault appエレガントでしょ?
全体のコードは以下にあります。
https://github.com/yusukebe/hono-htmx
もしかして、あなたはこう思うかもしれません。
それってPHPの話?
そうすると私はこう答えるでしょう。
いいえ違います。でもとても似ています!
これは非常にPHPっぽいです、もしくはRuby on Rails。でも私はPHPは好きだし、このスタックには以下の良い点があります。
冒頭で述べた通り、私はバックエンドエンジニアだったので、このウェブサイトをつくるアプローチは私にとって馴染みがあり、快適です。シンプルでクリーンなのです。
このスタックをより発展させるために必要なことがいくつかあります。ひとつはファイルベースのルーティングです。また、HonoのJSXを使うのがベストのアプローチではないかもしれません。PreactやReactをサーバーサイドで使う選択肢もあります。
どちらにせよ、このスタックはノスタルジックであり新しい心地もします。あ、ひとつ忘れてることがありました。このスタックの名前はどうしましょか!
バッジを受け取った著者にはZennから現金やAmazonギフトカードが還元されます。
