Skip to content

Commit b7f833b

Browse files
committed
add article on the fetch()ening
1 parent 20f97a6 commit b7f833b

File tree

2 files changed

+270
-0
lines changed

2 files changed

+270
-0
lines changed
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
+++
2+
title = "The fetch()ening"
3+
description = """\
4+
You know, technically, I never said anything about a version *four*"""
5+
date = 2025-11-01
6+
authors = ["Carson Gross"]
7+
[taxonomies]
8+
tag = ["posts"]
9+
+++
10+
11+
![Stop trying to make fetch() happen](/img/fetch.png)
12+
13+
OK, I said there would never be a version three of htmx.
14+
15+
But, _technically_, I never said anything about a version *four*...
16+
17+
## htmx 4: The fetch()ening
18+
19+
In [The Future of htmx](@/essays/hypermedia-driven-applications.md) I said the following:
20+
21+
> We are going to work to ensure that htmx is extremely stable in both API & implementation. This means accepting and
22+
documenting the [quirks](https://htmx.org/quirks/) of the current implementation.
23+
24+
Earlier this year, on a whim, I created [fixi.js](https://github.com/bigskysoftware/fixi), a hyperminimalist implementation
25+
of the ideas in htmx. That work gave me a chance to get a lot more familiar with the `fetch()` and, especially, the
26+
[async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) infrastructure
27+
available in JavaScript.
28+
29+
In doing that work I began to wonder if that, while the htmx [API](https://htmx.org/reference/#attributes)
30+
is (at least reasonably) correct, maybe there was room for a more dramatic change of the implementation that took
31+
advantage of these features in order to simplify the library.
32+
33+
Further, changing from ye olde [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
34+
(a legacy of htmx 1.0 IE support) to [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) would
35+
be a pretty violent change, guaranteed to break at least *some* stuff.
36+
37+
So I began thinking: if we are going to consider moving to fetch, then maybe we should _also_ that this update as a
38+
chance address at least _some_ of the [quirks](https://htmx.org/quirks/) that htmx has acquired over its lifetime.
39+
40+
So, eventually & reluctantly, I have changed my mind: there _will_ be another major version of htmx.
41+
42+
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.
43+
44+
## Project Goals
45+
46+
With htmx 4.0 we are rebuilding the internals of htmx, based on the lessons learned from
47+
fixi.js and [five+ years](https://www.npmjs.com/package/htmx.org/v/0.0.1) of supporting htmx.
48+
49+
There are three major simplifying changes:
50+
51+
### The fetch()ening
52+
53+
The biggest internal change is that `fetch()` will replace `XMLHttpRequest` as the core ajax infrastructure. This
54+
won't actually have a huge effect on most usages of htmx _except_ that the events model will necessarily change due
55+
to the differences between `fetch()` and `XMLHttpRequest`.
56+
57+
### The Long National Nightmare of Implicit Attribute Inheritance Ends
58+
59+
I feel that my biggest mistake in htmx 1.0 & 2.0 was making attribute inheritance _implicit_. I was inspired by CSS in
60+
doing this, and the results have been roughly the same as CSS: powerful & maddening.
61+
62+
In htmx 4.0, attribute inheritance will be *explicit* rather than *implicit*, via the `:inherited` modifier:
63+
64+
```html
65+
<div hx-target:inherited="#output">
66+
<button hx-post="/up">Like</button>
67+
<button hx-post="/down">Dislike</button>
68+
</div>
69+
<output id="output">Pick a button...</output>
70+
```
71+
72+
Here the `hx-target` attribute is explicitly declared as `inherited` on the enclosing `div` and, if it wasn't, the
73+
`button` elements would not inherit the target from it.
74+
75+
This will be the most significant upgrade change to deal with for most htmx users.
76+
77+
### The Tyranny Of Locally Cached History Ends
78+
79+
Another constant source of pain for both us and for htmx users is history support. htmx 2.0 stores history in local
80+
cache to make navigation faster. Unfortunately, snapshotting the DOM is often brittle because of third-party
81+
modifications, hidden state, etc. There is a terrible simplicity to the web 1.0 model of blowing everything away and
82+
starting over. There are also security concerns storing history information in session storage.
83+
84+
In htmx 2.0, we often end up recommending that people facing history-related issues simply disable the cache entirely,
85+
and that usually fixes the problems.
86+
87+
In htmx 4.0, history support will no longer snapshot the DOM and keep it locally. It will, rather, issue a network
88+
request for the restored content. This is the behavior of 2.0 on a history cache-miss, and it works reliably with
89+
little effort on behalf of htmx users.
90+
91+
We will offer an extension that enables history caching like in htmx 2.0, but it will be opt-in, rather than the default.
92+
93+
This tremendously simplifies the htmx codebase and should make the out-of-the-box behavior much more plug-and-play.
94+
95+
## What Stays The Same?
96+
97+
Most things.
98+
99+
The [core](https://dl.acm.org/doi/10.1145/3648188.3675127) functionality of htmx will remain the same, `hx-get`, `hx-post`,
100+
`hx-target`, `hx-boost`, `hx-swap`, `hx-trigger`, etc.
101+
102+
Except for adding an `:inherited` modifier on a few attributes, many htmx projects will "just work" with htmx 4.
103+
104+
These changes will make the long term maintenance & sustainability of the project much stronger. It will also take
105+
pressure off the 2.0 releases, which can now focus on stability rather than contemplating new features.
106+
107+
## Upgrading
108+
109+
That said, htmx 2.0 users _will_ face an upgrade project when moving to 4.0 in a way that they did not have to in moving
110+
from 1.0 to 2.0.
111+
112+
I am sorry about that, and want to offer three things to address it:
113+
114+
* htmx 2.0 (like htmx 1.0 & intercooler.js 1.0) will be supported _in perpetuity_, so there is absolutely _no_ pressure to
115+
upgrade your application: if htmx 2.0 is satisfying your hypermedia needs, you can stick with it.
116+
* We will create extensions that revert htmx 4 to htmx 2 behaviors as much as is feasible (e.g. Supporting the old implicit
117+
attribute inheritance model, at least)
118+
* We will roll htmx 4.0 out slowly, over a multi-year period. As with the htmx 1.0 -> 2.0 upgrade, there will be a long
119+
period where htmx 2.x is `latest` and htmx 3.x is `next`
120+
121+
## New Features
122+
123+
Of course, it isn't all bad. Beyond simplifying the implementation of htmx significantly, switching to fetch also gives
124+
us the opportunity to add some nice new features to htmx
125+
126+
### Streaming Responses & SSE in Core
127+
128+
By switching to `fetch()`, we can take advantage of its support for
129+
[readable streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams), which
130+
allow for a stream of content to be swapped into the DOM, rather than a single response.
131+
132+
htmx 1.0 had Server Sent Event support integrated into the library. In htmx 2.0 we pulled this functionality out as an
133+
extension. It turns out that SSE is just a specialized version of a streaming response, so in adding streaming
134+
support, it's an almost-free free two-fer to add that back into core as well.
135+
136+
This will make incremental response swapping much cleaner and well-supported in htmx.
137+
138+
### Morphing Swap in Core
139+
140+
[Three years ago](https://github.com/bigskysoftware/idiomorph/commit/7760e89d9f198b43aa7d39cc4f940f606771f47b) I had
141+
an idea for a DOM morphing algorithm that improved on the initial algorithm pioneered by [morphdom](https://github.com/patrick-steele-idem/morphdom).
142+
143+
The idea was to use "id sets" to make smarter decisions regarding which nodes to preserve and which nodes to delete when
144+
merging changes into the DOM, and I called this idea "idiomorph". Idiomorph has gone on to be adopted by many other
145+
web project such as [Hotwire](https://hotwired.dev/).
146+
147+
We strongly considered including it in htmx 2.0, but I decided not too because it worked well as an extension and
148+
htmx 2.0 had already grown larger than I wanted.
149+
150+
In 4.0, with the complexity savings we achieved by moving to `fetch()`, we can now comfortably fit a `morphInner` and
151+
`morphOuter` swap into core, thanks to the excellent work of Michael West.
152+
153+
### Explicit <partial> Tag Support
154+
155+
htmx has, since very early on, supported a concept of "Out-of-band" swaps: content that is removed from the main HTML
156+
response and swapped into the DOM elsewhere. I have always been a bit ambivalent about them, because they move away
157+
from [Locality of Behavior](https://htmx.org/essays/locality-of-behaviour/), but there is no doubt that they are useful
158+
and often crucial for achieving certain UI patterns.
159+
160+
Out-of-band swaps started off very simply: if you marked an element as `hx-swap-oob='true'`, htmx would swap the element
161+
as the outer HTML of any existing element already in the DOM with that id. Easy-peasy.
162+
163+
However, over time, people started asking for different functionality around Out-of-band swaps: prepending, appending,
164+
etc. and the feature began acquiring some fairly baroque syntax to handle all these needs.
165+
166+
We have come to the conclusion that the problem is that there are really _two_ use cases, both currently trying to be
167+
filled by Out-of-band swaps:
168+
169+
* A simple, id-based replacement
170+
* A more elaborate swap of partial content
171+
172+
Therefore, we are introducing the notion of `<partial>`s in htmx 4.0
173+
174+
A partial element is, under the covers, a template element and, thus, can contain any sort of content you like. It
175+
specifies on itself all the standard htmx options regarding swapping, `hx-target` and `hx-swap` in particular, allowing
176+
you full access to all the standard swapping behavior of htmx without using a specialized syntax. This tremendously
177+
simplifies the mental model for these sorts of needs, and dovetails well with the streaming support we intend to offer.
178+
179+
Out-of-band swaps will be retained in htmx 4.0, but will go back to their initial, simple focus of simply replacing
180+
an existing element by id.
181+
182+
### Improved View Transitions Support
183+
184+
htmx 2.0 has have [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API) support since
185+
[April of 2023](https://github.com/bigskysoftware/htmx/blob/master/CHANGELOG.md#190---2023-04-11). In the interceding
186+
two years, support for the feature has grown across browsers (c'mon, safari, you can do it) and we've gain experience
187+
with the feature.
188+
189+
One thing that has become apparent to us while using them is that, to use them in a stable manner, it is important
190+
to establish a queue of transitions, so each can complete before the other begins. If you don't do this, you can get
191+
visually ugly transition cancellations.
192+
193+
So, in htmx 4.0 we have added this queue which will ensure that all view transitions complete smoothly.
194+
195+
CSS transitions will continue to work as before as well, although the swapping model is again made much simpler by the
196+
async runtime.
197+
198+
We may enable View Transitions by default, the jury is still out on that.
199+
200+
### Stabilized Event Ordering
201+
202+
A wonderful thing about `fetch()` and the async support in general is that it is _much_ easier to guarantee a stable
203+
order of events. By linearizing asynchronous code and allowing us to use standard language features like try/catch,
204+
the event model of htmx should be much more predictable and comprehensible.
205+
206+
We are going to adopt a new standard for event naming to make things even clearer:
207+
208+
`htmx:<phase>:<system>[:<optional-sub-action>]`
209+
210+
So, for example, `htmx:before:request` will be triggered before a request is made.
211+
212+
### Improved Extension Support
213+
214+
Another opportunity we have is to take advantage of the `async` behavior of `fetch()` for much better performance in our
215+
preload extension (where we issue a speculative (`GET` only!) request in anticipation of an actual trigger). We have
216+
also added an optimistic update extension to the core extensions, again made easy by the new async features.
217+
218+
In general, we have opened up the internals of the htmx request/response/swap cycle much more fully to extension developers,
219+
up to and including allowing them to replace the `fetch()` implementation used by htmx for a particular request. There
220+
should not be a need for any hacks to get the behavior you want out of htmx now: the events and the open "context" object
221+
should provide the ability to do almost anything.
222+
223+
### Improved `hx-on` Support
224+
225+
In htmx 2.0, I somewhat reluctantly added the [`hx-on`](https://htmx.org/attributes/hx-on/) attributes to support light
226+
scripting inline on elements. I added this because HTML does not allow you to listen for arbitrary events via `on`
227+
attributes: only standard DOM events like `onclick` can be responded to.
228+
229+
We hemmed and hawed about the syntax and so, unfortunately, there are a few different ways to do it.
230+
231+
In htmx 4.0 we will adopt a single standard for the `hx-on` attributes: `hx-on:<event name>`. Additionally, we are
232+
working to improve the htmx JavaScript API (especially around async operation support) and will make those features
233+
available in `hx-on`:
234+
235+
```html
236+
<button hx-post="/like"
237+
hx-on:htmx:after:swap="await timeout('3s'); ctx.newContent[0].remove()">
238+
Get A Response Then Remove It 3 Seconds Later
239+
</button>
240+
```
241+
242+
htmx will never support a fully featured scripting mechanism in core, we recommend something like
243+
[Alpine.js](https://alpinejs.dev/) for that, but our hope is that we can provide a relatively minimalist API that
244+
allows for easy, light async scripting of the DOM.
245+
246+
I should note that htmx 4.0 will continue to work with `eval()` disabled, but you will need to forego a few features like
247+
`hx-on` if you choose to do so.
248+
249+
### A Better But Familiar htmx
250+
251+
All in all, our hope is that htmx 4.0 will feel an awful lot like 2.0, but with better features and, we hope, with fewer bugs.
252+
253+
## Timeline
254+
255+
As always, software takes as long as it takes.
256+
257+
However, our current planned timeline is:
258+
259+
* An alpha release is available _today_: `[email protected]`
260+
* A 4.0.0 release should be available in early-to-mid 2026
261+
* 4.0 will be marked `latest` in early-2027ish
262+
263+
You can track our progress (and see quite a bit of dust flying around) in the `four` branch on
264+
[github](https://github.com/bigskysoftware/htmx/tree/four) and at:
265+
266+
<https://four.htmx.org>
267+
268+
Thank you for your patience and pardon our dust!
269+
270+
> "Well, when events change, I change my mind. What do you do?" --Paul Samuelson or John Maynard Keynes

www/static/img/fetch.png

375 KB
Loading

0 commit comments

Comments
 (0)