はじめに
htmx以外は全て初めて触る技術だったので、入門ということでTODOアプリを作ってみました。
Demo
使用した技術スタック
htmx
htmxに関しては、以前書いた記事に詳しく書いております。
もうjsなんていらない!世界で流行っているHTMXについてまとめてみた #JavaScript - Qiita
Hono
あらゆるランタイム上で動作する軽くて速いWebフレームワーク。
Bun
Zigで書かれた高速なJavaScriptランタイム。
Pico CSS
最小限のものだけが入ったシンプルなCSSフレームワーク
動作方法
以下からクローンしてください。(成果物のリポジトリ)
git clone https://github.com/tomo1227/todo-bun-hono-htmx-pico.git
Dev-Containerが使える方はコンテナを立ち上げて、bun run dev
すれ
bunをインストールしていない人は https://bun.sh/docs/installation からインストールして、以下のコマンドを叩いてください。
bun i
bun run dev
すると,http://localhost:3000/
にアクセスできるようになります。
実装の紹介
今回は、サンプルアプリなので、Pico CSSやhtmxはCDNで導入しています。本番環境などでは、非推奨なので気をつけてください。
index.tsx
は以下のように、割とシンプルに書くことがで来ました。
パッとみてFastAPIと似てる気がしました。
import { Hono } from "hono";
import "typed-htmx";
import TodoApp from "./components/TodoApp";
import Task from "./components/Task";
import Todo from "./components/Todo";
const app = new Hono();
let todos: Todo[] = [];
app.get("/", (c) => {
return c.html(<TodoApp />);
});
app.post("/todo", async (c) => {
const formData = await c.req.parseBody();
const newId =
todos.length > 0 ? Math.max(...todos.map((todo) => todo.id)) + 1 : 1;
const newTodo = {
id: newId,
text: formData.todo as string,
};
todos.push(newTodo);
return c.html(<Task todos={todos} />);
});
app.delete("/delete/:id", (c) => {
const id: number = parseInt(c.req.param().id, 10);
todos = todos.filter((todo) => todo.id !== id);
return c.html(<Task todos={todos} />);
});
export default app;
htmxはformのところで多く使用した。できるだけjavascriptを書かずに、HTMLの部分だけで行うことを目指しました。
<main class="container">
<h1 class="pico-color-pink-500">Welcome!</h1>
<h1>Hono & Bun & htmx & Pico CSS Todo App</h1>
<form
hx-post="/todo"
hx-target="#todos"
{...{ "hx-on::after-request": "this.reset()" }}
>
<input
id="todo"
name="todo"
type="text"
required="true"
placeholder="ここにタスクを入力"
/>
<button type="submit">ADD TASK</button>
</form>
<section id="todos" hx-get="/todo" hx-trigger="load"></section>
</main>
また、タスクを追加すると、以下のようなコンポーネントが作成される。
deleteボタンを押すと、hx-deleteが発火する。対象をsectionで、swap方法を指定すると、削除が簡単にようになった。
import Todo from "./Todo";
interface TaskProps {
todos: Todo[];
}
const Task = ({ todos }: TaskProps) => {
return (
<article class="container">
{todos.map((todo) => (
<section class="grid">
<div id={todo.id.toString()} class="card">
<strong>{todo.text}</strong>
</div>
<div></div>
<button
type="reset"
class="pico-background-red-450"
hx-delete={`/delete/${todo.id}`}
hx-target="closest article"
hx-swap="outerHTML"
>
DELETE
</button>
</section>
))}
</article>
);
};
export default Task;
ハマった箇所
htmxのhx-onでシンタックスエラー
フォーム送信後に入力をリセットするのだが、これをhtmxで書くと以下のようになる。
<form hx-on::after-request="this.reset()" />
しかし、HonoのJSXでは、上記の書き方はシンタックスエラーとなる。
ちなみに、コロンが1つだとエラーにならないが、動作しない。
<form hx-on="htmx:after-request: this.reset()" />
最終的に以下のようにすると動作することがわかった。
<form {...{ 'hx-on::after-request': 'this.reset()' }} />
成果物
今回作ったTodoアプリのリポジトリです。