HTMX проти стандарту SPA: коли гіпермедіа перевершує фреймворк JavaScript
Як атрибути htmx замінюють рендеринг на клієнтській стороні, що насправді означають розміри бандлів, та збалансований огляд ситуації, де переваги мають хайпермедійні додатки, а де все ще перемагає React.
Багато команд обирають React для кожного нового проекту, включаючи панелі адміністрування та інструменти CRUD, які переважно складаються з форм та таблиць. htmx стверджує, що значна частина цих додатків ніколи не потребувала фреймворку з боку клієнта: сервер може надсилати HTML, а кілька атрибутів дозволяють будь-якому елементу отримувати та замінювати його фрагменти. У цьому посібнику пояснюється, як працює htmx, порівнюється його об’єм із об’ємом React, а також наводяться як справжні переваги, так і обмеження, які прихильники React часто ігнорують, щоб ви могли свідомо обирати архітектуру, а не через звичку.
Цей посібник передбачає, що ви вже створювали веб-додатки та більшу частину свого часу присвятили React або схожому фреймворку.
Як SPA став стандартом для всього
Приблизно у 2013 році індустрія змінила свою основну модель. Замість того, щоб сервери повертали HTML-сторінки, вони почали повертати JSON, а JavaScript у браузері перетворював цей JSON на інтерфейс.
Для деяких продуктів односторінковий додаток став справжнім кроком уперед. Gmail, Figma та Google Maps зберігають багато даних у браузері, і їхнє взаємодія має здаватися миттєвою.
Проблема полягає у тому, що ця модель поширилась на все. Внутрішня панель керування, яка відображає рядки та дозволяє їх редагувати, опинилася з такою ж архітектурою, як і інструменти для дизайну. Маркетинговий сайт із однією формою зв’язку отримав інструменти для об’єднання коду, маршрутизатор, бібліотеку стану та етап гідратації.
Цей стандарт має конкретні витрати:
- Два набори коду, часто у двох мовах
- Модель даних, визначена з обох сторін
- Ланцюжок інструментів для компіляції, який потрібно підтримувати та оновлювати
Основна ідея htmx полягає у тому, що багато додатків несуть ці витрати, не отримуючи нічого у взамін.
Що таке htmx
htmx — це невелика бібліотека JavaScript, яка розширює HTML за допомогою атрибутів. За їх допомогою будь-який елемент може надіслати HTTP-запит та розмістити відповідь у певному місці на сторінці. По суті, це і є вся бібліотека.
Прикладом може слугувати поле для пошуку в режимі реального часу. У ньому вказується URL, до якого потрібно звернутися, подія, яка ініціює цей запит, та місце, куди потрібно розмістити результат:
<input type="text"
name="q"
hx-get="/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#results">
<div id="results"></div>
Читайте атрибути як речення: коли клавіша відпущена та значення справді змінилося, почекайте 300 мс, надішліть запит GET до /search та розмістіть отриманий результат у #results. Модифікатор delay:300ms запобігає надмірним запитам, тож швидке набирання не призводить до відправки запиту за кожен натискання клавіші.
Сервер не повертає JSON. Він повертає готовий фрагмент HTML:
<ul>
<li>First result</li>
<li>Second result</li>
</ul>
htmx вставляє цей фрагмент у цільовий елемент. Не відбувається парсинг JSON, немає шаблонів на стороні клієнта, а також немає стану клієнта, який міг би відрізнятися від стану на сервері.
Атрибути, які ви будете використовувати найчастіше
hx-get,hx-post,hx-put,hx-delete— вибирають метод HTTP та URL.
hx-trigger визначає подію, яка ініціює запит, наприклад click, keyup, load, every 2s або revealed (коли елемент з’являється у видимій частині сторінки).hx-target вибирає елемент, який отримуватиме відповідь.hx-swap керує способом вставки відповіді: innerHTML, outerHTML, beforeend або delete.hx-indicator вказує на елемент, який відображається під час обробки запиту.hx-confirm просить користувача підтвердити дію перед надсиланням.Ці елементи добре поєднуються. У наступному фрагменті коду видаляється рядок таблиці: кнопка надсилає запит DELETE, націлюється на найближчий оточуючий елемент tr, замінює весь цей рядок на (зазвичай порожній) результат та спочатку просить підтвердження. Модифікатор swap:1s затримує процес заміни на секунду, що дає CSS-переходу час на поступове зникнення рядка та створює враження швидкої реакції системи:
<tr>
<td>Widget</td>
<td>
<button hx-delete="/items/42"
hx-target="closest tr"
hx-swap="outerHTML swap:1s"
hx-confirm="Delete this item?">
Delete
</button>
</td>
</tr>
Зверніть увагу на те, чого тут немає: жодного окремого файлу JavaScript, жодного кроку компіляції та жодного стану на клієнтській стороні. Сервер залишається відповідальним за видалення елемента та визначення того, яким має стати рядок.
Дві архітектури поруч
Різниця стає більш зрозумілою у вигляді алгоритму виконання, ніж у порівнянні інструментів.
- Модель SPA: сервер надсилає JSON, клієнтський код його відображає та зберігає стан, користувач вчиняє дію, клієнт оновлює свій стан, перевідображає контент та надсилає JSON назад.
- Модель гіпермедіа: сервер надсилає HTML, браузер його відображає, користувач вчиняє дію, сервер відповідає новим HTML, і браузер замінює його.
У моделі гіпермедіа існує лише один джерело правди — сервер. Це усуває цілу категорію помилок, при яких клієнт вважає щось інше, ніж стверджує база даних, таких як застарілі кеші чи оптимістичні оновлення, які так і не були узгоджені.
Карсон Гросс, творець htmx, представляє його як повернення до того, чим мав бути REST. Підхід REST Роя Філдінга включає обмеження HATEOAS (Hypermedia As The Engine Of Application State): сервер надсилає інформацію, яка містить дані про дії, що можна виконати далі. Типовий JSON API цього не робить; клієнт мусить заздалегідь знати, які кінцеві точки існують. HTML робить це вбудовано, адже посилання є переходом стану, а форма — дією. Те, чи здасться вам цей підхід проникливим чи академічним, є хорошим показником того, як ви ставитиметеся до htmx в цілому.
Вимірювання розміру пакету та те, що він не повідомляє
Наведені розміри варіюються, тому корисно їх вимірювати. На момент написання миніфікована версія htmx важила приблизно так:
htmx.min.js: 51,238 bytes raw
16,576 bytes gzipped (16.2 KB)
Версії React 19.2.8 у продакшн-форматі, пакет React разом із клієнтом DOM, мали такий розмір:
react.production.js: 4,446 bytes gzipped
react-dom-client.production.js: 94,757 bytes gzipped
combined: 98,420 bytes gzipped (96 KB)
Це приблизно у шість разів більше, і це стосується лише часу виконання React. Справжній React-додаток містить маршрутизатор, бібліотеку стану, шар отримання даних та власні компоненти, тому розмір завантажуваних пакетів зазвичай сягає кількох сотень кілобайт. Натомість показники htmx враховують усі залежності на клієнтській стороні. Точні цифри змінюються з кожною новою версією, тому необхідно перевіряти їх для версій, які ви насправді будете розповсюджувати.
Проте будьте обережні з висновками. Розмір пакета впливає на перше завантаження, але не на швидкість подальших взаємодій. При хорошому з’єднанні різниця у часі завантаження є незначною, а під час повторних візитів обидва файли завантажуються з кешу. Більш переконливим аргументом щодо продуктивності є те, що htmx не має фази гідратації — періоду, під час якого сторінка, згенерована на сервері, виглядає готовою, але ігнорує кліки до тих пір, поки не будуть приєднані обробники подій.
Де htmx справді допомагає
- Одна мова та один кодовий базис. Команда, яка працює з Go, Python, Ruby чи C#, може створити всю програму. Перевірка даних знаходиться в одному місці, а модель даних визначається лише один раз.
- Відсутність конвеєра збірки. Достатньо одного тега
script. Не потрібна налаштування бандлера, немає переднього фронту зnode_modules, а також немає необхідності оновлювати залежності переднього фронту. - Локальність поведінки. Те, що робить елемент, описано безпосередньо в самому елементі. Замість того, щоб слідувати за кліком крізь кілька файлів та базу даних, достатньо прочитати маркап. Це один із найсильніших аргументів на користь цього підходу, і він стосується підтримки коду, а не швидкості.
- Стан у одному місці. Не потрібно скасовувати кеш клієнта, немає застарілих даних та не потрібно виконувати оптимістичні оновлення для скасування змін.
Де htmx не досконалий
Це ті аспекти, про які часто не згадують.
Багата, безперервна взаємодія
Редактори з функцією перетягування елементів, робочі аркуші та інтерактивні діаграми з можливістю зумування та створення штрихів вимагають постійної взаємодії, а не окремих запитів. У htmx кожна взаємодія означає подвійну передачу даних на сервер. Для такого інструменту, як текстовий редактор, це не є компромісом; саме тому htmx не підходить для нього.
Затримка у поганих мережах
Захисники цього підходу зазначають, що хостинг на периферії значно скоротив час подвійних передач даних, і це правда. Проте користувач зі слабким мобільним з’єднанням у сільській місцевості може чекати кілька сотень мілісекунд на те, що React обробив би локально за кілька мілісекунд. Додатки htmx працюють чудово у хороших мережах, але значно гірше у поганих, що є протилежністю тому, як цей аргумент зазвичай представляється.
Відсутність можливостей для мобільних пристроїв чи роботи офлайн
htmx призначений виключно для вебу. React Native дозволяє команді ділитися концепціями та значною кількістю коду з нативними додатками, тож якщо у планах є розробка нативних мобільних додатків, це може переважити всі інші фактори. Офлайн-використання також неможливе, оскільки кожна взаємодія потребує сервера.
Складний стан на клієнтській стороні
Багатокрокові майстерні з взаємозалежними полями, форми з реальною перевіркою даних між полями чи екрани, де кілька компонентів мають реагувати на одну зміну, створюють складнощі. Команди зазвичай додають Alpine.js або hyperscript, і тоді вони фактично складають фреймворк з окремих елементів, а не використовують готовий фреймворк.
Малий екосистема та обмежений пул кваліфікованих фахівців
У React є зрілі компоненти майже для будь-якого віджета. З htmx ви можете або створити власний вибірник дат, або вбудувати готовий JavaScript-код та самостійно керувати його функціонуванням. Знання цих інструментів також мають значення: на момент написання цього тексту опитування показують, що React використовують понад 40 відсотків розробників, тоді як htmx — близько 7 відсотків, причому співвідношення завантажень через npm становить приблизно 560 до 1. Це впливає на те, наскільки швидко нові співробітники стають продуктивними, та на те, наскільки легко можна знайти рішення у складних ситуаціях.
Ретельне аналізування цифр щодо впровадження
Реальна картина є більш складною, ніж стверджують обидві сторони.
- Використання React не знижується. Він залишається домінуючим у абсолютних показниках з дуже великою перевагою.
Основний висновок: React не замінюється, але його статус беззаперечного стандарту послаблюється. Слід пам’ятати, що багато контенту про суперництво htmx та React на Інтернеті створено для пошукового трафіку, і такі показники, як відсоток скорочення коду чи різні коефіцієнти швидкодії, повторюються без посилань на джерела. Сприймайте їх як орієнтовні та перевіряйте початкові джерела перед їх цитуванням.
Вибір між ними
Оберіть htmx, коли:
- Додаток полягає переважно у формах та списках: панелях адміністрування, внутрішніх інструментах, додатках типу CRUD, сайтах з контентом, панелях керування, які відображають дані, а не маніпулюють ними.
- Сильна сторона вашої команди — це бекенд.
- Ви хочете єдину базу коду, яку зможе підтримувати будь-хто з команди через кілька років.
Оберіть React, коли:
- Браузер справді зберігає значну кількість даних, як у редакторах, інструментах дизайну, системах реального часу для співпраці або при інтенсивній обробці даних.
- Вам потрібні нативні мобільні додатки або підтримка офлайн-режиму.
- Ви покладаєтесь на розвинену екосистему компонентів.
Існує ще третій варіант, який часто ігнорується: використовувати обидва. htmx може обслуговувати екрани CRUD, тоді як React забезпечує функціонал двох чи трьох вікон, яким це справді потрібно. Ніщо не змушує використовувати одну архітектуру у всьому продукті, і поєднання htmx з окремими елементами React є простішим, ніж поєднання двох фреймворків типу SPA. Якщо ви вже використовуєте htmx, посібник з оновлення до htmx 4 розповідає про зміни у наступній основній версії.
React Server Components запозичують частину ідеї гіпермедіа, переміщуючи процес відображення на сервер. Проте їм все одно потрібен повний середовище виконання React та сервер, сумісний з Node, тому вони не є легковаговим варіантом; це React, який отримує деякі з тих самих переваг, залишаючись при цьому важким.
Основні висновки
- htmx дозволяє будь-якому елементу надсилати запит та замінювати його на HTML, створений на сервері, що зберігає сервер як єдине джерело істини та усуває необхідність синхронізації стану клієнта.
- Час його виконання становить приблизно шосту частину від часу роботи React ще до додавання будь-якого коду застосунку React, проте практична перевага полягає скоріше у уникненні процесу гідратації, ніж у скороченні часу завантаження.
- Він ідеально підходить для застосунків типу CRUD, внутрішніх інструментів та сайтів з контентом, але має проблеми з постійною взаємодією, поганими мережами, використанням на мобільних пристроях, роботою офлайн та складним станом клієнта.
- Поєднання цих двох підходів є легітимною архітектурою, а не компромісом.
Більш важливе питання, яке стоїть за цією дискусією, — це те, як архітектура, розроблена для Gmail, стала стандартною для форми з шістьма полями. Ніхто не був абсолютно неправий; інструмент став стандартним, а зі стандартами перестають працювати. Що б ви не обрали, включаючи React, робіть це на основі обдуманих міркувань, а не через успадкування.
Пов’язані матеріали
- Scheduler у React та цикл подій: хто насправді вирішує, коли виконуватися завдання — Дізнайтеся, як кооперативний Scheduler у React працює всередині циклу подій JavaScript, чому відбуваються переходи та чому жоден scheduler не може врятувати заблоковану нитку.