htmx — инструмент для создания сложных и интерактивных веб-приложений на HTML, альтернатива клиентскому рендерингу на Javascript. В этой статье рассказываем, как библиотека помогает переиспользовать элементы на сервере, сократить объем кода на Javascript и отказаться от сборки.
Это адаптированный перевод статьи htmx and HTML Driven Development фронтенд-разработчика Раджасегара Чандрана. Повествование ведется от лица автора оригинала.
HTML как центр вселенной
Интернет в том виде, в котором мы знаем его сегодня, во многом существует благодаря HTML и CSS. Javascript (JS) мог стать связующим звеном между ними и сделать страницы более динамичными и интерактивными. Но история веб-программирования развивалась иначе — после появления клиентского рендеринга и других подобных технологий, использовать JS для создания веб-приложений стало заметно сложнее.
Что такое htmx
htmx — библиотека, которая позволяет создавать современные и мощные пользовательские интерфейсы с простой разметкой. С ее помощью AJAX-запросы, запуск CSS-переходов, вызов WebSocket и событий, отправленных сервером, можно проводить непосредственно из элементов HTML.
Я начинал с фуллстэк-разработки, поэтому использование HTML для создания веб-страниц казалось мне естественным. После перехода во фронтенд я быстро разочаровался в клиентском рендеринге на базе JS — технология создавала слишком много проблем.
После интерфейсных фреймворков (например, Ember, React, Svelte) мне гораздо приятнее работать с HTML и hyperscript в связке с htmx. Он позволяет создавать веб-приложения, ориентированные на серверный рендеринг (SSR), и сильно увеличивает мою продуктивность.
SSR-приложения
Использование htmx мотивирует постепенно отказываться от клиентского рендеринга в пользу серверного. Считается, что SSR — это последнее средство, которое используют только когда необходимо быстро повысить производительность. Однако на серверном рендеринге может строится весь пользовательский интерфейс приложения.
htmx не требует для запуска других пакетов JS и она независима от фреймворков или языков. Поэтому использовать ее можно с любой серверной платформой: например, с Node Express, RAILS, Django, Phoenix, Laravel и другими.
Переиспользование компонентов на сервере
htmx позволяет переиспользовать элементы пользовательского интерфейса на стороне сервера с помощью более привычных библиотек: например, pug для Node или библиотек шаблонов для RAILS и Django. Это помогает сделать HTML сложным и динамичным.
Вот пример демонстрационного веб-приложения Rentals Listing, созданного на Express.js и HTML. Один и тот же паршиал в нем используется и для статистических, и для динамических сценариев:
ul.results
each rental in rentals
li
article.rental
button.image(type="button", _="on click toggle .large then if #view-caption.textContent === 'View Larger' then set #view-caption.textContent to 'View Smaller' else set #view-caption.textContent to 'View Larger'")
img(src=rental.attributes.image, alt='An image of ' + rental.attributes.title)
small#view-caption View Larger
.details
h3
a(href='/rentals/' + rental.id) #{rental.attributes.title}
.detail.owner
span Owner:
| #{rental.attributes.owner}
.detail.type
span Type:
| #{rental.attributes.category}
.detail.location
span Location:
| #{rental.attributes.city}
.detail.bedrooms
span Bedrooms:
| #{rental.attributes.bedrooms}
.map
img(alt='A map of ' + rental.attributes.title, src=rental.mapbox, width="150",height="150")
В листинге домашней страницы для отображения паршиала я использовал include из библиотеки pug:
extends layout
block content
.jumbo
.right
h2 Welcome to Super Rentals!
p We hope you find exactly what you're looking for in a place to stay.
a.button(href="/about") About Us
.rentals
label
span Where would you like to stay?
input.light(type="text", name="search",
hx-post="/search" ,
hx-trigger="keyup changed delay:500ms" ,
hx-target=".results" ,
hx-indicator=".htmx-indicator")
include includes/rental-list.pug
Каждый раз, когда пользователь ищет жилье в аренду на сайте, я использую один и тот же паршиал для заполнения результатов поиска. Результат выглядит так:
app.post('/search', (req, res) => {
const { search } = req.body;
const results = _rentals.data.filter(r => {
const _search = search.toLowerCase();
const _title = r.attributes.title.toLowerCase();
return _title.includes(_search);
});
const template = pug.compileFile('views/includes/rental-list.pug');
const markup = template({ rentals: results });
res.send(markup);
});
Маршрутизация на стороне сервера
Маршрутизация на стороне клиента создает целый набор проблем. Например, всегда существует дилемма между маршрутизацией на основе хэша и URL. Поскольку history api не поддерживается в старых браузерах (таких как Internet Explorer 11), предпочтение почти всегда отдается маршрутизации на основе хэшей, которая использует идентификаторы фрагментов в URL-адресе.
Большинство фреймворков JS реализуют собственную логику маршрутизации на стороне клиента. При этом под всеми фреймворками используется собственный API браузера: таких как window.history. Это приводит к появлению большого количества шаблонного кода в приложении.
Меньше кода на JS
На мой взгляд, главное преимущество htmx — количество кода на JS, который мы пишем и отправляем в браузер. Вместе с hyperscript библиотека позволяет создавать многофункциональные интерактивные приложения без использования клиентского кода на JS:
<!-- have a button POST a click via AJAX -->
<button hx-post="/clicked" hx-swap="outerHTML">
Click Me
</button>
Когда одностраничные приложения только начали входить в моду, сообщество приняло JSON в качестве стандарта обмена для данными. Теперь, чтобы реконструировать HTML из данных JSON, часто требуется обработать большой объем данных на стороне клиента, которые приходят с сервера через API. В ответах API часто содержатся либо неполные, либо избыточные данные.
Для решения этой проблемы разработаны сложные альтернативы вроде GraphQL, благодаря которым можно получать от сервера только нужные данные. htmx решает проблему лучше: достаточно заменить HTML на HTML-ответ, полученный от сервера. Больше никаких данных на стороне клиента.
Отсутствие сборки/компиляции
Еще одно преимущество htmx — отсутствие инструментов сборки веб-приложений. Вы можете использовать инструмент через CDN:
<!-- Load from unpkg -->
<script src="<https://unpkg.com/htmx.org@1.3.3>"></script>
Отсутствие сборки — глобальный тренд в разработке веб-приложений. С одной стороны, спецификации модулей ES приняли все разработчики браузеров, с другой — появились инструменты Skypack , Snowpack , Vite, которые сочетаются с подходами CDN и ESM. Все это в конечном счете приведет к тому, что мы будем видеть сборку для Javascript на стороне клиента все реже и реже. Добавьте к этому отсутствие необходимости устанавливать тысячи пакетов npm и поддерживать сложные конфигурации сборки.
Единая база кода
Две базы кода для одного приложения — дополнительные проблемы при разработке. В частности, нужно синхронизировать развертывания обновлений, дважды настраивать конвейер сборки, обновлять фреймворк в обеих базах, поддерживать код и запускать тестовые пакеты из двух источников.
htmx позволяет объединить весь код в одном месте: поскольку рендеринг происходит на стороне сервера, отдельная база для интерфейса не нужна. В долгосрочной перспективе это поможет сэкономить много времени и денег. Кроме того, разработчики смогут действовать более согласованно — им не придется проверять два и более репозиториев.
Принцип локальности поведения (LoB)
Принцип LoB сформулировал теоретик программирования Ричард Гэбриэлл. Он гласит, что все разработчики должны стремиться к тому, что поведение каждого элемента кода должно быть очевидным при его проверке.
Локальность, по словам Гэбриэлла, — главное условие для простоты поддержки кода. Локальность — это характеристика, которая позволяет программисту понять, к какой части архитектуры принадлежит код, увидев лишь небольшую его часть.
Выглядит это так:
The behaviour of a code unit should be as obvious as possible by looking only at that unit of code
<div hx-get="/clicked">Click Me</div>
LoB часто конфликтует с другими принципами разработки ПО: например, с разделением ответственности. Частично этот конфликт был разрешен, когда React представил HTML и CSS в Javascript. Создатель React Пит Хант при этом считает, что речь идет о разделении технологий:
Отсуствие проблем с синхронизацией состояний
Управление состоянием на стороне клиента создает больше проблем, чем решает. Внедрение этого принципа приводит к тому, что управлять состоянием нужно и на стороне клиента, и на стороне сервера. Альтернативное решение — хранить состояние на сервере. В этом случае клиент служит фиктивным исполнителем для рендеринга изменения состояний.
Это похоже на модель тонкого клиента — недокомпьютера с легкой операционной системой, который соединяется с терминальным сервером. Такие устройства использовали для создания первый веб-приложений ради экономии ресурсов.
htmx поможет избежать запутывания кода пользовательского интерфейса в сети управления состоянием: например, в двусторонней привязке данных, однонаправленном потоке данных, реактивных данные.