silver-arrow on Dec 14, 2022 | parent | context | favorite | on: Htmx in a Nutshell

Have been using htmx for a little over a year now, and I am so thankful for this library. It has simplified our development tremendously from ClojureScript / React to vanilla Clojure on the backend doing SSR of HTML with htmx HTML element attributes. All with 1 script tag that includes this wonderful library. Kudos to the creator of htmx!

This is what hypermedia architecture with true HATEOAS is all about. It feels like we took a wrong turn a decade ago, and we have been trying to reinvent rich clients with JSON RPC designs from the 90s. It has resulted in too much churn and complexity IMO.

bnert on Dec 14, 2022 | next [–]

Doing something similar with janet. Really does simplify so much, and being able to not have to worry about always translating json -> html via { insert SPA framework here } is a breath of fresh air.

silver-arrow on Dec 14, 2022 | parent | next [–]

Agreed that it is a breath of fresh air. It totally eliminates heavy complexity in the development environment and networking protocol layers. What makes me happy is that it completely reinvigorates the server side language you prefer to use.

harryvederci on Dec 14, 2022 | parent | prev | next [–]

Same here, also using Janet + htmx!

hipjiveguy on Dec 15, 2022 | root | parent | next [–]

Do you mean this?

https://janet-lang.org/

bnert on Dec 15, 2022 | root | parent | next [–]

Yes!

mrits on Dec 14, 2022 | prev | next [–]

I've done a few years of solid frontend dev in the last decade and became efficient in a few different modern frameworks. For personal projects I just pull in a bootstrap and jquery framework from a CDN. I think htmx is going to replace this practice.

eduction on Dec 15, 2022 | prev | next [–]

Do you know off hand how much JavaScript your pages are including now versus when you were using ClojureScript/React?

I ask because I know ClojureScript is built on Google Closure which has pretty advanced dead code elimination (and typically for React functionality people use libraries like reagant that take advantage of this - I think?). Presumably with htmx users are having to download the whole htmx lib.

silver-arrow on Dec 15, 2022 | parent | next [–]

The JavaScript we use is now much, much less. Actually, most times we just sprinkle in a bit of hyperscript for the times we need something a bit more dynamic than plain htmx. _hyperscript is a separate library by the same author that provides an HTML type of scripting in the attribute tags.

Still, in general, because we are generating fragments of hypermedia (HTML) on the server and returning that to target elements in the browser directly, the need for JavaScript directly in our code lessens dramatically. As you mentioned, though, you do need to include the hmtx JavaScript libary with a script tag. That library is very small though when min - something like 12k.

Amazingly, you can build some pretty dynamic web apps using htmx and sprinkling in a little vanilla JavaScript or _hyperscript if you are daring! The main key for us though is thinking from a hypermedia point of view with SSR; that coupled with the elimination of the complicated development configurations, is a huge win in simplicity and productivity. No more versioning our endpoints too!

kaeshiwaza on Dec 15, 2022 | root | parent | next [–]

As we use less and less Javascript, thanks to htmx, i'm no so reluctant to use vanilla JS when needed.

sandGorgon on Dec 15, 2022 | root | parent | prev | next [–]

why hyperscript and not alpine.js ? just asking. alpine.js seems to be a lot more popular generally (even without htmx)

silver-arrow on Dec 15, 2022 | root | parent | next [–]

Hmmm. alpine.js does complement htmx very well and is pretty popular, so it is a great choice. I liked _hyperscript when I saw it when assessing htmx so thought they might play better together since the author is the same for both. But I don't know if that is even really true actually, and alpine.js seems to be just fine. Honestly, it may be because I used AppleScript back in the day and _hyperscript reminded me of it!

bnert on Dec 15, 2022 | parent | prev | next [–]

I will say, the :advanced flag for the closure compiler has caused us some real headaches in production, enough to where we needed to migrate to only the :simple flag.

