Frontend без JS
Frontend для бэкэндера, или как сделать реактивный UI без React.
Я люблю делать бэкенд и вообще не люблю фронтенд. Но для всяких indie и pet-projects без фронта далеко не уедешь. Но как бы так сделать, чтобы не учить React и при этом собирать интерактивные интерфейсы? А лучше — чтобы вообще без JavaScript. Ну или почти без него.
В чудесное время мы живем, есть несколько библиотек, которые полностью закрывают мои потребности.
⚡ Alpine.js
Это современный jQuery. Очень лёгкий, без сборщиков и билдеров. Подключаешь по CDN, и у тебя уже реактивный UI, если надо. По возможностям — ближе всего к Vue, но намного проще.
<div x-data="{ open: false }">
<!--Кнопка переключает состояние open-->
<button @click="open = !open">
Toggle
</button>
<!--Блок отображается, если open = true-->
<div x-show="open">
Hello World
</div>
</div>
Можно писать полноценные компоненты, можно просто вешать на дропдауны, менюхи, табы и модалки. Мы используем его уже давно на основном проекте, и пока ни разу не встретили необходимости переехать на что-то более серьезное. Даже наоборот, изначально у нас был Vue.js, и мы полностью переехали на Alpine. Меньше кода, не надо ничего собирать, не надо возиться с зависимостями, документация читается за полчаса, все интуитивно и ничего лишнего.
Да, понятно, это все же JavaScript, но он настолько минималистичный, что его можно не считать. Большая часть кода — это HTML-атрибуты и немного реактивных переменных. Всё, что нужно для интерактивности, — это x-data, x-show, @click и подобные директивы.
🔌 Alpine-плагины
У Alpine есть несколько интересных плагинов, мне приглянулись вот эти.
👉 Persist
Хранит состояние в localStorage или sessionStorage.
<div x-data="{ dark: $persist(false) }">
<button @click="dark = !dark">
Toggle Theme
</button>
</div>
Темы, настройки пользователя — всё это теперь живёт между перезагрузками.
👉 Sort
Плагин для drag-and-drop списков и досок с карточками типа kanban.
<ul x-sort>
<li x-sort:item>foo</li>
<li x-sort:item>bar</li>
<li x-sort:item>baz</li>
</ul>
Элементы такого списка можно перемещать между собой мышкой. Можно добавить обработчик, чтобы запоминал состояние.
👉 Collapse
Плагин для аккордеонов и скрытия/показа блоков с анимацией.
<div x-data="{ open: false }">
<button @click="open = !open">
Toggle
</button>
<div x-collapse x-show="open">
Hello World
</div>
</div>
🧠 HTMX
Геймченджер в минималистичном фронтенде. Позволяет делать асинхронные запросы без JavaScript. Просто добавляешь нужные атрибуты в штмл:
<input hx-post="/search"
hx-trigger="keyup[key=='Enter']"
hx-target="#search-results"/>
<div id="search-results">
<!--тут будет html из ответа-->
</div>
Работает с любым бэкендом, даже с PHP или Flask. Поддерживает WebSockets и SSE для обновления данных в реальном времени.
А теперь посмотрим, как это связать с бэкендом.
🔁 HTMX + Flask
Простейшая интеграция, бэкэнд на Flask:
@app.route("/hello")
def hello():
return "<p>Hello from Flask!</p>"
Получаем данные прямо в html:
<button
hx-get="/hello"
hx-target="#output">
Load
</button>
<div id="output">
<!-- Ответ от сервера будет здесь -->
</div>
Теперь свяжем это с Alpine.
⚡ HTMX + Alpine + Flask
HTMX получает html с Alpine, а тот управляет логикой внутри:
<!-- Кнопка подгружает модалку -->
<button
hx-get="/modal"
hx-target="#modal"
hx-swap="innerHTML">
Open Modal
</button>
<div id="modal">
<!-- Контейнер, куда прилетит Alpine-компонент -->
</div>
Flask отдает шаблон:
@app.route('/modal')
def modal():
return render_template('modal.html')
Шаблон модалки с Alpine:
<!--modal.html-->
<div
x-data="{ open: true }"
x-show="open"
class="modal">
<p>Hello from modal</p>
<!-- Закрываем модальное окно по клику -->
<button @click="open = false">
Close
</button>
</div>
Что насчет более сложных запросов? Например, отправка форм?
📋 Формы с HTMX
HTMX позволяет отправлять формы без перезагрузки страницы и без JavaScript. Просто добавляем нужные атрибуты:
<form
hx-post="/submit"
hx-target="#response">
<input type="text" name="name">
<button type="submit">
Submit
</button>
</form>
<div id="response">
<!-- Ответ от сервера будет здесь -->
</div>
Хорошо, а что если у нас есть данные, но нет формы? Например, нужно отправить состояние Alpine в бэкэнд?
📤 Отправка данных Alpine в бэкэнд с HTMX
Можно добавить hx-vals к кнопке или форме, чтобы указать, какие данные отправить из Alpine:
<div x-data="{
name: 'Demon',
location: 'Bangkok'
}">
<button
hx-post="/submit"
:hx-vals="JSON.stringify({ name, location })">
Отправить данные
</button>
</div>
Ок, не плохо, есть еще вариант с Axios. Это уже не совсем без JS, но тоже неплохо
📦 Axios
Axios — это HTTP-клиент для браузера и Node.js. Он позволяет делать асинхронные запросы к API (но уже с использованием JavaScript). Если HTMX не подходит, можно использовать Axios. В связке с Alpine можно сделать очень минималистично:
<div x-data="{ name: 'Demon' }">
<button @click="axios.post('/api/submit', { name })">
Отправить
</button>
</div>
Почему Axios, а не Fetch API? Потому что Axios проще в использовании. Он автоматически преобразует JSON и имеет более удобный синтаксис для работы с ответами.
Вот так просто. Теперь у нас есть всё, чтобы собрать веб-приложение — и почти не писать JS. И уж точно не учить React. Я в восторге.
Еще всякое интересное