htmx + Alpine.jsでLess JavaScriptに開発する

Zennに投稿したHTML Drivenな開発の記事のサンプルリポジトリに関する解説・感想です。
経緯やコンセプトについては当該記事を読むことをお勧めします。

全体

今回htmx等を使用してHTML中心にviewを構築するサンプルを2つ作りました。
1つはテンプレートエンジンとしてjsx(React)を使用して、ExpressサーバーでHTMLにレンダーして返すパターンです。
もう1つがAstroを使用するパターンになります。

どちらもサーバーサイドはNode.jsにしてSSRする前提で作っています。
Rails等の他言語のフレームワークに乗せることも出来ますが、実運用を考えるとhtmxがAPIのレスポンスにHTMLを要求する性質上BFFサーバを立ててAPIの結果の描画等はフロントエンドの開発者が責務を持ち、バックエンドのロジック等は切り離した方が良そうなのでこのようにしています。

CSSはTailwindを使用しています。
所謂Atomic CSSの様なものであればコンセプト的にはOKだと思います。

今回以下の機能を実装しました。
特に想定のサービスみたいなものは無くて、よくある実装を詰め込んだ感じです。

  • ユーザー登録
  • ログイン・ログアウト
  • ログイン状態によってリダイレクト処理
  • APIから取得する記事一覧の描画
  • カルーセル
  • モーダル

デプロイしていないのでcloneしてローカルで動かせば色々試せます。

全体の感想

HTML属性を活用するライブラリを使ってHTML中心にフロント部分の実装をするというコンセプトでした。

まず、htmxの使用感は非常に良いものでした。
api通信している事をあまり意識せず、少しのコードだけでリクエストと画面の更新が行えるためかなり便利です。
ただ、どうやらレスポンスが200以外の時は処理が止まってしまったりする様なのでhtmxの挙動をよく理解してAPIを作る必要はあると思います。
BFFサーバーをたててブラウザからのリクエストはそちらに集約してフロント用に最適化するような構成とは相性が良さそうです。
逆にモノリシックな構成の場合バックエンドロジックにviewに関する知識を含めないといけなくなるので、設計的にも運用面でも微妙な気がします。

Alpine.jsに関して手軽で大抵のものは作れそうですが、割と限界もありそうです。
カルーセルの実装部分が分かりやすいですが、ロジックを実装しようとするとJavaScriptのコードを文字列としてHTML属性に渡す必要がありかなり辛いです。
scriptタグなどに書いた関数を関数名だけ渡すことも出来ますが、その場合自前のJavaScriptコードをクライアントバンドルに含める必要がありコンセプトに反してしまいます。

Tailwindに関してはかなりメジャーなので特に言及することもない気がしますが、CDN経由で読み込んだ場合不要なクラスも読み込まれてしまうためマイナスです。
また、Tailwindの良さはtailwind.config.jsによってデザインシステムをフレームワークレベルで強制出来る点だと思っているためその点も活かしづらくなってきます。(一応CDN利用の場合もtheme設定は出来ます)

全体的に、開発体験は悪くないが何を作るかによってかなり評価が分かれそうという印象でした。
それぞれのライブラリの学習コストはそんなに高くないですが、結局「それらを組み合わせてどう実装するのが良いか」まで考えると手さぐりになりますしフロントエンド開発にある程度精通している必要が出てきますから、そんなにハードルは低くない気がします。
とりあえず動くものを作るという意味ではReact等よりはハードルは低いと思いますが。

コンセプトに関する感想はこんなところで、以降は各構成に関して書き散らかしていきます。

jsx + Expressのパターン

こちらは極力ベーシックな形で作ろうとしたパターンです。

まずテンプレートエンジンですが、Node.js用のテンプレートエンジンとしてはpugやejsなど色々あります。
ただ個人的にこれらのテンプレートエンジンはHTMLとは書き方が大きく変わっていたり、テンプレート構文が見づらかったりIDEのシンタックスハイライトが壊れたりとあまりいい思い出が有りませんでした。
色々調べた結果、react-dom/serverモジュールのrenderToStringを使えばサーバー上でjsxをrenderしてHTMLを生成できると分かりました。
ちなみにサーバー用のrender系のAPIは色々あって、Next.jsなんかのフレームワークが内部的に使っています。https://ja.react.dev/reference/react-dom/server

という訳でjsxをNode.jsサーバーでレンダーして返してやろうという事になり、サーバーは特に深く考えずExpressにしました。

感想

まず、やはりjsxはテンプレートエンジンとして恐ろしく優秀です。
コンポーネント化、ループによるリストの描画等の基本的なところはもちろん簡単にできますし何よりTypeScriptとの相性が圧倒的に良いです。正直これだけでjsxから離れられなくなっています。

一方でExpressはただのサーバー構築用のフレームワークなのでルーティングは自前で全部設定する必要がありますし、各リクエストハンドラーで毎回renderToStringする必要があって割と面倒です。
file-based routingとHonoの様なjsxのサポートがあるフレームワークが欲しいところです。

また、これは詳細追ってないのでよく分かっていませんが後述のAstroを使ったときと比べてローカルサーバーのレスポンスが明らかに遅いです。
Expressが遅いせいなのかrenderが重いのかよく分かっていませんが、プロダクトで使うならfastifyやHono等他の選択肢も検討した方が良いかもしれません。

まとめ

  • jsx最高!
  • ルーティングやらをいちいち書くのが面倒
  • パフォーマンス懸念あり

Astroを使うパターン

Astroならテンプレート構文も用意されてるしfile-based routingもあるから良さそうじゃね?
となってAstroでも試してみました。

ちなみにAstroのテンプレート構文はjsxによく似ていますが、実際には少し違うようです。
https://docs.astro.build/en/core-concepts/astro-syntax/

また、Expressのパターンと異なる点としてAstroを使う以上フロント部分も含めたbuildが発生するためAlpine.jsとTailwindはCDNを使用せずAstroのintegration経由で導入しています。
htmxはintegrationが見つからなかったためとりあえずCDNを使っています。

感想

やはりAstroが提供している機能に乗っかるだけで良いので色々楽です。
ハイパフォーマンスを謳っているだけあって少なくともローカルでの動作は非常に高速でした。

一方で、純粋なts・tsxでは無いため型周りが若干微妙な感じになります。
コンポーネントのpropsの型制約が良い感じに反映されない気がする。

また、cookie操作やリダイレクト等にAstroのAPIを使用するのですが、基本的にグローバルなAstroオブジェクトにアクセスする形になるのがあんまり好きじゃなかったです。
全体的にAstroの独自ルールを把握するのに結構時間がかかりました。
もうちょっとExpressにルーティング周りとテンプレート構文を追加しただけ、みたいな薄めのフレームワークが欲しいところです。

あとはAstro自体が基本的には静的サイトジェネレーターであるため、SSRする場合はホスト先やデプロイ方法を検討する必要があります。
一応adapterとして@astrojs/nodeを使えば大抵は何とかなりそうですがそもそもSSR前提のフレームワークでは無いため、SSG的な構成の際の選択肢として考えておいた方が良さそうです。

まとめ。

  • 設定少なくて楽!
  • 型周りがやや微妙
  • 色々やろうとすると覚える事が多い
  • SSR向きじゃない
shin-taroのプロフィール画像

shin-taro

フロントエンドをやっています。 このブログは気ままに書いているので更新頻度は疎らです。