Перейти к содержанию
Mercury
Русский
Дизайн книга
Меню
Русский

Серверный рендеринг: что есть и чего пока нет

Live Mercury может запросить HTML острова у рендерера при первом, неподключённом рендеринге. Рендерер не настроен, поэтому флаг выключен, элемент острова изначально пуст, а клиент всегда отрисовывает содержимое заново.

Возможности серверного рендеринга Live Mercury легко переоценить, поэтому этот урок описывает поведение кода. Есть флаг и поведение для рендерера, но рендерер не настроен, а клиент не выполняет гидратацию.

Текущее состояние серверного рендеринга

  • У <.mercury> есть флаг ssr. Он выключен по умолчанию, а в umbrella-проекте нигде не задан :ssr_module — рендерер, который должен вызываться.
  • При выключенном SSR элемент <.mercury> пуст до запуска его JavaScript.
  • Если элемент содержит серверную разметку, первая клиентская отрисовка заменяет её. Гидратации нет.
  • Острова EDU показывают серверный HTML до запуска JavaScript, но это резервная разметка самой страницы, а не результат серверного рендеринга компонента.
  • Руководство live_mercury прямо указывает: серверный рендеринг Mercury Hyperscript через LiveMercury не входит в этот релиз.

Флаг ssr

ssr — зарезервированный assign функции mercury/1:

~H"""
<.mercury name="MyComponent" ssr={true} />
"""

Его значение по умолчанию задаёт config :live_mercury, :ssr, читаемый при компиляции; если настройка отсутствует, используется false. Даже включённый флаг действует только при первом, неподключённом рендеринге: когда отслеживание изменений ещё не началось (__changed__ равен nil) и сокет не подключён. Каждый рендеринг при активном подключении игнорирует этот флаг.

Когда флаг действует, mercury/1 вызывает LiveMercury.SSR.render(name, props, slots) с закодированными свойствами. Если вызов поднимает LiveMercury.SSR.NotConfigured, остров рендерится так, будто SSR выключен. Иначе элемент получает data-ssr, а его содержимым становится :html из ответа.

Здесь есть тонкость, закреплённая тестом. Если :ssr_module не настроен, render/3 не вызывает исключение, а возвращает %{preloadLinks: "", html: ""}. Поэтому <.mercury ssr={true}> помечает атрибутом data-ssr элемент с пустым HTML. Именно поэтому флаг выключен по умолчанию: комментарий над @ssr_default описывает такое поведение при прежнем включённом значении.

LiveMercury.SSR и render/3

LiveMercury.SSR — поведение с одним обратным вызовом:

@callback render(component_name, props, slots) :: render_response | no_return

Рендерер — модуль, реализующий это поведение и указанный в конфигурации:

config :live_mercury, ssr_module: MyCustomSSRModule

LiveMercury.SSR.render/3 вызывает настроенный модуль внутри интервала telemetry [:live_mercury, :ssr], метаданные которого содержат имя компонента, свойства и слоты. Строковый ответ разделяется по первому <!-- preload --> на preloadLinks и html; ответ другого типа возвращается как есть, а в элемент попадает только его :html. Шаблон mercury/1 не использует preloadLinks.

В приложении остаются две реализации для проектов с собственным рендерером; обе требуют рендерер вне BEAM:

  • LiveMercury.SSR.ViteJS отправляет имя, свойства и слоты POST-запросом на http://{:vite_host}/ssr_render. LiveMercury не поставляет ни плагин Vite, ни серверный рендерер для этого адреса.
  • LiveMercury.SSR.NodeJS вызывает файл server.js через пакет NodeJS.

Они поднимают LiveMercury.SSR.NotConfigured, когда не могут обратиться к рендереру: ViteJS — если :vite_host не задан, NodeJS — если пакет NodeJS не загружен или его супервизор не запущен. Ни одна реализация не настроена в этом репозитории, и курс их не использует: они вернули бы среду выполнения JavaScript в путь рендеринга острова.

Остров без SSR

При выключенном SSR элемент <.mercury> изначально пуст. Так настроены все острова этого типа в репозитории. Всё необходимое компоненту передаётся в атрибутах:

  • свойства в data-props, в компактном JSON;
  • HTML слота по умолчанию в data-slots, в кодировке base64; клиент отображает его как дочернее содержимое компонента.

Поэтому остров <.mercury> ничего не показывает, пока не загрузится его бандл и MercuryHook не выполнит отрисовку. LiveView как обычно рендерит всё вокруг острова, поэтому страницу можно читать; самому содержимому острова нужен JavaScript.

Без гидратации

При гидратации клиент принимает серверный DOM и подключается к нему. Live Mercury этого не делает. При первой отрисовке элемента с атрибутом data-ssr хук очищает элемент и рендерит компонент заново. Причина описана в исходнике хука: render() из hyperscript повторно использует совпадающие узлы, а остальные оставляет рядом с островом. Без очистки при частичном совпадении серверная разметка и остров отображались бы одновременно.

Тест закрепляет поведение: элемент с содержимым <span>server</span><p>fallback</p> и атрибутом data-ssr после mounted() показывает только остров и содержит один дочерний узел.

Элемент без data-ssr сохраняет существующие дочерние узлы. Так сохраняется скрипт свойств EDU: хук рисует рядом с ним.

Резервная разметка EDU — не SSR компонента

Контейнер EDU EchoEduWeb.Island.island/1 действительно помещает на страницу читаемый HTML: свой внутренний блок, написанный в шаблоне LiveView.

~H"""
<.island id={@island_id} bundle={@bundle} props={%{title: "The demo island"}}>
  <p class="mx-text mx-text--muted">The island mounts here when JavaScript runs.</p>
</.island>
"""

Это вручную написанная резервная разметка, а не результат рендеринга компонента. При монтировании острова загрузчик EDU временно убирает её, кроме скрипта свойств, и возвращает те же узлы после завершения работы острова. Тест страницы проверяет, что серверная разметка страницы с островом читается без JavaScript.

Используйте резервную разметку, когда страницу нужно читать без JavaScript. Читатель сначала увидит её, затем остров.

Чего пока нет

  • Нет рендерера. Ни один файл конфигурации umbrella-проекта не задаёт :ssr или :ssr_module для :live_mercury.
  • Рендерер Mercury не подключён. У @fiberfx/hyperscript есть точка входа ./server, упакованная как @fiberfx/render-to-string с функцией renderToString; также экспортируются hydrate и hydrateRoot из клиентской точки входа. Они разобраны в уроке Рендеринг на сервере. Ни echo/apps/live_mercury, ни @fiberfx/live-mercury, ни загрузчик EDU их не вызывают.
  • Нет гидратации — так устроен текущий хук.
  • preloadLinks разбирается, но нигде не используется.

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