I've been very impressed, so far, with Datastar, a tiny JavaScript library for front-end work; I've been switching a personal side-project from using Svelte for it's UI to Datastar, and as amazing as Svelte is, Datastar has impressed me more.
Datastar's essential concept is for the client to shift virtually all logic and all markup rendering back to the server; event handlers can succinctly call server endpoints, which return markup, and the markup is morphed into the running DOM. This makes the server-side is the system of record. Datastar has a nice DSL, based on data-* attributes, allowing you to do nearly anything you need to do in the client, declaratively.
Alternately, the server can start an SSE (server sent event) stream and send down markup to morph into the DOM, or JavaScript to execute, over any period of time. For example, my project has a long running process and it was a snap to create a modal progress dialog and keep it updated as the server-side process looped through its inputs.
The mantra of Datastar is to trust the morph and the browser -- it's surprisingly fast to update even when sending a fair bit of content. It feels wasteful to update a whole page just to change a few small things (say, mark a button as disabled) but it works, and it's fast, and it frees you from a nearly all client-side reactive updates (and all the related edge cases and unforseen consequences).
The server side is not bound to any particular language or framework (they have API implementations for Clojure, Java, Python, Ruby, and many others) ... and you could probably write your own API in an afternoon.
I especially like side-stepping the issue of needing more representations of data; the data lives server-side, all that is ever sent to the client is markup. There's no over-the-wire representation, and no parallel client-side data model. All that's ever exposed as endpoints are intentional ones that do work and deliver markup ... in other words, always use-case based, never schema based.
There's a minimal amount of reactive logic in the client, but the essence of moving the logic to server feels like home; Tapestry (way back in 2005) had some similar ideas, but was far more limited (due to many factors, including JavaScript and browser maturity in that time).
I value simplicity, and Datastar looks to fit my needs without doing so much that is magical or hidden. I consider that a big win!
Top comments (3)
I was just adding a feature to my project; I wanted to be able to toggle on and off some extra information that can be a distraction. I decided to go with the Datastar mantra of trusting the morph, so on a change to the toggle, I'd just re-render the entire page.
I figured, if it didn't look good, I'd work out some JavaScript+CSS to simply hide or reveal the optional content when the toggle button changed state.
Instead what I saw was about 16 ms to re-render and send the entire page (about 76000 characters) and less than a millisecond to morph the DOM.
The specific application on this project is entirely local ... I'm running a server and a client together to edit a special kind of file related to my project ... so again, I'm only concerned with local access, where transmission speed is not a factor.
However, even if I were to be concerned with getting that 76K of data down to the client over an Internet connection -- that is, something more real-world than my single local client toy -- I'm pretty confident that I could enable Brotli compression to shrink the payload down to very little.
What have I gained? Simplicity. No extra JavaScript on my page, no hidden data, no special CSS rules, no possibility of de-synchronization between what's on the client browser view and any server-side state.
As much as I liked Svelte, being able to do my principle programming in Clojure and not TypeScript, having no JavaScript build step at all (I have perhaps 20 lines of custom JavaScript entirely), not having to refresh my browser page when server-side logic or rendering changes (caveat: except maybe to refresh the CSS style sheet) just makes this work a joy.
With Svelte, I definitely had a few difficult-to-diagnose issues with the client state and the server state diverging, and laggy client-side performance problems due to all that reactive state logic. Datastar, by comparison, is simple, straight forward, and more than fast enough for my needs, and I'm just feeling so much less constrained.
Nice write-up, Howard.
I also got interested in Data* recently and was looking for a server that can emit an SSE stream. I hoped to do so with Pedestal and I'm still working my way through the (Getting Started) guides on the Pedestal website.
Since the documentation for the Clojure-SDK on the Data* website is also still above my head, I was wondering if you could list the steps needed to create a bare minimum example (e.g. a page just showing the ISO_LOCAL_TIME).
If you can provide those, I can try to write a tutorial based on those.
As I understand it, someone else is working on Datastar for Pedestal.
In my application, which you can see (it's public, linked above), I used an existing Clojure API provided by Datastar, that builds directly on HttpKit. I can't use Pedestal because Pedestal can't run in Babashka, which is my deployment model for the application (which is essentially a Babashka script distributed as a Homebrew formula).
Really, what I missed is proper Pedestal routing; instead I used Tonsky's simple router, and cobbled together a couple of other things to support Hiccup style markup generation.
It boils down to a little bit of extra middleware in my Ring-based request processing pipeline.
Looks like GitHub is having a rough day, can't link to the specific bit of code right now.