Этот пост — попытка объединить заметки об HTMX в Телеграме во что-то целостное. Написать статью о нем за один подход не вышло, поэтому пришлось по частям.

HTMX — это библиотека для серверного рендера HTML. От современных React и Vue она отличается тем, что не хранит состояние на клиенте и не занимается рендером на JavaScript. Она посылает запрос к серверу и получает кусок HTML — в особых случаях несколько кусков, — которые вставляет в нужное место DOM.

При всей примитивности этого подхода у него есть сторонники, и их число растет. Я тоже склоняюсь к серверному рендеру и хочу написть о нем подробнее.

Htmx — не единственная в своей области библиотека. Есть еще HotWire, LiveView и другие. Под HTMX я имею в виду не конкретную библиотеку, а серверный рендер в принципе; просто HTMX звучит короче.

Первая заметка об HTMX касалась числа запросов на сервер. Порой говорят: HTMX требует больше запросов, потому что забирает состояние с сервера. А в React-приложении ты выгреб состояние и хранишь на клиенте — улыбаешься.

Это неправда. Когда в HTMX открываешь диалог, происходит один — и только один — запрос. Когда открывается диалог в приложении на Vue, происходит 14, — прописью, четырнадцать — запросов. Буквально на днях я правил фронтенд по работе, и там был именно такой случай. Цифры я взял какие есть, без преувеличений.

Другой пример: дашбоард сайта Use Multiplier совершает 235 запросов. Если отфильтровать по слову “graphql”, останется 35 запросов. Выходит, JavaScript-разработчикам нужно 35 сетевых сессий, чтобы построить документ. Не многовато ли?

Что же выгодней: один запрос или их десятки?

В такие минуты искренне желаю, чтобы фронтендеров заменил HTMX. Они делают интернет хуже: больше запросов, больше трафика, больше расход CPU, дольше время ожидания. Таким разработчикам не место в профессии.

Вторая мысль об HTMX такая: часто говорят, что это “хайп” (в кавычках, потому что не люблю это слово). Очередная вещь, которая пройдёт зенит и уйдёт из повестки.

Это не так. HTMX — это не очередная JS-поделка, снискавшая популярность. HTMX и его аналоги — это протест. Отказ от того, что предлагает индустрия для разработки приложений в браузере. Протест — по определению более сильная вещь, чем хайп, потому что в его основе идея.

В этом году исполняется 10 лет с публикации исходного кода React.js. У Реакта и аналогов было достаточно времени, чтобы показать себя. Результаты есть, но не ошеломительные. Да, по сравнению с Backbone.js делать интерфейс стало проще, однако рано или поздно все сводится к одному: проект тонет в каше из компонентов, а состояние разбросано, как субстанция, попавшая на вентилятор.

Сейчас работаю с Vue, и чтобы добавить новый диалог, нужно поправить 18 файлов. Такая структура явно говорит о проблемах, но никого это не волнует. Отрефакторить это невозможно, потому что поедет все. Только переписать на новый фреймворк, что обязательно состоится, когда бизнес купят или команда фронтендеров уйдет в полном составе.

За эти десять лет мы получили одно — возросшую сложность разработки. Современный сайт обязательно содержит пачку npm-модулей, их сборщик, компоненты на React/Vue и бог знает что еще. В каждой фирме эту кухню готовят по-разному; вся она глючная и хрупкая.

Еще печальней, что фронтендеры, получив инструменты, не научились ими пользоваться. Фронтам нужно 35 запросов Graphql на страницу; 14 запросов на открытие диалога. Одна и та же сущность запрашивается по пять раз. Это полная капитуляция, расписка непригодности разработчика. Если нужно 35 запросов на страницу, в команде не думают о качестве, а просто закрывают тикеты. Это просто данность.

За это время я видел только одну удобную обертку над React — это кложурный re-frame. В нем скрыли острые углы Реакта и свели разработку к простому принципу: база, подписка, событие. Просто настолько, что дальше уже невозможно. Конечно, и на re-frame можно сделать проект неподдерживаемым, но кривая сложности у него более покатая.

HTMX и его аналоги — это отказ от всего, описанного выше. Это отказ от хрупких npm/js-артефактов. Это отказ от команды фронтендеров, которым платят 10-20 тысяч долларов в месяц — на эту сумму можно купить тысячи машин в облаке. Это отказ от сложности коммуникаций, когда бекенд выкатил фичу, а фронты доберутся до нее через месяц. Это отказ от двух источников правды — сервера и состояния на клиенте — в пользу одного. И много от чего другого отказ.

