EN RU

@demondehellis

Рассказываю о технологиях и программировании.

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. Я в восторге.


Еще всякое интересное