The reason being, is we needed to do some code gen of Svg components in React Native, and in the build process (via shadow-cljs), the closure compiler was munging the definitions in the.

Off the top of my head an svgr component which looked like: // generated component import React from 'react'

function SomeSvg(props) { return React.createElement(..., ...merged props..., ...children...) }

was getting "optimized" to be used as: function $SomeSvg(props) { return $R.$c($$..., $$...merged props..., $$...children...) }

The above is psuedo-ish code, but I think covers the point. There were also some edge cases where not includeing a ^js reader macro before using js values caused crashes, due to the symbol being munged in the background. As a result, whenever we used any sort of js, we needed to mark it w/ a ^js reader macro, which began to get really noisey and error prone if you forgot. :simple has made the code based more stable. Could be user error on our end, but the fact that we ran into so much with :advanced and our application stabilized significantly after that one configuration change, it makes me think using :advanced can be too involved.

In addition (to an already winded comment), cljs needs to include the cljs runtime. I haven't benched the size, but it is no negligible (think in the 100's of KB), on top of what react/reagent/re-frame may bring in, you can easily end up w/ a compiled cljs app of > 1MB.

0x6c6f6c on Dec 14, 2022 | prev | next [–]

I would love to see some example projects of stacks like this

sandGorgon on Dec 15, 2022 | prev [–]

here's a question to you...since ur like, almost from the Java world!

Why not clj-thymeleaf ? or even clojurescript ? this is very interesting that you find HTMX better than clojurescript - typically clojure devs prefer to stay within the lisp world for any markup.

ive been getting pushback in a java team against htmx. cos the value prop is unclear vs jsp or thymeleaf. Would love to hear ur perspective.

silver-arrow on Dec 15, 2022 | parent | next [–]

I see bnert gave a very good answer to your question in a reply. So to echo what bnert said, as far as the java template frameworks like jsp and thymeleaf, htmx is complementary to them. Here is a good scenario in this blog post by someone. https://www.wimdeblauwe.com/blog/2021/10/04/todomvc-with-thy...

Also like bnert, when using Clojure we prefer to use a library on the server-side called hiccup to generate HTML from Clojure data structures instead of a template library. Either way, template engine or hiccup HTML generation, the principle is the same. You are coding everything on the server-side and rendering HTML back to the browser - something it is VERY good at rendering. :) As part of that HTML rendering, you are putting attributes on some HTML elements that htmx understands from the browser side.

ClojureScript is a totally different concept, as it basically allows a Clojure developer to write JavaScript in Clojure - it generates JS. With the server-side rendering model above, we still code 100% in Clojure, but we get to jettison a lot of the complexity of the ClojureScript model (Reagent etc), and development set-up. It is far simpler.

Other benefits of the hypermedia approach provided by htmx: 1. It is obviously true HATEAOS, so we don't worry about end-point versioning issues. 2. The barriers of the UI team and Server team negotiating and re-negotiating end-points is gone. We have all transitioned to "full stack developers" - sanely. 3. As mentioned above, it is language and server-side agnostic, so use what you prefer and have competitive advantage with using. 4. Simpler mental model. The team thinks in hypermedia terms from a server rendering perspective only. That lets you wrap your brain more easily around the business problems. 5. You have the full power and capability of your database at your disposal instead of going through and desigining the correct endpoints to get there first.

Hope that made sense

sandGorgon on Dec 15, 2022 | root | parent | next [–]

hey thanks so much for the explanation. so my question about cljscript and thymeleaf vs htmx was because they all do the same thing. im totally sold on the server side aspect of the whole thing...and especially the "full stack developers" aspect.

what im not sold on is HTMX specifically. im kind of wondering things like "hey what if we just used alpine.js with JSP or thymeleaf and skip htmx entirely", etc

what im getting incrementally with htmx is a bit unclear...while staying in the server side rendering of things.

bnert on Dec 15, 2022 | root | parent | prev | next [–]

