HTMXからDatastarへ移行した理由
(everydaysuperpowers.dev)- HTMX を使うことでコード量を約70%削減できた一方、UI間の同期の問題に加え、フロントエンドの状態管理の複雑さが増した
- Datastar 導入後は、リアルタイムのマルチユーザーアプリ開発において WebSockets なしで簡潔なコード構成と保守のしやすさ を実現できた
- HTMX が HTML属性を中心に動作ロジックを分散 させるのに対し、Datastar は サーバー主導の更新モデル によりロジックの 一貫性と保守性 を高める
- Datastar API は属性が少なく、コードの 可読性と生産性 が向上すると感じた
- Datastar は Server-Sent Events(SSE)、Web Components、CSS View Transitions などの Webネイティブ技術を積極的に活用 し、リアルタイム協調と再利用可能なコンポーネント構造を可能にする
紹介と動機
- 2022年、David Guillot が DjangoCon Europe で、ReactベースのSaaSを HTMX に移行してコード量を約70%削減し、機能改善も達成した事例を共有した
- その後、多くのチームが単一ページアプリ(SPA)からマルチページの ハイパーメディアアプリ へ移行する中で、コード削減と 開発者体験・ユーザー体験 の両方の向上を経験した
- 筆者自身も HTMX から Datastar へプロジェクトを移行し、コードがさらに短くなり、WebSocket や複雑な状態管理なしに リアルタイムのマルチユーザーアプリ を開発できることを確認した
移行のきっかけになった問題点
- FlaskCon 2025 発表の準備中に、HTMX と AlpineJS を組み合わせて UI を同期しようとしたが、UI 同期の問題に直面した
- 両ライブラリは異なる開発者が作った別個のツールであり、相互通信できない ため、開発者が自分で統合作業を担う必要があった
- さまざまなタイミングでコンポーネントを初期化し、イベントを調整する過程で、想定以上のコード記述とデバッグ時間を要した
- Datastar が2つのライブラリの機能を統合しつつ、11KB未満のサイズ で提供されている点に注目して試してみた
- モバイル端末ユーザーのページ読み込み性能の改善にも有利
より優れた Datastar の API 設計
- Datastar の API は HTMX より はるかに軽量な印象 があり、望む結果を得るために追加すべき属性(attribute)の数が少ない
- HTMX はほとんどの相互作用で複数の属性を必要とする
- URL 定義、ターゲット要素の指定、応答の処理方法などをそれぞれ別の属性で設定する
- 通常は 2〜3個の属性 を毎回使い、時には継承チェーンをたどって属性の動作方式を確認しなければならない
<a hx-target="#rebuild-bundle-status-button" hx-select="#rebuild-bundle-status-button" hx-swap="outerHTML" hx-trigger="click" hx-get="/rebuild/status-button"></a> - Datastar は通常、たった1つの属性 で同じ機能を実装できる
<a data-on-click="@get('/rebuild/status-button')"></a>- 数か月後にコードを見直しても、動作方式を簡単に理解できる
動作原理の違い
- HTMX はフロントエンドライブラリ として HTML 仕様の拡張を目指す一方、Datastar はサーバー主導ライブラリ として高性能な Web ネイティブのリアルタイム更新アプリケーション構築を目指す
- HTMX はリクエストを発火する要素に属性を追加して動作を定義するため、ページ内の離れた要素を更新する場合でもロジックが複数レイヤーに分散する
- Datastar は サーバーが何を変更するかを決定 し、すべての更新ロジックを1か所に集中させる
-
HTMX の例
<div> <div id="alert"></div> <button hx-get="/info" hx-select="#info-details" hx-swap="outerHTML" hx-select-oob="#alert"> Get Info! </button> </div>- ボタンを押すと
/infoに GET リクエストを送り、応答内のinfo-detailsID 要素でボタンを置き換え、さらに応答内のalertID 要素でページ上の同じ ID の要素を置き換える - ボタン要素が知っておくべき情報が多すぎるうえ、サーバーが返す内容を事前に知る必要があるため、HTMX の「行動の局所性(locality of behavior)」 の原則が弱まる
- ボタンを押すと
-
Datastar の改善されたアプローチ
<div> <div id="alert"></div> <button id="info-details" data-on-click="@get('/info')"> Get Info! </button> </div>- サーバーは同じ ID を持つ2つのルート要素を含む HTML 文字列を返す
<p id="info-details">These are the details you are looking for…</p> <div id="alert">Alert! This is a test.</div> - シンプルで高性能な選択肢である
- サーバーは同じ ID を持つ2つのルート要素を含む HTML 文字列を返す
コンポーネントレベルで考える
- より良いアプローチは、HTML を コンポーネントとして扱う こと
- そのコンポーネントの本質を把握する
- ユーザーが特定項目の追加情報を得る方法
- ユーザーがボタンをクリックすると情報が表示されるか、情報がなければエラーがレンダリングされ、どちらの場合でもコンポーネントは静的状態になる
-
状態ごとにコンポーネントを分離
- プレースホルダー状態:
<!-- info-component-placeholder.html --> <div id="info-component"> <button data-on-click="@get('/product/{{product.id}}/info')"> Get Info! </button> </div> - 情報表示状態:
<!-- info-component-get.html --> <div id="info-component"> {% if alert %}<div id="alert">{{ alert }}</div>{% endif %} <p>{{product.additional_information}}</p> </div> - サーバーが HTML をレンダリングすると、Datastar がページを自動更新する
- コンポーネントレベルで考えることで、誤った状態に入ったりユーザー状態を失ったりすることを防げる
- プレースホルダー状態:
複数コンポーネントの同時更新
- David Guillot の発表で印象的だったのは、アプリがお気に入り件数を更新する際、変更されたコンポーネントだけでなく、そこから大きく離れたカウント要素も一緒に更新していたことだ
- HTMX では JavaScript イベントを発火させ、それが再びリモートコンポーネントへの GET リクエスト発行をトリガーする
- Datastar では 同期関数内でも複数コンポーネントを同時に更新 できる
-
ショッピングカートの例
- カート追加コンポーネント:
<form id="purchase-item" data-on-submit="@post('/add-item', {contentType: 'form'})">" > <input type=hidden name="cart-id" value="{{cart.id}}"> <input type=hidden name="item-id" value="{{item.id}}"> <fieldset> <button data-on-click="$quantity -= 1">-</button> <label>Quantity <input name=quantity type=number data-bind-quantity value=1> </label> <button data-on-click="$quantity += 1">+</button> </fieldset> <button type=submit>Add to cart</button> {% if msg %} <p class=message>{{msg}}</p> {% endif %} </form> - カート件数表示コンポーネント:
<div id="cart-count"> <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"> <use href="#shoppingCart"> </svg> {{count}} </div> - Django で2つのコンポーネントを同じリクエストで更新:
from datastar_py.consts import ElementPatchMode from datastar_py.django import ( DatastarResponse, ServerSentEventGenerator as SSE, ) def add_item(request): # 重要な状態更新は省略 return DatastarResponse([ SSE.patch_elements( render_to_string('purchase-item.html', context=dict(cart=cart, item=item, msg='Item added!')) ), SSE.patch_elements( render_to_string('cart-count.html', context=dict(count=item_count)) ), ])
- カート追加コンポーネント:
Webネイティブ哲学
- Datastar Discord コミュニティを通じて、Datastar が単なるヘルパースクリプトではなく、Web の基本プリミティブを活用してアプリを構築する哲学 であると理解した
- HTMX が HTML 仕様を発展させようとする一方で、Datastar は Webネイティブ機能の採用促進 により関心を持っている
- CSS view transitions
- Server-Sent Events
- Web Components など
- 複雑な AlpineJS コンポーネントをリファクタリングして、シンプルな Web Components を抽出 し、複数箇所で再利用する大きな成果を得た
- React のようなツールがなくても、カスタム HTML 要素の作成 によって高い行動の局所性と再利用性を実現できる優れたパターンだ
マルチユーザーアプリのためのリアルタイム更新
- 協調機能を第一級の機能として備えたアプリは他と差別化され、Datastar はこの課題を解決する
- ほとんどの HTMX 開発者は ポーリング方式 でサーバーから情報を取得するか、カスタム WebSocket コード を書いて複雑さを増やしている
- Datastar は Server-Sent Events(SSE) というシンプルな Web 技術を使い、サーバーが接続されたクライアントに更新を「プッシュ」する
- ユーザーがコメントを追加したり状態が変わったりすると、サーバーが即座にブラウザを更新し、追加コードは最小限で済む
- カスタム JavaScript なしでも、リアルタイムダッシュボード、管理パネル、協調ツールを構築できる
- クライアント接続が切れた場合でも、ブラウザが 自動的に再接続を試みる ため、追加コードは不要
- サーバーに「最後に受信したイベント」を伝えることもできる
過剰な複雑さを避ける
- Datastar Discord コミュニティは、Web アプリ制作に対する Datastar のビジョンを理解する助けになった
- プッシュベースの UI 更新
- 複雑性の低減
- Web Components のようなツールを活用したローカルな複雑状況の処理
- コミュニティは、新規ユーザーが過度に複雑なアプローチをしていることに気づかせてくれる
主なヒント
- コンポーネント全体を再レンダリング して送ることを恐れないこと
- そのほうが簡単で、性能への影響も大きくない
- より良い圧縮率が得られ、ブラウザによる HTML 文字列のパースも非常に高速
- サーバーが真実の状態 であり、ブラウザより強力である
- 状態の大半はサーバーに処理させるべきで、思っているほどリアクティブシグナルは必要ないかもしれない
- Web Components は、高い行動の局所性を持つカスタム要素にロジックをカプセル化するのに優れている
- Datastar の Web サイト ヘッダーの星空アニメーションが良い例
<ds-starfield>要素が星空アニメーションのすべてのコードをカプセル化し、内部状態を変更する3つの属性を公開している- Datastar は範囲入力が変化したり、マウスが要素上を動いたりしたときにその属性を駆動する
限界を超える可能性
- Datastar が可能にする潜在力こそが最も興味深い
- コミュニティは、他のツールを使う開発者が経験する限界をはるかに超えるプロジェクトを定期的に生み出している
注目すべき事例
- サンプルページの データベース監視デモ
- Hypermedia を活用し、JavaScript カンファレンスで発表されたデモの速度とメモリ使用量を大幅に改善した
- Anders Murphy の 10億個のチェックボックス
- 100万個のチェックボックス実験がサーバー容量を超えたため、Datastar を使って安価なサーバー上で10億個を実現した
- 米国中のすべてのレーダーステーションデータを表示する Web アプリ
- レーダー信号が変化すると、UI 上の該当ポイントが 100ミリ秒以内 に変化する
- 毎秒80万件以上のポイントが更新 され、ユーザーは最大1時間前までスクラブ可能(遅延は700ミリ秒未満)
- これが Hypermedia アプリとして実現できること自体が、Datastar の可能性を示している
現在の使用体験
- まだ Datastar を探求している段階だが、標準的な HTMX 機能にあたる UI 更新の AJAX 処理をすばやく簡単に実装できている
- Datastar を使ってさらに多くを達成するためのさまざまなパターンを学び、実験している
- 数十年にわたり、リアルタイム更新によってより良いユーザー体験を提供する方法に関心を持ってきたが、Datastar が 同期コードでもプッシュベース更新 を可能にしてくれる点が気に入っている
- HTMX を使い始めたときには大きな喜びを感じたが、Datastar に移行してから失ったものは何もなく、むしろ はるかに多くを得た と感じている
- HTMX を使って喜びを感じたことがあるなら、Datastar でも同じ飛躍を再び感じるだろう。それは Web が本来あるべき姿を再発見すること に近い
2件のコメント
Datastar - インタラクティブなWebアプリ構築のための軽量ハイパーメディアフレームワーク
Hacker News のコメント
<span hx-target="#rebuild-bundle-status-button" hx-select="#rebuild-bundle-status-button" hx-swap="outerHTML" hx-trigger="click" hx-get="/rebuild/status-button"></span>こういう datastar のコードに変わるようだが:<span data-on-click="@get('/rebuild/status-button')"></span>さらに他の例はもっと混乱する。結局、なぜ htmx から Datastar へ移るのか理由が分からない