Alek�ei Matiu�hkin

сделано с умом



Реактивные серверы, или как перестать писать JavaScript и начать жить

Thursday, 9 Apr 2026 Tags: 2026tech

Phoenix LiveViewи его друзья из других стеков:Rails Hotwire,Laravel LivewireиGo Live/HLive


Преамбула: краткая история нежелания

В начале двухтысячных, когда jQuery еще казался вершиной инженерной мысли, а слово «фреймворк» произносилось с придыханием и легким немецким акцентом, веб-разработчик был существом предсказуемым. Он писал HTML, немного CSS, чуть-чуть серверного кода — и уходил домой к шести. Ajax обрел второе дыхание, и подходы к созданию веб-приложений разветвились, как генеалогическое древо разорившегося аристократа.

К 2010-м трещина превратилась в пропасть. По одну сторону остались серверные разработчики — степенные, что твоя пожилая немецкая пара на Фридрихштрассе, в отутюженных рубашках и с прямой спиной. По другую — фронтендеры, вечно взъерошенные, перебирающие фреймворки, как четки: Backbone, Angular, React, Vue, Svelte, Solid, $mol, конечно, куда без него, — и далее по списку. Между ними пролегала нейтральная полоса из JSON-ов и REST API, которую обе стороны пересекали с выражением лица таможенника, проверяющего документы.

И вот, примерно к 2018 году, несколько независимых друг от друга команд пришли к одной и той же еретической идее: а что, если вообще не отдавать рендеринг браузеру? Что, если сервер будет отрисовывать HTML, отправлять его по WebSocket — и пусть клиент занимается тем, чем ему положено: отображением текста и картинок?

Идея была не нова. Собственно, именно так работал весь веб до изобретения XMLHttpRequest. Но в новой реинкарнации она обросла диффами, патчами, виртуальными DOM-ами на сервере и прочей инженерной роскошью, позволяющей создавать иллюзию мгновенного отклика без единой строчки клиентского JavaScript. Ну, почти без единой.

Так родились четыре подхода к одной и той же проблеме: Phoenix LiveView (2018, Elixir), Rails Hotwire (2020, Ruby), Laravel Livewire (2019, PHP) и целое семейство Go-реализаций: Live (jfyne), HLive (SamHennessy), Fir (livefir — заархивировано в 2025). Каждая из них — ответ своей экосистемы на вопрос, который можно сформулировать коротко: как написать интерактивное веб-приложение, не выходя из зоны комфорта?


I. Phoenix LiveView: демиург серверной реактивности

LiveView появился первым -- если не считать Meteor, который, впрочем, решал несколько другую задачу и делал это с размахом пьяного декоратора. Крис Маккорд представил LiveView на ElixirConf 2018, и с тех пор фреймворк прошел путь от экспериментальной библиотеки до версии 1.0 (декабрь 2024), обретя по дороге HEEx-шаблоны, компоненты, стримы и прочую атрибутику зрелости.

Принцип работы прост до смешного: при первом запросе сервер отрисовывает полноценную HTML-страницу. Затем браузер открывает WebSocket-соединение, и дальше все изменения приходят в виде минимальных диффов. Состояние живет на сервере, в отдельном легковесном BEAM-процессе — том самом, которых Erlang VM умудряется держать одновременно по два миллиона на машине, не особо при этом потея.

defmodule DemoLive do
  use Phoenix.LiveView
  def mount(_params, _session, socket) do
    {:ok, assign(socket, count: 0)}
  end
  def render(assigns) do
    ~H"""
    <div>
      <h1>Счетчик: {@count}</h1>
      <button phx-click="inc">+</button> <button phx-click="dec">-</button>
    </div>
    """
  end
  def handle_event("inc", _, socket) do
    {:noreply, update(socket, :count, &(&1 + 1))}
  end
  def handle_event("dec", _, socket) do
    {:noreply, update(socket, :count, &(&1 - 1))}
  end
end

Обратите внимание: здесь нет ни контроллера, ни JSON, ни маршрутизатора в привычном смысле. Один модуль. Состояние — живет в assigns. Рендеринг — в ~H-сигиле. События — в handle_event. Все остальное берет на себя фреймворк. Примерно так хороший печник складывает из кирпичей нечто кривобокое, немного покосившееся — но оно протапливается с первого раза, как родное.

