Upgrading to from HTMX1 to HTMX4
I recently saw the htmx 4 release announcement and wanted to give the upgrade a try. While, I’ve been following the project for many years, but docket was using HTMX 1.x. With the release of HTMX 4, I wanted to upgrade. While docket is not that complicated of an application, it does rely on several custom HTMX extensions, which I used as a way to bridge libraries like SortableJs and marked into the declarative HTML world view that HTMX lets one have.
HTMX Upgrade tool
I was pleased to discover that htmx provides an upgrade tool:
- npx htmx.org@4.0.0 upgrade-check -- ./templates
This tool helped identify places I was relying on deprecated features like attribute inheritance, and attributes that were renamed. This helped me find and fix my usage of hx- attributes.
Updating extensions
A significant part of docket’s behavior is in HTMX extensions. While the htmx upgrade tool was able to identify that I was using deprecated events and methods, updating the implementation of my extensions was a manual process. Parts of those changes were straight forward, like renaming htmx.defineExtension() to htmx.registerExtension(). The changes to extensions, also required refactoring the generic onEvent hook into a collection of htmx_* hook methods. I was never a fan of having switch statements over the event types in onEvent, so the explicit hook methods are a nice ergonomic improvement. The new hook methods have different signatures that required reworking the logic of each extension. The following diff is a simple example of the changes needed:
- htmx.registerExtension('ajax-header', {
- - htmx_config_request: function (evt: Event) {
- - evt.detail.headers['X-Requested-With'] = 'XMLHttpRequest';
- - evt.detail.headers['X-Csrf-Token'] = document.getElementById('csrf-token').getAttribute('content');
- + htmx_config_request: function (element: HTMLElement, detail) {
- + detail.ctx.request.headers['X-Requested-With'] = 'XMLHttpRequest';
- + detail.ctx.request.headers['X-Csrf-Token'] = document.getElementById('csrf-token').getAttribute('content');
- },
- });
These changes weren’t covered in the upgrade guide, but also weren’t hard to figure out after consulting the extension docs for the new release.
Extension element processing
Under htmx1 extensions would only be passed the elements that had a matching hx-ext="ajax-header" attribute on them. However, in htmx4, I found that extension’s htmx_after_process hooks would process the entire page, or HTML fragment that was being swapped in. In order to get my extension behavior to only apply to nodes with the matching attributes, I needed to filter elements with an attribute selector:
- htmx.registerExtension('hx-flash-message', {
- htmx_after_process: function (element: HTMLElement, _detail) {
- for (const item in element.querySelectorAll('[hx-flash-message]')) {
- processItem(item);
- }
- },
- });
This is could be a result of me using htmx1 “wrong” in the past, but it was a change I needed to make to get behavior working under htmx4.
htmx.process() ordering
One part of docket renders ‘templates’ client side and then inserts them into the DOM using innerHTML. Under htmx1, htmx attributes would ‘just work’. With htmx4, I needed to call htmx.process() on the nodes only after the node was inserted into the DOM:
- - htmx.process(reveal);
- attachRevealEvents(reveal);
- this.appendChild(reveal);
- + htmx.process(reveal);
Overall, upgrading 3 major versions at once was pretty smooth, and htmx will continue to be my weapon of choice for building fast, efficient hypermedia applications.
There are no comments, be the first!