Поэтому я верю, что HTMX и схожие проекты с нами надолго. Эпоха Реакта и реактивного интерфейса проходит. Результаты есть, но они обходятся слишком дорого. Время нового подхода.

Третья мысль об HTMX касается его роли в прошлом. Мысль о том, что HTMX появился недавно и поэтому хайп — ошибочна. На самом деле серверный рендер существует давно, и некоторые его используют. До сих пор о нём было мало разговоров, потому что в центре внимания Реакт — и сумасшедшие деньги, которые вливал в него производитель. Не только на зарплату разработчикам, но и поддержку, связи с сообществом, конференции, митапы и прочее.

Серверный рендер по существу очень прост. Сервер выдает HTML, который вставляется в нужное место DOM. Кустарная реализация занимает не более ста строк, поэтому неудивительно, что серверный рендер тихо делает свою работу — без кричащих статей на Hacker News, звезд на Гитхабе и остального.

Другое дело, что кустарное решение учитывает только свои нюансы, поэтому нужен общий знаменатель. Так и появились проекты HTMX и аналоги.

Не каждый знает, что серверный рендер был одно время в Твиттере. Первые 20 твитов грузились как часть страницы, а при скролле выгребались куски HTML, в которых были новые 20 твитов и так далее. Хорошо это помню, потому что у меня был пет-проект по парсингу Твиттера. С HTML было относительно удобно: распарсил, прошелся Xpath-селекторами и готово. Серверный рендер в Твиттере ввели после того, как люди стали массово жаловаться на клиентский рендер — он был столь ужасен, что пара вкладок раскручивала вентиляторы на полную.

Пользуясь случаем, скажем отдельное спасибо фронтендерам за это.

Кроме Твиттера, серверный рендер использовал ваш покорный слуга. Было это 13 лет назад в Чите, в славной компании ООО Энергосбыт. Возле моего кабинета стоял терминал для показа каких-то роликов. Я решил сделать из него облегченную версию личного кабинета без авторизации. Вводишь лицевой счет и номер квартиры и видишь задолженность и показания счетчиков. Можно передать текущие показания. Принять деньги, увы, было нельзя из-за отсутствия купюроприемника.

Интерфейс терминалов — это обычный Хром, запущенный с ключом --kiosk (по крайней мере раньше он так назывался). В режиме киоска браузер растягивается на весь экран, прячет курсор и противится попыткам свернуть его или открыть системные менюшки. Беда в том, что если во время запроса обрывалась связь, Хром показывал страницу “Oh no!” с диназавром, и нужно было нажать кнопку “Обновить”. Сейчас Хром обновляет ее по таймауту, а тогда нет.

Ясное дело, я не мог бегать каждый час к терминалу смотреть, все ли с ним в порядке. Нужно, чтобы все работало на аяксе, но одна мысль об этом бросала в ужас. На дворе 2009 год, никаких реактов нет и в помине. jQuery в зените популярности, развивается Backbone.js — попытка навертеть MVT-фреймворк поверх jQuery. Окинув этот зоопарк, я решил задачу проще: на каждый запрос отвечал куском HTML, который вставлялся в центральный элемент на странице. Это было неоптимально, потому что в обновлении нуждались не все элементы, но разницы не было.

В итоге я контролировал все на сервере, а клиент был минимально прост. Разработка заняла неделю, и вскоре терминал стоял в самом проходном месте города. Им пользовались, все было хорошо. Ради интереса представил разработку сегодня: npm/yarn, React/Vue, папка node_modules, хрупкие сборки и конечно компоненты, компоненты, компоненты. Команда фронтендеров, недели и месяцы работы. Неподдерживаемый результат в конце, смена команды и переписывание.

Вот почему я уверен в своей правоте: я пробовал и знаю. Серверный рендер — это просто и быстро. Да, есть недостатки, и порой без клиентского кода не обойдешься. Но ни Реакт, ни Вуй даже близко не приблизились в плане скорости и удобства к серверному рендеру.

Четвертый пост об HTMX касается его не очевидных недостатков. Прочитав текст выше, кто-то может решить, что HTMX — это избавление от всех бед фронтенда. К сожалению, это не так. Ниже я перечислю, какие трудности вас ждут при переходе на HTMX.

Прежде всего, HTMX не подходит крайне динамичным страницам — картам, редакторам текста, играм. Их объединяет сложное состояние, которое меняется даже не клику, а по движению курсора или прокрутке. Опрашивать сервер с такой частотой будет тяжело, хотя и возможно: в HTMX есть поддержка веб-сокетов. Всё зависит от условий задачи, но для сложного состояния HTMX будет скорее проблемой.