Сильные стороны:

  • Настоящий реалтайм через WebSocket. Не имитация, не поллинг (хотя при отсутствии вебсокетов предусмотрен откат на него) — живое, двустороннее соединение с персистентным процессом на сервере.
  • Fault tolerance по наследству от BEAM/OTP. Процесс упал — отжался перезапустился. Приложение продолжает работать.
  • Минимальный трафик: отправляются только диффы измененных частей DOM.
  • Один язык, один стек, одна кодовая база. Тестирование LiveView-компонентов не требует браузера.

Слабые стороны:

  • Elixir — прекрасный почти функциональный язык с паттерн-матчингом, иммутабельностью и рекурсией. Но найти на рынке команду, знакомую с нюансами — практически нереально.
  • Экосистема заметно меньше, чем у Ruby или PHP. Библиотеку для интеграции с экзотическим API придется, вероятнее всего, написать самому. Хотя, по моему опыту, все нужное — уже давно написано.
  • Каждое WebSocket-соединение потребляет память на сервере (40KB). При миллионе одновременных пользователей арифметика становится неуютной. Впрочем, это не CGI, конечно, и когда у вас есть миллион активных пользователей, можно позволить себе воткнуть рядом еще один сервер.

II. Rails Hotwire: HTML по проводам, как завещал Дедушка Харизматичного Хайпа (известный в узких кругах, как DHH)

Hotwire — это Basecamp/37signals, DHH, Ruby on Rails, и все, что из этого следует: конвенция превыше конфигурации, мнение фреймворка превыше мнения разработчика, и твердая уверенность, что большинству приложений не нужен React (что так на самом деле и есть, в принципе). Это набор из двух библиотек: Turbo (управление навигацией и обновлением фрагментов страницы) и Stimulus (минимальный JS-фреймворк для поведения).

Философия Hotwire — «Html Over The WIRE» в буквальном смысле: сервер отправляет готовые куски HTML, а клиент вставляет их в нужные места страницы. Никаких диффов на уровне атрибутов, никакого виртуального DOM. Грубо, эффективно, понятно — как кирпичная кладка.

<%# app/views/posts/index.html.erb %>
<%= form_with url: posts_path, method: :get,
              data: { turbo_frame: "results" } do %>
  <input type="search" name="q" value="<%= params[:q] %>">
<% end %>
    
<turbo-frame id="results">
  <% @posts.each do |post| %>
    <div><%= post.title %></div>
  <% end %>
</turbo-frame>

# Turbo Stream: обновление в реальном времени
# app/models/message.rb
class Message < ApplicationRecord
  after_create_commit -> { 
    broadcast_append_to "messages, target: "messages, partial: "messages/message"
  }
end

Turbo Frames — это изолированные контексты навигации: ссылка внутри фрейма обновляет только фрейм, остальная страница остается нетронутой. Turbo Streams — серверные команды (append, prepend, replace, remove), которые можно доставлять как через обычный HTTP-ответ, так и через WebSocket (Action Cable).

Сильные стороны:

  • Максимально пологая кривая обучения. Если вы знаете Rails — вы уже знаете Hotwire.
  • Гигантская экосистема джемов. Для любой задачи найдется готовое решение, проверенное временем и тысячами продакшенов.
  • Нет привязки к WebSocket: Turbo Frames работают через обычный HTTP. Turbo Streams умеют и так, и так.
  • Stimulus дает ровно столько JavaScript, сколько нужно, и ни граммом больше.

Слабые стороны:

  • Hotwire — не реактивный фреймворк. Это набор соглашений по доставке HTML. Состояние живет в базе данных и перестраивается при каждом запросе. Это принципиально другая модель, нежели чем у LiveView.
  • Сложная интерактивность (drag-and-drop, nested modals, client-side state) быстро упирается в необходимость писать настоящий JavaScript.
  • Производительность Ruby при высокой конкурентности — тема для отдельного, грустного разговора, но никто не мешает воткнуть много прикленных к соединениям серверов за балансером, так делает, например, Shopify.

