|
| 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 | + |
| 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 |
0 commit comments