Thanks for calling out some gaps in my explanation! Appreciate your perspective :)

bnert on Dec 15, 2022 | parent | prev [–]

After a quick glance, it seems like htmx would complement thymeleaf, if the web page/app you're writing doesn't need any sort of eager client (eager as in, treat and interaction with a remote service as "successful" and resolve the error in the background somehow).

W/ htmx + clojure, you can define your ui like so:

(def counter (atom 0))

(defn partial-count-markup [c]

[:span (str "Pressed: " c)])

; Handler for /partial/count

(defn partial-count []

(swap! counter inc)

(partial-count-markup @counter))

; Handler for index.html

(defn handler []

[:button {:hx-put "/partial/count" :hx-swap "innerHtml"}

(partial-count-markup @counter)])

And like that you have a page with a button that tracks a counter and updates the ui (I haven't tested this, YMMV).

Also if it isn't clear, you can also keep all your markup as Clojure data structures, which means you can write an `html` function which has the common styles/scripts/etc.. necessary so you get a ton of re-use with an already similar syntax vs needing to learn a new templating syntax w/ its own conventions.

W/ clojurescript, to get the same behavior you need:

- clojurescript toolchain w/ some configuration of how you'll bundle/package it

- an idea of how you'll distribute your application (serve spa from same API service? S3/Object store? another web service? How to reconcile state?)

- an idea of how you'll reconcile state between local/server, if you want to go that route. If only local, nbd. If server, you add a handler and fetch data once SPA or cljs has loaded.

- and idea of what format you want to consume (JSON, HTML, XML,text) and then write the translation between that format and your markup.

etc...

I hope the above answers your question, or at the very least offers another perspective.

As a quick postscript, I think it is underestimated how convoluted templates/templating engines are, given they have the tendency to implement their own language/semantics outside of the PL you're using. I have much respect for the authors of template engines/spec, the engineering that goes into them is impressive, however, most I have come across tend to be a leaky abstraction. Once I experienced writing markup as Clojure data structures, it ruined me for templates permanently. I don't want to go back to writing templates, and doing an assessment of "what language does this template engine implement, and is it simple/easy to learn?" is an exercise I do not miss.

Note: I've been Clojure/ClojureScript developing professionally for almost two years now and have debated most of the above internally during that time. Done some templating w/ JS, Go.

sandGorgon on Dec 15, 2022 | root | parent [–]

hey thanks so much! truly appreciate the detailed answer. I have one doubt and uve probably answered it...but i still cant see it.

im not able to figure out what is it that htmx is saving you in the example above. with clojurescript, wouldnt you have written very similar code ? i mean all you are doing is calling an api. is it automatically doing conversion of JSON to ur DTO/business object. that cant be right can it ?

my mind is telling me that it is some kind of lifecycle management - like before/after hooks. make sure that the html loads after server is loaded, etc. is that what it is ?

bnert on Dec 16, 2022 | root | parent [–]

No worries, good questions!

Taking clojure out of the equation and only looking at htmx, I would say it buys you is simplicity, and for most use cases that is the difference between a shipped thing and a dead one (thing in this case is project, product, etc...). Granted, htmx isn't a silver bullet, as you have to learn some of the idioms of the library. But it may be worth it to some (it is to me), to not have to bring in an entire JS toolchain to get a thing bootstrapped.

im not able to figure out what is it that htmx is saving you in the example above. with clojurescript, wouldnt you have written very similar code ? i mean all you are doing is calling an api. is it automatically doing conversion of JSON to ur DTO/business object. that cant be right can it ?

It can kind of be what you need it to be (if I am interpreting the API provided by htmx correctly). The way I have been using it is to partially update my DOM based on some user interaction (either a POST, PUT, DELETE), by returning html. This makes updates html -> html vs json -> frontend framework -> html.

As far as ClojureScript code, the above example would resemble the equivalent ClojureScript code almost 100% (with some slight differences). The example above was a little contrived, in that it is so simple. However, if you think about a larger use case (i.e. 100's of elements need to be rendered from a db), it doesn't become contrived.

With a Clojure backend + htmx, all I do is write my business logic, define my html via hiccup (Clojure vectors w/ a convention similar to html) and return the html to the client, which I can ship without needing to coordinate different pieces. In addition, the hiccup I hopefully defined is broken up into functions that'll allow me to partially update the DOM for different user CRUD operations.

With a Clojure backend + ClojureScript SPA, its the same-ish business logic on the backend to return results from a JSON API, some more logic on the front end to validate said JSON, some additional logic to add said data to a global store, then finally my view can update. Then in order to get you're app out there, you'll need to figure how to get you're application deployed. There has been a lot of work in ClojureScript land as far a build/packaging tools, but they can still be rough around the edges, which results in headaches sometimes between dev/prod. After you've gone through that and put your app in an S3 bucket/object store (for the sake of example), you'll need to get your API deployed (which should be same steps as above backend + htmx).

So while it may seem the same, there is a lot more complexity that comes with ClojureScript SPA.

Also, as a final thought for this section, even if you went with vanilla ClojureScript + html, you won't get much for free. You still need to compile your ClojureScript, which still requires DOM API's, which you'll still need to call from ClojureScript to handle updating the DOM from the ClojureScript which still needs to call AJAX/fetch, which still needs to resolve promises in order to get your JSON data (or html data), which still needs to translate your interchange format (in this instance JSON) to html. At this point, are the layers of abstraction worth it?

my mind is telling me that it is some kind of lifecycle management - like before/after hooks. make sure that the html loads after server is loaded, etc. is that what it is ?

Sort of. I like to think of htmx as filling the role of sync-ing my web view/page/app with backend state without all the ceremony and fuss of a frontend framework. I can handle all the logic and UI definition on the server and defer http requests (GET, POST, PUT, PATCH, DELETE) and DOM patching to htmx. Kind of like how a user of a frontend framework defers that same patching to the underlying virtual dom implementation.

Personally, when I first saw htmx I thought "well... that doesn't seem useful. I can get a React (or Vue, or Solid, or Svelte, or X) spun up w/ a cli command and do all my things there. But then I used it and couldn't have been more wrong on its usefulness. I encourage you to play around with htmx, try implementing the counter example in your preferred language, see if you come to the same conclusion you had previously.

There are cases where a SPA is appropriate and merits the investment, but I think they should be the exception not the rule, due to the complexity brought about by a frontend framework.

In conclusion, I hope I answered your questions. I am happy to continue this discourse if you still want to chat about it. At the end of the day though, all we're trying to do is render web pages, so do whatever makes sense to you to accomplish that.

sandGorgon on Dec 16, 2022 | root | parent [–]

>The way I have been using it is to partially update my DOM based on some user interaction (either a POST, PUT, DELETE), by returning html. This makes updates html -> html vs json -> frontend framework -> html.

oh this clicked. Now i get what htmx is doing. its basically doing a react-ish way of UI changes without a full page reload (which thymeleaf would have done).

so what happens if i click something that needs to go to another "page"? do you "swap" the html out with the html of the other page ? or do you trigger a full page reload. For SEO reasons, im kinda putting the requirement that every interaction must have a unique url

bnert on Dec 16, 2022 | root | parent [–]

its basically doing a react-ish way of UI changes without a full page reload (which thymeleaf would have done).

Interesting, I missed that in my scan of the thymeleaf docs. I haven't used that library at all (and tend to shy away from Java land in general, unless it's Clojure).

And for my purposes, yes. I may be missing the point of the library, but that is how I've tended to use it.

so what happens if i click something that needs to go to another "page"? do you "swap" the html out with the html of the other page ? or do you trigger a full page reload. For SEO reasons, im kinda putting the requirement that every interaction must have a unique url

I'd say pick your poison. You could go either route and it would most likely be roughly the same code.