It's an amusing solution, but if this ends up being anything like the missing PHP 6, it's also going to cause confusion for users. It might've been better to just mea culpa and release 3.0 anyway. I can't imagine anybody would really hold it against the author.
What's the confusion around PHP 6? - There would have been a lot more confusion due to articles, talk slides, even printed books, .. talking about PHP 6's Unicode support would have been truly confusing.
From what I know of Carson from his writing and presentations, he probably worded it that way on purpose knowing he'd eventually do a new version, and he didn't want to miss an opportunity to troll everyone a bit.
> htmx 2.0 (like htmx 1.0 & intercooler.js 1.0) will be supported in perpetuity, so there is absolutely no pressure to upgrade your application: if htmx 2.0 is satisfying your hypermedia needs, you can stick with it
This is commendable, specially during times when libraries and programs aren't afraid of breaking changes and API churn.
I think the ideas behind HTMX are cool, and Carson’s advocacy around Hypermedia is great.
I came from SPA-land and was tired of the fundamental architectural issue of having to keep the front-end and back-end state in sync.
I’ve compared Datastar and HTMX and decided on Datastar. There’s overlap between the two libraries in that they both support the request/response model, but with Datastar my learning investment takes me further and opens up new possibilities.
In one project I was able to remove a bunch of polling code and simply push a signal from the server to the browser when an external event occurred. The reduction in complexity was crazy.
On an internal tool I noticed I didn’t need Alpine.js anymore, and while anytime I can remove a dependency is a good time, the conceptual simplicity is what really makes me happy.
Now I’m doing a small app where I decided to make a streaming connection between browser and server and simply regenerate and send the entire view over that connection anytime something changes. Intuitively this felt wasteful but it turned out that with compression this works beautifully. There’s just less code in my app, and the code that’s there is less “fiddly” not having to deal with partial updates of the page.
If you’re coming from the world of SPAs, definitely check out both.
One of the things I had to wrap my head around was the whole async server style of programming. I learned that doing a sleep on the server consumes essentially zero resources. The thread goes to sleep and wakes up when the timer expires. In the meantime the server can handle other requests unhindered. You can have hundreds or thousands of these threads at the same time - serving requests, waiting for IO, etc.
It’s not like synchronous servers e.g. Flask with Gunicorn (in the Python world) where you have a few pre-forked server processes. In that case it would be a waste to prevent one of those processes from serving other requests while sleeping, I agree.
Ultimately it’s cool though to be able to control the client from the backend asynchronously, updating signals (think of them as a kind of special front-end variable with superpowers) when something on the backend happens, or updating the client’s DOM, or running scripts.
That's a contrived example to show how you can redirect from the backend (and the sleep is to make it easy to see it working.) It isn't a recommendation that you should do it exactly that way.
> hx-target attribute is explicitly declared as inherited on the enclosing div and, if it wasn’t, the button elements would not inherit the target from it.
So confusing. I'm pretty sure it should be "inheritable", because "inherited" on an attribute means the attribute is inherited, not the element's children will inherit the attribute.
UPDATE: or "inherit", sounds like a command, little less confusing.
I was going to make the same comment. "inherited" is the wrong word. I like "inherit" too. And it's a bit shorter which is always nice. I guess "bequeath" might work also?
> I'm pretty sure it should be "inheritable", because "inherited" on an attribute means the attribute is inherited, not the element's children will inherit the attribute.
That is one interpretation, but not the one I had reading it. I read it as "This target will be inherited by its children," rather than "This target is inherited from its parents," which, while a grammatical possibility, doesn't really make sense because you are specifying it right there with the equal sign.
Both "inherit" and "inheritable" have the same possible double readings. Something like "pass-to-children" or "pass-down" would remove the possible ambiguity, but I'm not sure like either better than "inherited".
> By switching to fetch(), we can take advantage of its support for readable streams, which allow for a stream of content to be swapped into the DOM, rather than a single response.
Based on this section, it will be interesting to see how this evolves. I've used HTMX a bunch but after stumbling on Datastar I've come to prefer it. Partially because I don't need something like alpine.js to get some frontend goodies but also because I've come to appreciate the power of SSE streaming morphable targets to the browser
I feel like people who crave stability pull all the wrong lessons from how to get to stability when doing this sort of "ok we'll make _one_ major version with all the breaking changes we've built up over time".
This feels like a repeat of the Python 3.0 strategy, though obviously at a much smaller scale. Some stuff is of course hard to roll out but to me it feels "obvious" that having a 4.0 with the inheritence change (or even better, a 2.1 with a toggle on that change!), then a 5.0 with some other changes, then a 6.0 with other changes... all feels way easier to manage than a 4.0 with all the changes at once.
We have version pinning! People who want 2.0 can have 2.0 "forever", so version numbers that go up very high are not actually a problem. Many releases can of course be a bit of a frustration from a release maker's perspective, but given that htmx is the way it is (ain't even getting type checking helping you out on any of this like you would with React!), having the gradual path seems way better.
"I think I've handled the 10 changes in between 2.0 and 4.0... but forgot the 11th change" is a constant annoyance with these huge major version bumps.
I will once again point to the Django strategy of having breaking changes be rolled out over several releases, including in between releases where "both" models exist. It is a very nice way to do release management when rolling things out, gives good checkpoints for pushing things out, and overall means users are seeing less bugs. Going from `XMLHttpRequest` to `fetch` really might not be a feasable thing to toggle, but a lot of the other stuff in that list feels like it.
>I feel like people who crave stability pull all the wrong lessons from how to get to stability when doing this sort of "ok we'll make _one_ major version with all the breaking changes we've built up over time".
Yeah this is a huge fallacy. If you do a breaking change, you want to do as few breaking changes at once as possible, with as little impact as possible and in the best case scenario you're in contact with every single person that is affected by the breaking change, so that it can be handled without anyone noticing anything other than having to bump a library version, which they were doing anyway since they're running a new Python version in the first place.
The dumbest change in python 3 was removing the print statement [0]. Maximum breakage, minimum gain. They could have introduced the new function as println() and deprecated the print statement in Python 3.
Great to see this evolution! Realtime ssr hypermedia is definitely the future.
But, my thoughts immediately go to Datastar, which has Fetch, SSE, declarative signals and js expressions, dom morphing, and much more - in a tiny package. I find it to have a more flexible, expressive and standards-compliant API as well. And it'll soon have a simple reactive web components and css framework as well.
At this point, why use HTMX when it really seems like (a heavier) Datastar-lite?
Because Datastar Pro isn't FOSS, and speaking from a purely probabilistic and historical standpoint, your odds of getting rugpulled (in some form or another) might as well be 100%.
I can appreciate why people would have this perspective - I have been rugpulled a few times recently by thoroughly unscrupulous companies (Augment Code are trash humans).
But Datastar is different. The project is literally owned by a 501c3 non-profit. The devs have dayjobs and donate their spare time to this. Funds are for going to conferences or hosting their own
And 99% of the features/value that I mentioned is MIT licensed, and the "rugpulled" code is still available to easily port via the plugin API.
I took a look at Datastar, and pretty quickly realized I really don't like libraries which try to do so many different things. The surface area of the API is way too big. htmx is already too big for me, I tend to reach for mostly vanilla-only or small vanilla-adjacent solutions I can either write by hand or import. But as I've been experimenting with htmx (including its very good extensions API), I'm realizing it's in a pretty good sweet spot overall.
I'm very excited about htmx 4.0 and sounds like things are headed in a promising direction.
> why use HTMX when it really seems like (a heavier) Datastar-lite?
The reason to use htmx is that it has a simpler interface optimized for the majority use-case.
With htmx, you are largely tied to a request/reply paradigm. Something happens that triggers a request (e.g. user clicks a button, or some element scrolls into view), htmx sends the request, and then it processes the response. The htmx interface (`hx-get`, hx-trigger`) is optimized to make this paradigm extremely simple and concise to specify.
Datastar's focus (last I checked) is on decoupling these two things. Events may stream to the client at any time, regardless of whether or not they were triggered by a specific action on the client, and they get processed by Datastar and have some effect on the page. htmx has affordances for listening to events (SEE extension, new fetch support) and for placing items arbitrarily on the page (out-of-band swaps) but if your use-case is a video game or a dashboard or something else where the updates are frequently uncorrelated with user actions, Datastar makes a lot of sense. It's a bit like driving a manual transmission.
Delaney is fond of saying that there's no need for htmx when Datastar can technically do everything htmx can [0]. But I think this misses the point of what makes htmx so popular: most people's applications do fit within a largely request/reply paradigm, and using a library that assumes this paradigm is both simpler to implement and simpler to debug. As an htmx maintainer, I often encourage people to even use htmx less than they want to, because the request/reply paradigm is very powerful and the more you can adhere to browser's understanding of it, the more durable and maintainable your website will be [1].
Moreover, if htmx's real value is ajax request/response, then why are you introducing SSE as a first-class citizen now?
2. Datastar has data-on, and various other attributes, that allow for triggering far more actions than just backend requests, from far more (any) events. I'm glad to see that htmx is now following suit with hx-on, even if it is apparently limited in capabilities.
3. Datastar can do OOB-swaps just fine - that's literally the core functionality, via (their own, faster) idiomorph.
4. Its a misnomer that Datastar is for video games etc - again, as described above, it can do all of the simple things that that HTMX can do, and more. And, again, why is HTMX introducing SSE if its so apparently unnecessary and unwieldy?
5. What makes htmx popular is that it was the first library to make declarative fragment swapping easy. And Carson is just a god-tier marketer. Its nice to see that he's now realized that Delaney was on to something when he wanted to introduce all of these v4 features to HTMX 3 years ago, but was (fortunately for us happy users) forced to go make Datastar instead.
6. We havent even talked about one of the key features - declarative signals. Signals are justifiably taking over all of the JS frameworks and there's even an active proposal to make them part of the web platform. D* makes them simpler to use than any of them, and in a tiny package.
I, Delaney, and all other D* users are grateful for HTMX opening this door. But I reiterate my original question - now that HTMX is becoming Datastar-lite, why not just use Datastar given that the powerful extras don't add any complexity and comes in a smaller package?
One important difference that I found is that HTMX (and Alpine AJAX) can easily push a URL into the browser's location history. I've used this feature often with my Django projects--essentially storing the state in the URL which is great for sharing URLs or bookmarking them. As far as I'm aware, Datastar have locked this feature behind the "Pro" paywall.
Fair. Though, D*'s authors are pretty adamant that this is an anti-pattern - that's why they put it behind the paywall.
Moreover, the FOSS code still exists and would take 2 minutes to update to the current plugin API (I have Datastar pro and the code is almost exactly the same)
> However, in order to keep my word that there will not be a htmx 3.0, the next release will instead be htmx 4.0.
technically correct.. the best kind of correct
Deleted Comment
Deleted Comment
This is commendable, specially during times when libraries and programs aren't afraid of breaking changes and API churn.
I came from SPA-land and was tired of the fundamental architectural issue of having to keep the front-end and back-end state in sync.
I’ve compared Datastar and HTMX and decided on Datastar. There’s overlap between the two libraries in that they both support the request/response model, but with Datastar my learning investment takes me further and opens up new possibilities.
In one project I was able to remove a bunch of polling code and simply push a signal from the server to the browser when an external event occurred. The reduction in complexity was crazy.
On an internal tool I noticed I didn’t need Alpine.js anymore, and while anytime I can remove a dependency is a good time, the conceptual simplicity is what really makes me happy.
Now I’m doing a small app where I decided to make a streaming connection between browser and server and simply regenerate and send the entire view over that connection anytime something changes. Intuitively this felt wasteful but it turned out that with compression this works beautifully. There’s just less code in my app, and the code that’s there is less “fiddly” not having to deal with partial updates of the page.
If you’re coming from the world of SPAs, definitely check out both.
async def redirect_from_backend():
LOL, they really think it's a good use of server resources to sleep so the client doesn't have to.[1] https://data-star.dev/how_tos/redirect_the_page_from_the_bac...
It’s not like synchronous servers e.g. Flask with Gunicorn (in the Python world) where you have a few pre-forked server processes. In that case it would be a waste to prevent one of those processes from serving other requests while sleeping, I agree.
Ultimately it’s cool though to be able to control the client from the backend asynchronously, updating signals (think of them as a kind of special front-end variable with superpowers) when something on the backend happens, or updating the client’s DOM, or running scripts.
So confusing. I'm pretty sure it should be "inheritable", because "inherited" on an attribute means the attribute is inherited, not the element's children will inherit the attribute.
UPDATE: or "inherit", sounds like a command, little less confusing.
That is one interpretation, but not the one I had reading it. I read it as "This target will be inherited by its children," rather than "This target is inherited from its parents," which, while a grammatical possibility, doesn't really make sense because you are specifying it right there with the equal sign.
Both "inherit" and "inheritable" have the same possible double readings. Something like "pass-to-children" or "pass-down" would remove the possible ambiguity, but I'm not sure like either better than "inherited".
Deleted Comment
Like male pattern baldness, we are cursed by our parents to inherit it
Deleted Comment
Based on this section, it will be interesting to see how this evolves. I've used HTMX a bunch but after stumbling on Datastar I've come to prefer it. Partially because I don't need something like alpine.js to get some frontend goodies but also because I've come to appreciate the power of SSE streaming morphable targets to the browser
This feels like a repeat of the Python 3.0 strategy, though obviously at a much smaller scale. Some stuff is of course hard to roll out but to me it feels "obvious" that having a 4.0 with the inheritence change (or even better, a 2.1 with a toggle on that change!), then a 5.0 with some other changes, then a 6.0 with other changes... all feels way easier to manage than a 4.0 with all the changes at once.
We have version pinning! People who want 2.0 can have 2.0 "forever", so version numbers that go up very high are not actually a problem. Many releases can of course be a bit of a frustration from a release maker's perspective, but given that htmx is the way it is (ain't even getting type checking helping you out on any of this like you would with React!), having the gradual path seems way better.
"I think I've handled the 10 changes in between 2.0 and 4.0... but forgot the 11th change" is a constant annoyance with these huge major version bumps.
I will once again point to the Django strategy of having breaking changes be rolled out over several releases, including in between releases where "both" models exist. It is a very nice way to do release management when rolling things out, gives good checkpoints for pushing things out, and overall means users are seeing less bugs. Going from `XMLHttpRequest` to `fetch` really might not be a feasable thing to toggle, but a lot of the other stuff in that list feels like it.
Yeah this is a huge fallacy. If you do a breaking change, you want to do as few breaking changes at once as possible, with as little impact as possible and in the best case scenario you're in contact with every single person that is affected by the breaking change, so that it can be handled without anyone noticing anything other than having to bump a library version, which they were doing anyway since they're running a new Python version in the first place.
The dumbest change in python 3 was removing the print statement [0]. Maximum breakage, minimum gain. They could have introduced the new function as println() and deprecated the print statement in Python 3.
[0] https://news.ycombinator.com/item?id=13260563
But, my thoughts immediately go to Datastar, which has Fetch, SSE, declarative signals and js expressions, dom morphing, and much more - in a tiny package. I find it to have a more flexible, expressive and standards-compliant API as well. And it'll soon have a simple reactive web components and css framework as well.
At this point, why use HTMX when it really seems like (a heavier) Datastar-lite?
But Datastar is different. The project is literally owned by a 501c3 non-profit. The devs have dayjobs and donate their spare time to this. Funds are for going to conferences or hosting their own
And 99% of the features/value that I mentioned is MIT licensed, and the "rugpulled" code is still available to easily port via the plugin API.
I'm very excited about htmx 4.0 and sounds like things are headed in a promising direction.
The reason to use htmx is that it has a simpler interface optimized for the majority use-case.
With htmx, you are largely tied to a request/reply paradigm. Something happens that triggers a request (e.g. user clicks a button, or some element scrolls into view), htmx sends the request, and then it processes the response. The htmx interface (`hx-get`, hx-trigger`) is optimized to make this paradigm extremely simple and concise to specify.
Datastar's focus (last I checked) is on decoupling these two things. Events may stream to the client at any time, regardless of whether or not they were triggered by a specific action on the client, and they get processed by Datastar and have some effect on the page. htmx has affordances for listening to events (SEE extension, new fetch support) and for placing items arbitrarily on the page (out-of-band swaps) but if your use-case is a video game or a dashboard or something else where the updates are frequently uncorrelated with user actions, Datastar makes a lot of sense. It's a bit like driving a manual transmission.
Delaney is fond of saying that there's no need for htmx when Datastar can technically do everything htmx can [0]. But I think this misses the point of what makes htmx so popular: most people's applications do fit within a largely request/reply paradigm, and using a library that assumes this paradigm is both simpler to implement and simpler to debug. As an htmx maintainer, I often encourage people to even use htmx less than they want to, because the request/reply paradigm is very powerful and the more you can adhere to browser's understanding of it, the more durable and maintainable your website will be [1].
[0] https://data-star.dev/essays/v1_and_beyond
[1] https://unplannedobsolescence.com/blog/less-htmx-is-more/
1. Datastar supports req/reply just fine - be it via normal text/html responses, or SSE (0, 1, or infinity responses) https://data-star.dev/reference/actions#response-handling. So, the crux of your argument is moot...
Moreover, if htmx's real value is ajax request/response, then why are you introducing SSE as a first-class citizen now?
2. Datastar has data-on, and various other attributes, that allow for triggering far more actions than just backend requests, from far more (any) events. I'm glad to see that htmx is now following suit with hx-on, even if it is apparently limited in capabilities.
3. Datastar can do OOB-swaps just fine - that's literally the core functionality, via (their own, faster) idiomorph.
4. Its a misnomer that Datastar is for video games etc - again, as described above, it can do all of the simple things that that HTMX can do, and more. And, again, why is HTMX introducing SSE if its so apparently unnecessary and unwieldy?
5. What makes htmx popular is that it was the first library to make declarative fragment swapping easy. And Carson is just a god-tier marketer. Its nice to see that he's now realized that Delaney was on to something when he wanted to introduce all of these v4 features to HTMX 3 years ago, but was (fortunately for us happy users) forced to go make Datastar instead.
6. We havent even talked about one of the key features - declarative signals. Signals are justifiably taking over all of the JS frameworks and there's even an active proposal to make them part of the web platform. D* makes them simpler to use than any of them, and in a tiny package.
I, Delaney, and all other D* users are grateful for HTMX opening this door. But I reiterate my original question - now that HTMX is becoming Datastar-lite, why not just use Datastar given that the powerful extras don't add any complexity and comes in a smaller package?
I think that even with req/resp morph leads to a simpler majority use case and that's what Turbo and Datastar have both shown. No?
Moreover, the FOSS code still exists and would take 2 minutes to update to the current plugin API (I have Datastar pro and the code is almost exactly the same)
https://github.com/starfederation/datastar/blob/v1.0.0-beta....