Второй момент — с HTMX усложняется серверный рендер. Раньше вы отдавали данные клиенту, а как он их выводил, вас не касалось. А теперь вы за это в ответе.

Предположим, вы достали из базы число покупок пользователя. Чтобы вывести фразу “У вас X покупок”, нужно учесть случаи:

  • X равно нулю или NULL, и тогда фраза будет “у вас нет покупок”;

  • учесть множественность: “1 покупка” и “N покупок”;

  • для второго случая учесть склонения: “3 покупки, 5 покупок, 21 покупка”.

Нужно писать функции, таблицы склонений, стемминг и остальное. Что-то есть в библиотеках, но без их адаптации не обойтись.

Еще интересней даты и форматы чисел, валют. Раньше ты выплюнул JSON с Unix timestamp, и остальное тебя не касается. А теперь, перед тем как рендерить, нужно узнать локаль пользователя — из запроса, кук или сессии — и передать ее во все подобные функции. Это усложняет процесс.

Положа руку на сердце, гораздо удобней отдать JSON, не заморачиваясь с рендером. Но потом, когда фронтендер уйдет или заболеет, ты полезешь в его код и увидишь, какой же там ад. И что лучше бы это был серверный рендер.

HTMX усложняет обработку ошибок. Если что-то пошло не так, нужно вывести сообщение внутри элемента или в отдельной области при помощи техники OOB — out of boundaries. Это когда сервер возвращает несколько HTML-элементов, и они вставляются в разные места страницы. Кроме того, можно задать реакцию на неудачный AJAX-запрос на уровне библиотеки. Я не буду подробно все расписывать, скажу лишь, что об обработке ошибок в HTMX нужно думать с самого начала и придерживаться одного подхода. Иначе будет разброд и шатание.

Тесты. Одно дело тестировать JSON, другое – HTML. Его нужно парсить, обходить проблемы склонений и форматов, применять рекулярки и XPath. Все это менее удобно, чем данные в JSON. Схемы – ваш лучший друг в случае с JSON, а какие схемы могут быть в HTML?

Последний и наиболее важный недостаток — это двойной API. Как правило, бекенд предоставляет апишку не только сайту, но и мобильному приложению и третьим фирмам. Удобно, когда бекенд — это чистое API, и все это понимают.

Теперь вы приходите к начальству с предложением сделать HTMX-апишку для серверного рендера. А зачем? — спросит начальство. На бекенде у нас REST/GraphQL, пусть сайт и ходит к нему через API. Этот довод почти невозможно проломить, потому что все сходится: бекенд общается с миром через JSON, у каждой платформы свой клиент. Что можно ответить?

Пожалуй, то, что команда фрондендеров дорогая, а их труд тяжело поддерживать. Скажем, чтобы сделать динамический сайт, кроме дизайнера нужно как минимум два фронтендера: один идет вперед, другой чинит баги. В случае с бекендом достаточно одного человека, который бы поддерживал HTMX-апишку. Это и дешевле, и надежней, потому что бекенд покрыт тестами и не такой хрупкий.

Поэтому не в каждой фирме дадут добро на серверный рендер. Протащить его в прод — та еще задача.

Пятая заметка об HTMX касается важного момента — источника правды. Должно быть, после недостатков серверного рендера кто-то приуныл. Чтобы восстановить баланс, поговорим о решающем плюсе HTMX, который не видно на первый взгляд.

Для начала вспомним React и Vue. Сильно упрощая, можно сказать, что их работа сводится к следующему. Мы переносим состояние с сервера на клиент и показываем в браузере. В теории нам не нужно беспокоится от отрисовке: реактивные фреймворки делают это за нас, когда мы изменяем состояние.

Звучит хорошо, но таким образом мы завязаны на состоянии. Каждый, кто немного программировал, знает, что самое сложное — контролировать состояние. Поэтому так популярны Advent of Code и похожие задачи: для программистов это своего рода отдых. В них почти нет состояния: тебе дают одну структуру данных, и ее нужно переколбасить в другую. Нет базы, Кафки, Редиса и всего этого.

Задачи из AoC решают на растах-хаскелях, потому что писать на них чистые преобразования — почти физическое удовольствие. Но приходит утро, и программист идет на работу, где его ждут кровавые Джава и Питон.