III. Laravel Livewire: состояние как образ жизни

Livewire — самый непосредственный среди наследников LiveView. Калеб Порцио создал его в 2019 году, вдохновившись именно Phoenix LiveView, и перенес основные идеи на почву Laravel и PHP. Результат — компонентная система, в которой PHP-класс хранит состояние, а Blade-шаблон его отображает. При каждом событии (клик, ввод текста, отправка формы) Livewire отправляет AJAX-запрос на сервер, сервер перерисовывает компонент и возвращает обновленный HTML.

// app/Livewire/Counter.php
class Counter extends Component {
  public int $count = 0;
  public function increment(): void { $this->count++; }
  public function decrement(): void { $this->count--; }
  public function render(): View { return view('livewire.counter'); }
}

{{-- resources/views/livewire/counter.blade.php --}}
<div>
    <h1>Счетчик: {{ $count }}</h1>
    <button wire:click="increment">+</button>
    <button wire:click="decrement">-</button>
</div>

Выглядит так, словно первый пример переписали на PHP? Неудивительно. Архитектурный паттерн тот же: состояние на сервере, события от клиента, перерисовка и доставка изменений. Но дьявол, как водится, прячется в деталях.

PHP не умеет держать постоянное WebSocket-соединение так же элегантно, как Erlang VM. Livewire 2 использовал AJAX-поллинг; Livewire 3 добавил поддержку WebSocket через Laravel Reverb, но по умолчанию все еще работает через HTTP. При каждом запросе состояние компонента сериализуется в JSON, отправляется на клиент, а при следующем событии — возвращается обратно. Это работает, но с грацией бригады грузчиков, затаскивающих рояль на шестнадцатый этаж в доме без грузового лифта.

Зато Livewire прекрасно интегрируется с Alpine.js — маленьким JavaScript-фреймворком, который берет на себя анимации, тогглы и прочую клиентскую мелочь, которую гонять через сервер было бы расточительно. Связка Livewire + Alpine — фактический стандарт в мире Laravel.

Сильные стороны:

  • wire:model — двустороннее связывание данных. Вводите текст в поле — свойство компонента обновляется автоматически.
  • Огромная экосистема Laravel. Filament, Jetstream, Breeze — все это работает с Livewire из коробки.
  • Минимальный порог входа для PHP-разработчиков.
  • Alpine.js покрывает те случаи, когда серверный рендеринг избыточен.
    Слабые стороны:
  • Отсутствие персистентного соединения (по умолчанию). Каждое взаимодействие — это HTTP round-trip. На нестабильных сетях пользователь это почувствует. Но мы и так на PHP.
  • Сериализация состояния при каждом запросе — накладные расходы, растущие пропорционально сложности компонента.
  • PHP и конкурентность — оксюморон того же толка, что «интеллект языковой модели» или «интеллигентный военрук».

IV. Go Live/HLive: суровый минимализм

В экосистеме Go нет одного доминирующего решения — скорее, россыпь проектов разной степени зрелости, объединенных общей идеей и общим нежеланием становиться мейнстримом. Два наиболее заметных: Live (jfyne, 730 звезд на GitHub) и HLive (SamHennessy, 98 звезд). Есть также Fir (livefir), но его автор уже объявил о прекращении разработки в пользу нового проекта.

Live — прямой наследник Phoenix LiveView по духу. WebSocket, серверный рендеринг, минимальные диффы. Встраивается в стандартный net/http.

// Термостат на Go Live (jfyne/live)
func thermoMount(ctx context.Context, s *live.Socket) (any, error) {
  return &ThermoModel{C: 19.5}, nil
}
func tempUp(ctx context.Context, s *live.Socket, p live.Params) (any, error) {
  model := s.Assigns().(*ThermoModel)
  model.C += 0.1
  return model, nil
}

// Шаблон
// <div>{{.Assigns.C}}</div>
// <button live-click="temp-up">+</button>// <button live-click="temp-down">-</button>

HLive идет дальше: вместо шаблонов — программная генерация DOM на сервере (виртуальный DOM à la React, но на Go). Никаких HTML-файлов, никаких template engines. Чистый Go.

