Команда htmx объявила о выходе версии 4.0.0. Релиз стал итогом восьми месяцев работы (не считая отдельно разработанной игры), и результатом в команде довольны.

Идея htmx 4 начала формироваться, когда один из разработчиков занялся созданием проекта fixi и в процессе глубже разобрался с fetch() API и асинхронным программированием в JavaScript. Раньше htmx всегда использовал XMLHttpRequest — из соображений обратной совместимости.

Однажды вечером с ним связался Кристиан, у которого были интересные идеи по поводу потоковой передачи HTML. Это натолкнуло на мысль, что перенос внутренней логики на fetch() упростит задачу и для него, и для библиотеки в целом.
После некоторой работы к проекту удалось подключить Майкла и Алекса, и разработка пошла полным ходом.

Процесс шёл гладко. Начали с портирования fixi и тестового набора htmx. Со временем команда заново открыла для себя причины, по которым htmx в своё время был устроен именно так, и новая реализация постепенно приближалась к старой. На данный момент поведенческие различия между 2.x и 4.x относительно невелики, а там, где они всё же есть, были сделаны осознанные решения — с расчётом на то, что приложения на htmx останутся жизнеспособными в духе концепции веб-сервисов на 100 лет.

Версия 4.0 не помечена в NPM как latest — команда не хочет принудительно обновлять пользователей, полагающихся на неверсионированные CDN-ссылки на htmx. Тег latest пока сохранится за веткой 2.x, а 4.0 будет помечена как next примерно до начала 2027 года. При этом сам сайт будет ссылаться на версию 4.0.

Основные изменения

Как уже отмечалось, с точки зрения пользователя htmx 4 почти идентичен htmx 2. Есть три главных изменения:

  • Наследование атрибутов теперь по умолчанию явное, а не неявное (это самый трудоёмкий пункт при обновлении)
  • Названия событий htmx стандартизированы и приведены в порядок. Некоторым продвинутым пользователям придётся изменить обработчики событий
  • Поддержка истории по умолчанию больше не использует localStorage (что раньше было источником немалого числа проблем). Большинство пользователей вообще не заметят этой перемены

Внутри библиотека перешла с XMLHttpRequest на fetch(), но для большинства пользователей htmx это должно быть незаметно.

Наследование атрибутов

В htmx 2 многие атрибуты по умолчанию «наследовались». Это позволяло размещать атрибуты на родительских элементах, а их поведение распространялось на дочерние. Такой подход, доставшийся в наследство от intercooler.js, был вдохновлён CSS и, что неудивительно, дал похожий результат: мощно, но временами трудно понять.

В htmx 4 атрибуты не наследуются, если это не указано явно — добавлением суффикса :inherited после имени атрибута:

<!-- htmx 2 -->
<div hx-confirm="Are you sure?">
    <button hx-delete="/item/1">Delete</button>
</div>

<!-- htmx 4 -->
<div hx-confirm:inherited="Are you sure?">
    <button hx-delete="/item/1">Delete</button>
</div>

Это станет самым трудоёмким пунктом при переходе с htmx 2 на htmx 4. Чтобы упростить процесс, разработчики подготовили утилиту командной строки, которая находит места, требующие пометки как наследуемые.

Атрибуты вроде hx-disinherit больше не нужны, и их следует удалить.

События

Набор событий, которые генерировал htmx 2, разрастался органически на протяжении всей истории библиотеки и не был особенно хорошо организован — было сложно точно понять, какое событие срабатывает в какой момент.

В htmx 4 все события теперь следуют схеме htmx:phase:action[:sub-action]:

htmx 2htmx 4
htmx:beforeRequesthtmx:before:request
htmx:afterRequesthtmx:after:request
htmx:beforeSwaphtmx:before:swap
htmx:afterSwaphtmx:after:swap
htmx:configRequesthtmx:config:request

Кроме того, внесены следующие изменения:

  • Большинство событий об ошибках объединены в htmx:error. HTTP-ошибки ответа сервера генерируют htmx:response:error
  • События htmx:xhr:* удалены — htmx 4 использует fetch()
  • События htmx:validation:* удалены в пользу нативной валидации форм браузера

Полная таблица соответствий приведена в разделе «Что нового в htmx 4».

Утилита проверки при обновлении помечает старые названия событий в атрибутах hx-on и в JavaScript-коде, если может их найти.

История

Поддержка истории браузера всегда была частью htmx и позволяла реализовать логику, реагирующую на кнопку «назад», с помощью простых атрибутов. В htmx 2 для восстановления страниц использовался кэш в localStorage, куда сохранялся снимок DOM. К сожалению, немалая часть проблем была связана с тем, что этот снимок мог включать мутации DOM, внесённые сторонними JavaScript-библиотеками. При восстановлении страницы эти мутации оставались, а вот логика JavaScript, которая их производила, — нет.

htmx 4 больше не кэширует страницы в localStorage. При переходе назад htmx заново запрашивает страницу и подставляет её в <body> либо в элемент [hx-history-elt], если он присутствует. Благодаря этому сторонние JavaScript-библиотеки в большинстве случаев работают «из коробки», а при хорошем кэшировании запросов процесс происходит очень быстро.