Так вот, состояние на клиенте влечет сложность уже самим появлением. За ним нужно следить и синхронизировать его с сервером. Если вам кажется это простым, вы ошибаетесь. Бывает, сервер не может вернуть данные в одном запросе, и нужно слать их несколько. Бывает, одни и те же данные нужны компонентам А и Б, но их писали разные люди, и одинаковые запросы идут параллельно без проверки на дублирование. Последее я вижу постоянно: одну и ту же сущность выгребают по пять раз просто потому, что она нужна всем, а разработчики не договорились.

Давно я читал в интернете: отдайте клиенту его данные, и пусть делает что хочет. Это не работает хотя бы из-за размера данных. Представим, вы активно пишете на каком-нибудь Твиттере или StackOverflow. За три года у вас скопилось 10 тысяч заметок. Вы хотите вывалить их клиенту при загрузке страницы? А заодно его друзей, рекомендации и прочее?

Хорошо, будем выгружать порциями по 100 штук. Выгрузили — сохранили на клиенте — отрисовали. Спрашивается, почему эту цепочку нельзя сократить — нарисовать без сохранения? Ведь состояние быстро протухает: написал комментарий, поскроллил, сменил фильтры, критерии сортировки, и все — пора лезть на сервер за новыми данными.

Все это мартышкин труд. Вы никогда не угадаете, что конкретно нужно клиенту в текущий момент. Выгружать все — дорого и долго. Остается запрашивать данные точечно: последние 10 статей, последние 50 комментариев. А если порции данных небольшие, зачем их хранить, если можно просто показать? Так мы уберем состояние, из-за которого одни сложности.

Ликвидация состояния — важный шаг вперед: управлять клиентом становится проще. Помните аналогию с Advent of Code и функциональными языками? С HTMX я испытал похожие чувства. Да, оказывается, так можно: отдаешь HTML, который без какого-либо состояния отображается на клиенте.

Резюмируя, можно сказать, что HTMX сводится к одному плюсу — ликвидации состояния на клиенте. Пусть каждый решает сам, но для меня это самое важное. Нет состояния — берем. Ради этой фичи я согласен на все минусы, о которых сказал раньше.

Шестая заметка об HTMX касается дизайна. Возможно, кто-то решил, что HTMX — это когда фронтендеров увольняют, а суровые бекендеры берут дизайн в свои руки — с предсказуемым результатом. Может, где-то практикуют подобное, но я это не одобряю, и вот почему.

Действительно, многим фронтендерам нужно дать пинка под зад. Например за то, что заставляют браузеры жечь трафик и процессор почем зря. Как мы выяснили, чтобы страница была динамичной, чаще всего фронтендер не нужен — можно вернуть HTML с сервера и вставить в DOM. Но остается вопрос с дизайном.

В компании должен быть человек, отвечающий за дизайн всего: не только сайта, но и логотипа, буклетов, визиток и остального. Кто-то думает, что дизайн — это верстка в программе, но это не так. Дизайн — это точка, где клиент соприкасается с продуктом, и дизайнер отвечает за эту точку в целом.

В компаниях, где процесс налажен правильно, он протекает так. Дизайнер делает макет в Фигме или иной программе. В ней указаны состояния страницы и сценарии перехода. Например, при нажатии на кнопку “добавить в корзину” четко указано, какие элементы изменятся и на что.

Верстальщик или фронтендер (в классическом понимании этого слова) переносит дизайн в статичный HTML. Если это продвинутый верстальщик, он вынесет изменяемые элементы в отдельные файлы, которые импортируются шаблонной системой вроде Jinja2. Получается набор статичных страниц, но у каждой из них есть структура.

Бекендеры принимают нарезанный HTML и оживляют его на сервере. При этом им запрещена любая отсебятина. Если бизнес-процесс не вписывается в дизайн, все трое — дизайнер, верстальщик, бекендер — собираются и решают, что исправить. Исправления вносятся во все три источника: дизайн, верстку, бекенд.

При таком подходе принцип HTMX остается в силе. Бекендеры не выдумывают дизайн на ходу, они действуют в рамках, которые ставит им дизайнер. Верстальщик служит промежуточным звеном между дизайном и его реализацией на бекенде.

В грамотных фирмах есть процесс сдачи сайта дизайнеру. Сайт не выкатят в прод, пока дизайнер не убедится, что верстальщик перенес дизайн в HTML без ошибок, а программисты оживили его без косяков.

Так был устроен процесс в Wargaming, где я когда-то работал. И хотя там были свои косяки, мне кажется он единственно верным. Что ещё в нем хорошего — он не привязан к конкретным технологиям, и HTMX отлично на него ложится.