// HLive: программная генерация
DOMfunc home() *l.Page {
  var message string
  input := l.C("input", l.Attrs{"type": "text"}, l.OnKeyUp(func(ctx context.Context, e l.Event) {
    message = e.Value
  }),)
  page := l.NewPage()
  page.DOM.Body.Add(
    l.T("div", input),
    l.T("hr"),
    "Hello, ",
    &message,
  )
  return page
}

Сильные стороны:

  • Go — компилируемый, статически типизированный, быстрый. Один бинарник, минимальное потребление памяти.
  • Совместимость с net/http — никаких зависимостей от мега-фреймворков.
  • Идеально для внутренних инструментов, админок, дашбордов.

Слабые стороны:

  • Незрелость. Live — наиболее развитый проект, но и он далек от production-ready в том смысле, в каком это понимают в индустрии.
  • Минимальная экосистема компонентов. Нет аналога Phoenix Components или Blade-компонентов.
  • Документация — на уровне README и примеров. Для сколько-нибудь серьезного проекта придется читать исходники.
  • Нет горячей перезагрузки, нет встроенной аутентификации, нет генераторов. Вы один на один с языком и стандартной библиотекой.

V. Сравнительная анатомия

Модель состояния

LiveView: состояние живет в BEAM-процессе, привязанном к WebSocket-соединению. Процесс — легковесный (2KB), изолированный, с собственной кучей. Падение одного процесса не затрагивает остальные.

Hotwire: состояния на сервере нет. Каждый запрос — stateless. Состояние — в базе данных, в сессии, в URL-параметрах. Это архитектурное решение, не ограничение.

Livewire: состояние сериализуется и путешествует между клиентом и сервером при каждом запросе. Сервер восстанавливает компонент из JSON, обрабатывает событие, перерисовывает, отправляет обратно.

Go Live/HLive: состояние — в горутине, привязанной к WebSocket-сессии. Модель ближе всего к LiveView, но без OTP-супервизоров и горячей перезагрузки кода.

Транспорт

LiveView: WebSocket (всегда). Первый рендер — по HTTP, далее — только WS.

Hotwire: HTTP (Turbo Frames) или WebSocket (Turbo Streams через Action Cable). Выбор за разработчиком.

Livewire: HTTP (по умолчанию). WebSocket — опционально, через Laravel Reverb.

Go Live/HLive: WebSocket (всегда).

Гранулярность обновлений

LiveView: диффы на уровне отдельных выражений внутри шаблона. Фреймворк знает, какие части шаблона статичны, а какие динамичны, и отправляет только изменения в динамических частях.

Hotwire: замена целых HTML-фрагментов. Turbo Stream команды (append, replace, remove) оперируют DOM-элементами целиком.

Livewire: полная перерисовка компонента, затем morphdom на клиенте определяет минимальный набор DOM-операций.

Go Live/HLive: Live — аналогично LiveView (диффы шаблонов). HLive — виртуальный DOM на сервере с tree diffing.


Заключение

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

Phoenix LiveView — наиболее зрелый и архитектурно последовательный. Если вы готовы инвестировать в изучение Elixir, вы получите инструмент, спроектированный от первого до последнего винтика для серверной реактивности. Это печь, сложенная мастером: кривобокая, но греет.

Rails Hotwire — прагматичный ответ зрелой экосистемы. Не претендует на революцию, зато работает в любом Rails-приложении через пять минут после bundle install. Покрывает восемьдесят процентов потребностей — и этого, как правило, достаточно.

Laravel Livewire — ближайший клон LiveView, адаптированный под реалии PHP. Идеален для Laravel-разработчиков, которые хотят интерактивности без JavaScript, но готовы мириться с накладными расходами HTTP round-trip.

Go Live/HLive — перспективный, но пока незрелый эксперимент. Подойдет для внутренних инструментов, дашбордов и прототипов, где Go уже используется на бэкенде и тянуть отдельный фронтенд-стек — непозволительная роскошь.

Выбор между ними — не вопрос технического превосходства. Это вопрос экосистемы, команды, и того, в каком кресле вам удобнее сидеть, когда самолет, наконец, наберет высоту.


А самое главное, конечно, что при помощи этих технологий можно перестать писать JavaScript :)


  ¦