Warum ich von HTMX zu Datastar gewechselt bin
(everydaysuperpowers.dev)- Mit HTMX ließ sich der Codeumfang zwar um etwa 70 % reduzieren, zugleich traten jedoch Synchronisierungsprobleme zwischen UIs sowie eine zunehmende Komplexität beim Frontend-Statusmanagement auf
- Nach der Einführung von Datastar wurde es bei der Entwicklung von Echtzeit-Multiuser-Anwendungen möglich, ohne WebSockets mit schlankem Code zu arbeiten und die Wartung zu erleichtern
- Während HTMX die Anwendungslogik auf HTML-Attribute verteilt, erhöht Datastar mit einem servergesteuerten Update-Modell die Konsistenz und Wartbarkeit der Logik
- Die Datastar-API kommt mit weniger Attributen aus, was die Lesbarkeit und Produktivität des Codes verbessert
- Datastar nutzt webnative Technologien wie Server-Sent Events (SSE), Web Components und CSS View Transitions aktiv, um Echtzeit-Zusammenarbeit und wiederverwendbare Komponentenstrukturen zu ermöglichen
Einführung und Motivation
- 2022 stellte David Guillot auf der DjangoCon Europe einen Fall vor, in dem ein React-basiertes SaaS-Produkt auf HTMX umgestellt wurde, wodurch sich der Codeumfang um etwa 70 % verringerte und Funktionen verbessert wurden
- Danach machten viele Teams die Erfahrung, dass beim Wechsel von Single-Page-Apps (SPA) zu mehrseitigen Hypermedia-Apps nicht nur Code eingespart wurde, sondern sich auch Entwickler- und Nutzererfahrung verbesserten
- Auch der Autor stellte Projekte von HTMX auf Datastar um und bestätigte dabei, dass der Code kürzer wurde und sich Echtzeit-Multiuser-Apps ohne WebSocket oder komplexes Statusmanagement entwickeln lassen
Probleme, die den Wechsel auslösten
- Bei der Vorbereitung eines Vortrags für die FlaskCon 2025 wurde versucht, die UI mit einer Kombination aus HTMX und AlpineJS zu synchronisieren, dabei stieß man jedoch auf Synchronisierungsprobleme
- Die beiden Bibliotheken sind getrennte Werkzeuge verschiedener Entwickler und können nicht direkt miteinander kommunizieren, sodass die Integrationsarbeit vom Entwickler selbst übernommen werden muss
- Beim Initialisieren von Komponenten zu unterschiedlichen Zeitpunkten und beim Abstimmen von Events war deutlich mehr Code und Debugging-Zeit nötig als erwartet
- Auffällig war, dass Datastar die Funktionen beider Bibliotheken vereint und dabei weniger als 11 KB groß ist, weshalb es ausprobiert wurde
- Das ist insbesondere für die Ladeperformance auf Mobilgeräten vorteilhaft
Das bessere API-Design von Datastar
- Die API von Datastar wirkt im Vergleich zu HTMX deutlich leichtergewichtig, und es sind weniger zusätzliche Attribute nötig, um das gewünschte Ergebnis zu erzielen
- HTMX benötigt bei den meisten Interaktionen mehrere Attribute
- URL-Definition, Ziel-Element und Art der Antwortverarbeitung werden jeweils über eigene Attribute konfiguriert
- In der Regel werden 2–3 Attribute pro Fall verwendet, und manchmal muss man entlang einer Vererbungskette nachverfolgen, wie ein Attribut funktioniert
<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> - Mit Datastar lässt sich dieselbe Funktion meist mit nur einem einzigen Attribut umsetzen
<a data-on-click="@get('/rebuild/status-button')"></a>- Auch Monate später lässt sich beim erneuten Lesen des Codes leicht nachvollziehen, wie er funktioniert
Unterschiede im Funktionsprinzip
- HTMX ist eine Frontend-Bibliothek, die HTML erweitern will, während Datastar eine servergesteuerte Bibliothek ist, die auf den Aufbau leistungsfähiger, webnativer Echtzeit-Update-Anwendungen abzielt
- HTMX definiert Verhalten über Attribute an dem Element, das die Anfrage auslöst. Selbst wenn weit entfernte Elemente auf der Seite aktualisiert werden, verteilt sich die Logik dadurch über mehrere Ebenen
- Bei Datastar entscheidet der Server, was geändert wird, sodass die gesamte Update-Logik an einer Stelle gebündelt ist
-
HTMX-Beispiel
<div> <div id="alert"></div> <button hx-get="/info" hx-select="#info-details" hx-swap="outerHTML" hx-select-oob="#alert"> Get Info! </button> </div>- Beim Klick auf die Schaltfläche wird eine GET-Anfrage an
/infogesendet, der Button durch das Element mit der IDinfo-detailsaus der Antwort ersetzt und zugleich das Element mit der IDalertauf der Seite durch das gleichnamige Element aus der Antwort ausgetauscht - Das Button-Element muss zu viel wissen, und da es im Voraus wissen muss, was der Server zurückliefern wird, wird das HTMX-Prinzip der „locality of behavior“ geschwächt
- Beim Klick auf die Schaltfläche wird eine GET-Anfrage an
-
Verbesserter Ansatz von Datastar
<div> <div id="alert"></div> <button id="info-details" data-on-click="@get('/info')"> Get Info! </button> </div>- Der Server gibt einen HTML-String mit zwei Root-Elementen zurück, die dieselben IDs tragen
<p id="info-details">These are the details you are looking for…</p> <div id="alert">Alert! This is a test.</div> - Eine einfache und performante Option
- Der Server gibt einen HTML-String mit zwei Root-Elementen zurück, die dieselben IDs tragen
Auf Komponentenebene denken
- Ein besserer Ansatz besteht darin, HTML als Komponenten zu behandeln
- Dabei wird das Wesen der betreffenden Komponente betrachtet
- Wie der Nutzer zusätzliche Informationen zu einem bestimmten Element erhält
- Klickt der Nutzer auf den Button, werden entweder Informationen angezeigt oder bei fehlenden Daten ein Fehler gerendert; in beiden Fällen geht die Komponente in einen statischen Zustand über
-
Komponenten nach Zustand trennen
- Platzhalterzustand:
<!-- info-component-placeholder.html --> <div id="info-component"> <button data-on-click="@get('/product/{{product.id}}/info')"> Get Info! </button> </div> - Zustand zur Anzeige der Informationen:
<!-- info-component-get.html --> <div id="info-component"> {% if alert %}<div id="alert">{{ alert }}</div>{% endif %} <p>{{product.additional_information}}</p> </div> - Sobald der Server das HTML rendert, aktualisiert Datastar die Seite automatisch
- Denken auf Komponentenebene verhindert, dass man in ungültige Zustände gerät oder den Nutzerzustand verliert
- Platzhalterzustand:
Mehrere Komponenten gleichzeitig aktualisieren
- Ein besonders eindrucksvoller Punkt in David Guillots Vortrag war, dass beim Aktualisieren der Anzahl bevorzugter Elemente nicht nur die geänderte Komponente, sondern auch ein weit entferntes Zählelement mit aktualisiert wurde
- In HTMX würde dafür ein JavaScript-Event ausgelöst, das wiederum eine GET-Anfrage an die entfernte Komponente triggert
- Mit Datastar lassen sich mehrere Komponenten sogar innerhalb einer synchronen Funktion gleichzeitig aktualisieren
-
Beispiel Warenkorb
- Komponente zum Hinzufügen in den Warenkorb:
<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> - Komponente zur Anzeige der Warenkorbanzahl:
<div id="cart-count"> <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"> <use href="#shoppingCart"> </svg> {{count}} </div> - In Django werden beide Komponenten mit derselben Anfrage aktualisiert:
from datastar_py.consts import ElementPatchMode from datastar_py.django import ( DatastarResponse, ServerSentEventGenerator as SSE, ) def add_item(request): # wichtige Statusaktualisierungen ausgelassen 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)) ), ])
- Komponente zum Hinzufügen in den Warenkorb:
Webnative Philosophie
- Über die Datastar-Discord-Community wurde klar, dass Datastar nicht nur ein Hilfsskript ist, sondern eine Philosophie zum Bau von Apps mit den grundlegenden Primitiven des Webs
- Während HTMX darauf abzielt, die HTML-Spezifikation weiterzuentwickeln, interessiert sich Datastar stärker dafür, die Nutzung webnativer Funktionen zu fördern
- CSS view transitions
- Server-Sent Events
- Web Components usw.
- Ein großer Erfolg war das Refactoring komplexer AlpineJS-Komponenten hin zu einfachen Web Components, die sich an mehreren Stellen wiederverwenden lassen
- Auch ohne Werkzeuge wie React ist dies ein hervorragendes Muster, um über benutzerdefinierte HTML-Elemente hohe locality of behavior und Wiederverwendbarkeit zu erreichen
Echtzeit-Updates für Multiuser-Apps
- Anwendungen, in denen Zusammenarbeit eine Funktion erster Klasse ist, heben sich von anderen Apps ab, und Datastar adressiert genau diese Aufgabe
- Die meisten HTMX-Entwickler holen Informationen per Polling vom Server oder schreiben eigenen WebSocket-Code, was die Komplexität erhöht
- Datastar verwendet mit Server-Sent Events (SSE) eine einfache Webtechnologie, mit der der Server Updates an verbundene Clients pushen kann
- Wenn ein Nutzer einen Kommentar hinzufügt oder sich ein Zustand ändert, aktualisiert der Server den Browser sofort, bei minimalem Zusatzcode
- So lassen sich Echtzeit-Dashboards, Admin-Panels und Kollaborationstools auch ohne eigenes JavaScript erstellen
- Wird die Client-Verbindung unterbrochen, versucht der Browser automatisch die Wiederverbindung, ohne dass zusätzlicher Code nötig ist
- Dem Server kann dabei auch das zuletzt empfangene Event mitgeteilt werden
Übermäßige Komplexität vermeiden
- Die Datastar-Discord-Community half dabei, Datastars Vision für den Bau von Web-Apps zu verstehen
- Push-basierte UI-Updates
- Weniger Komplexität
- Nutzung von Werkzeugen wie Web Components zur Behandlung lokal komplexer Situationen
- Die Community hilft neuen Nutzern auch zu erkennen, wenn sie an ein Problem unnötig kompliziert herangehen
Wichtige Tipps
- Scheue dich nicht davor, ganze Komponenten erneut zu rendern und zu übertragen
- Das ist einfacher und hat kaum Auswirkungen auf die Performance
- Es kann sogar zu besserer Kompression führen, und Browser parsen HTML-Strings sehr schnell
- Der Server ist die Quelle der Wahrheit und leistungsfähiger als der Browser
- Der Server sollte den Großteil des Status verwalten, und reaktive Signale werden womöglich seltener benötigt als gedacht
- Web Components eignen sich hervorragend dazu, Logik in benutzerdefinierten Elementen mit hoher locality of behavior zu kapseln
- Das Sternenfeld im Header der Datastar-Website ist ein gutes Beispiel
- Das Element
<ds-starfield>kapselt den gesamten Code für die Sternenfeld-Animation und stellt drei Attribute bereit, um den internen Zustand zu verändern - Datastar steuert diese Attribute, wenn sich ein Range-Input ändert oder wenn sich die Maus über dem Element bewegt
Möglichkeiten jenseits bisheriger Grenzen
- Am spannendsten ist das Potenzial dessen, was Datastar möglich macht
- Die Community erstellt regelmäßig Projekte, die weit über die Grenzen hinausgehen, die Entwickler mit anderen Werkzeugen typischerweise erleben
Bemerkenswerte Beispiele
- Das Datenbank-Monitoring-Demo auf der Beispielseite
- Nutzt Hypermedia und verbessert Geschwindigkeit und Speicherverbrauch eines auf einer JavaScript-Konferenz vorgestellten Demos deutlich
- Anders Murphys 1 Milliarde Kontrollkästchen
- Nachdem ein Experiment mit 1 Million Kontrollkästchen die Serverkapazität überschritt, wurden mit Datastar 1 Milliarde auf einem günstigen Server umgesetzt
- Eine Web-App, die die Daten aller Radarstationen in den USA anzeigt
- Ändert sich das Signal eines Radars, ändert sich der entsprechende Punkt in der UI innerhalb von 100 Millisekunden
- Es werden mehr als 800.000 Punkte pro Sekunde aktualisiert, und Nutzer können bis zu eine Stunde zurück scrubben, bei einer Latenz von unter 700 Millisekunden
- Dass so etwas als Hypermedia-App möglich ist, zeigt, was Datastar ermöglichen kann
Aktuelle Nutzungserfahrung
- Der Autor befindet sich weiterhin in einer Erkundungsphase mit Datastar und setzt damit die AJAX-artige UI-Aktualisierung klassischer HTMX-Funktionen schnell und einfach um
- Gleichzeitig werden verschiedene Muster gelernt und ausprobiert, um mit Datastar noch mehr zu erreichen
- Seit Jahrzehnten besteht Interesse daran, wie Echtzeit-Updates die Nutzererfahrung verbessern können, und besonders positiv ist, dass Datastar push-basierte Updates auch in synchronem Code ermöglicht
- Schon beim Einstieg in HTMX war die Freude groß, doch seit dem Wechsel zu Datastar hat der Autor nicht das Gefühl, etwas verloren zu haben, sondern vielmehr sehr viel mehr gewonnen zu haben
- Wer mit HTMX Freude hatte, wird mit Datastar vermutlich denselben Sprung noch einmal erleben — als würde man neu entdecken, was das Web eigentlich leisten sollte
2 Kommentare
Datastar – ein leichtgewichtiges Hypermedia-Framework zum Erstellen interaktiver Web-Apps
Hacker-News-Kommentare
hx-trigger="click"weg, sind schon 20 % der Attribute verschwunden. Und wenn man semantisch zugänglicheres HTML schreiben würde, also zum Beispiel<span>durch<button>ersetzt, wirkte das Ganze glaubwürdiger. Letztlich scheint die Stärke von Datastar darin zu liegen, dass Funktionen von Alpine oder Stimulus direkt eingebaut sind, und das ist wirklich beeindruckenddata-replace-urlgäbe, die die URL der aktuellen Ansicht automatisch auf die entsprechenden Koordinaten (x=123&y=456usw.) aktualisiert<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>in so etwas wie diesen Datastar-Code umgewandelt zu werden:<span data-on-click="@get('/rebuild/status-button')"></span>Und andere Beispiele verwirren noch mehr. Ich verstehe also am Ende nicht, warum man überhaupt von htmx zu Datastar wechseln sollte/rebuild/status-button, extrahiere aus dem zurückgegebenen HTML das Element#rebuild-bundle-status-buttonund ersetze dann das bestehende Element.“ Bei Datastar heißt es dagegen eher: „Wenn auf das span geklickt wird, folge einfach den Anweisungen von/rebuild/status-button.“ Gibt der Server mehrere Elemente mit IDs zurück, erkennt Datastar diese automatisch und ersetzt alle entsprechenden Elemente. Man muss alsotarget,selectundswapnicht extra angeben; IDs reichen aus, damit es wie beabsichtigt funktioniertspanals klickbares Element verwendet. Einbuttonoder Link wäre doch passenderhtmx-swap-oob="true"; ohne dieses Attribut funktioniert es anders als erwartet. 2. Umgekehrt wirdhtmx-swap-oob="true"ignoriert oder verhält sich falsch, wenn es nicht OOB ist. Dadurch muss der Server bei der Wiederverwendung derselben Komponente als OOB/Nicht-OOB jedes Mal einisOob-Flag mitliefern, was ziemlich lästig ist