Для тех, кто предпочитает локальное кэширование, теперь доступно расширение hx-history-cache — оно восстанавливает историю из sessionStorage и спроектировано так, чтобы хорошо интегрироваться с такими решениями для скриптинга, как Alpine.js.

Новые возможности

В htmx 4 появились две крупные новые функции, которыми в команде особенно гордятся.

Morph-свопы

Теперь htmx поддерживает morph-свопы «из коробки». Ранее был создан проект idiomorph, который чуть было не включили в htmx 2.x, но тогда от этой идеи отказались. В htmx 4 Майкл проделал большую работу по улучшению этого алгоритма и его бесшовной интеграции в библиотеку.

Тег <hx-partial>

Ещё одна значимая новая функция — тег <hx-partial>. Он похож на внеполосные свопы (out-of-band swaps), но гораздо нагляднее, когда требуется сделать что-то более сложное, чем просто заменить один элемент новой версией самого себя:

<hx-partial hx-target="#messages" hx-swap="beforeend">
    <div>New message</div>
</hx-partial>
<hx-partial hx-target="#count">
    <span>5</span>
</hx-partial>

Расширения

Значительная часть новинок htmx 4 связана именно с расширениями. Переход на fetch() изнутри позволил переосмыслить, как расширения могут и должны работать, что дало толчок к созданию (и пересозданию) множества новых расширений, например:

  • hx-preload — предзагрузка контента (например, по mouseover) для ускорения запросов
  • hx-download — нативная загрузка файлов на базе fetch()
  • hx-alpine-compat — сглаживает проблемы совместимости между htmx и Alpine.js
  • hx-history-cache — кэширует историю в sessionStorage, обеспечивает совместимость с Alpine.js

Кроме того, появились три новых или обновлённых расширения для потоковой передачи HTML:

  • hx-sse — потоковая передача через text/event-stream
  • hx-ws — потоковая передача и отправка через WebSockets
  • hx-multipart — потоковая передача через multipart/mixed

Наконец, команда решила, что настало время попробовать создать собственное небольшое решение для скриптинга на фронтенде, тесно интегрированное с htmx. hx-live вдохновлён Alpine.js, jQuery и hyperscript и делает написание фронтенд-скриптов приятным занятием. Он даже поддерживает то, что в команде называют DOM-based, HATEOAS-дружественной реактивностью.

В дистрибутив также добавлен новый пакет htmax.js, который объединяет htmx с наиболее популярными из этих расширений в одном файле — на случай, если не хочется выбирать нужные вручную.

Обновление

Полное руководство по обновлению доступно в разделе «Что нового в htmx 4».

Как уже упоминалось, для помощи в обновлении предоставляется специальная утилита:

$ npx htmx.org@4.0.0 upgrade-check -- ./templates

File extensions: .html, .php, .js, .ts, .jinja, .jinja2, .j2, .erb, .hbs
Use --ext to add more (e.g. --ext .vue --ext .svelte)

Scanning 1 file(s)...

Found 8 issue(s) in 1 of 1 file(s).
templates/index.html:1: [inheritance] hx-headers needs :inherited suffix (descendant on line 3 has hx-delete) (this looks like a CSRF token; without :inherited the header does not reach child elements and the server rejects the request)
templates/index.html:2: [inheritance] hx-target needs :inherited suffix (descendant on line 3 has hx-delete)
templates/index.html:2: [inheritance] hx-confirm needs :inherited suffix (descendant on line 3 has hx-delete)
templates/index.html:3: [renamed-attr] hx-disable -> rename to hx-ignore (hx-disable now means 'disable during request')
templates/index.html:4: [removed-attr] hx-vars is removed -> use hx-vals with js: prefix
templates/index.html:4: [removed-attr] hx-prompt is removed -> load the hx-prompt extension to keep the same syntax
templates/index.html:9: [old-event] old event name "htmx:afterRequest" -> "htmx:after:request"
templates/index.html:9: [old-api] htmx.addClass() is removed -> use element.classList.add()

Также подготовлен специальный навык (skill) для агентов, который помогает с обновлением.

Установка

htmx 4.0 можно установить через пакетный менеджер, указав версию 4.0.0, либо подключить через CDN:


<script src="https://unpkg.com/htmx.org@4.0.0/dist/htmx.min.js"></script>

или скачать напрямую.

LLM

Нравится это или нет, но многие используют LLM, и для них подготовлены следующие файлы навыков:

Хорош или плох сам факт выпуска новой версии библиотеки в эпоху LLM — вопрос отдельный.

Заключение

Команда надеется, что htmx 4 придётся по душе пользователям. Поддержка htmx 2 продолжится на неопределённый срок, так что спешить с обновлением не обязательно.

Отдельная благодарность людям, без которых этот релиз был бы невозможен:

  • Michael West — невероятный товарищ по команде и разработчик с «умом простака» (grug-brained)
  • Christian Tanul — вдохновил на создание htmx 4 и возглавил работу над потоковыми и live-расширениями
  • Alex Petros — держал курс проекта в ровном русле
  • Stephen Mitchell — гений, стоящий за игрой
  • Stu Kennedy — эксперт команды по WebSockets
  • André Ahlert Jr. — обеспечил поддержку в IDE и редакторах
  • Dien Hoa Truong — тестировал ранние версии htmx 4 и помог исправить множество багов

Музыка для апгрейда

Какой же релиз htmx без музыки для апгрейда: