フロントエンドフレームワークはやめたい?でもLive ReloadもTailwindもリアクティビティも諦めたくない?しょうがないにゃあ
TL;DR
Go / Echo + Templ / TailwindCSS w/ Hot&Live Reload
はじまりは突然になの
フロントエンドエンジニアの皆様ご機嫌麗しゅう。個人開発していたCharaXivというサービスをSvelteKitで開発して「Svelteちゅき♡」なんて言ってたらSvelte 5がすべてを台無しにして悲しみに打ち震えていることねです。フレームワークの進化については色々な意見がありますがGo言語もといRob Pike大好き人間であるところの私のスタンスとして「後方互換だけは破壊してくれるなよ」というのが正直な感想です。というわけでSvelteさんと決別し新たなフロントエンドのフレームワークと再婚しようと画策していたんですがしばらく追わない間に想像以上に群雄割拠の状況になっていました。
今からSSRできるオールインワンのフレームワークを、となると強いのは以下でしょうか。
- Next.js
- Nuxt.js
- QwikCity
- SolidStart
ただ個人的にこの辺は色々と思うところがあるのが正直なところです。Next.jsはVercelの匂いがするし、Vue.jsは script setup
したコンポーネントを参照で辿れないし、QwikCityはSvelteよろしくどんでん返しされるのが怖いし、SolidStartは3年くらい前に触ったときに型の付け方がいまいちしっくり来なかったんですよね。
そこで私は当然Honoにも気を引かれるわけですが、2025-04-09現在HonoXは明確にアルファだと銘打ってあるので手を出すのを躊躇ってます。素のHonoを使うでもいいんですがそれならちょっと大好きなGoをワンスモアという気持ちがどうしても拭えませんでした。もちろんJSXをテンプレートエンジンにもIslandとしても使えるHonoの方が圧倒的に開発の上では有利なことはわかってます。でもポエム書いちゃうくらい好きなんだもん、Go。
というわけで一旦Goでどこまでできるんだろうというのをリミットテストしてみることにしました。
それでも捨てたくなかったもの
Hot ReloadとLive Reload
とはいえJSコミュニティが築き上げてきた膨大な技術的資産に頼れないというのはつらいものです。今となってはアプリケーションのHot Reload(開発サーバーの自動再起動)もLive Reload(ブラウザ上のアプリケーション再描画)もどんなフレームワークでも当たり前に与えられる時代です。Lifestyle CreepならぬDeveloper Experience Creepでしょうか。怖いですね。
Hot ReloadはAirがなんとかしてくれる(といっても私の理解が間違っていなければwatchして再ビルドしてるだけではありますが)ところですがLive Reloadはどうしても自前で仕組みを作らなければなりません。浅い理解ですがJS系のフレームワークではサーバとブラウザをWebSocketで繋いでコンポーネントの再マウントだったりブラウザのリロードを発火しているだけのはず(なんて言ってますが前者は超難しいことは想像に難くないです)なので要は更新タイミングでブラウザにイベントを飛ばすWebSocketのサーバーサイド実装とイベントが飛んできたらページリロードをかけるクライアントサイド実装を書けばいいはず。そしてその程度のことはLLMがチョチョイとやってくれる。いい時代になったものです。
サーバー実装
クライアント実装
JavaScriptのdedupe
JS系フレームワークのいいところはやはり配信するJSを最適化できるところにあると思います。例えばJSXなどで書かれたコンポーネントのロジックはコンポーネントを描画するたびに増えるなんてことはありませんがよくあるバックエンドのテンプレートエンジンを使って例えばボタン要素に動的な振る舞いをつけるためにscriptタグを埋め込もうものならコンポーネントを使い回すたびに複製されていきます。それでもReactのランタイムコードを全部読ませるよりも軽いなんてことはありそうで怖いのが現代Webフレームワークですが私が最終的に実装したいCharaXivは数百レベルでボタンを生やすのでさすがにこのオーバーヘッドは看過できないわけです。ボタンに割り当てるロジックがシンプルならAlpine.jsなりHTMXなりを使ってやればいいんですがちょっと複雑な関数を挟みたいとか言い出すとコンポーネントとは別の場所にJSを書いて読み込ませるみたいなことをする必要がでてきます。
そんな私を救ってくれたのがtemplのOnceHandlerで、これは参考実装には含んでいませんが指定したマークアップは何度テンプレート上で登場しても一度しかレンダリングされないというすぐれものです。これを使えば堂々とコンポーネントローカルなロジックを書けると思うともうそれだけでワクワクが止まらないですね。うっかりコンポーネント単位の状態をここに書かないように注意する必要はありますがそこはAlpine.jsを使うから問題ないやろみたいな気持ちでいます。この見立て、甘い気がするけど今は一旦ええやろ。
TailwindCSS
これまでの技術的ハードルに比べればTailwindCSSの導入なんてのは朝飯前ですね。面倒だとすればサーバ起動と並列してCSSの生成コマンドを走らせる必要があるくらいのものですがそこはGo先輩が得意な領域なのでタスクランナーをちょちょっとAIに書かせてやれば解決します。なお go-task なるものがあることをあとから知ったんですが9割くらい書き終わってからだったので泣きました。というかGoのツールはもうちょっとググラビリティよくしたほうが良くない?
で、どうだったの?
結論から言うと一応求めていたものはできました。hello.templ
に書いてあるTailwindのクラスを変更すると自動的にTailwindのコンパイルとtemplのコード生成が発火して、それを検知してAirがメインのサーバプロセスを再起動して、そこからWebSocket通信用に立てたEchoのサーバに通知が飛んでそこから変更がブラウザにpush通知されて更新がかかります。完全にピタゴラ装置なので変更→自動更新までに2~3秒程度かかるんですがブラウザとシェルを行ったり来たりしてCmd+Rを連打するのに比べれば圧倒的に体験はいいです。
あとはサーバーサイドのGoとクライアントサイドのJSで二度同じロジックを書くことになる可能性に対する気持ちの問題だったり、Alpine.jsやHTMX自体と向き合う必要性だったりが残っていますが一旦は満足の行く環境が構築できました。JS/TSを書く量が1バイトでも少ないほうがいいという方にとっては嬉しいスタックなのではないでしょうか。何よりいわゆる「Magic」的な要素が少なく一つ一つの構成要素が数年後にも割と安定していそうなところが個人的には精神安定剤になりそうな気がしています。Goにはsqlcのような優秀なSQLからのコード生成ツールもあれば、パフォーマンス的にも有利ですし、何よりもエコシステムの安定性が高いことが個人的には魅力です。
さああとは作るだけだ...... ってそれが一番大変なんですけどね。この記事と参考実装が誰かのお役に立てたのなら幸いです。
Discussion