SSR и веб-компоненты

Haunted — это легковесная библиотека для создания веб-компонентов с реактивным состоянием на основе Hooks, вдохновленных React. Одной из ключевых особенностей Haunted является возможность интеграции серверного рендеринга (SSR) с веб-компонентами, что позволяет улучшить производительность и SEO.

Веб-компоненты и Shadow DOM

В Haunted каждый компонент создаётся как стандартный веб-компонент. Это обеспечивает инкапсуляцию стилей и логики через Shadow DOM. Основной подход:

import { component, html, useState } from 'haunted';

function Counter() {
  const [count, setCount] = useState(0);
  return html`
    <button @click=${() => setCount(count + 1)}>
      Счётчик: ${count}
    </button>
  `;
}

customElements.define('my-counter', component(Counter));
  • component() — функция, оборачивающая обычную функцию в веб-компонент.
  • html — шаблонизатор, использующий Tagged Template Literals.
  • useState — Hook для реактивного состояния внутри компонента.

Shadow DOM гарантирует, что стили и события компонента не конфликтуют с остальной страницей, что особенно важно при SSR, когда компонент может быть встроен в уже готовую HTML-разметку.

Серверный рендеринг (SSR) с Haunted

SSR позволяет рендерить веб-компоненты на сервере и отдавать уже готовый HTML клиенту. В Haunted это реализуется через render() из пакета haunted/ssr.

Установка
npm install haunted haunted/ssr
Использование
import { renderSSR } from 'haunted/ssr';
import { component, html, useState } from 'haunted';

function Greeting({ name }) {
  return html`<p>Привет, ${name}!</p>`;
}

const GreetingComponent = component(Greeting);

const { html: ssrHtml, elements } = renderSSR(GreetingComponent, { name: 'Мир' });

console.log(ssrHtml); // <p>Привет, Мир!</p>
  • renderSSR возвращает объект с полями:

    • html — строка с готовым HTML.
    • elements — массив виртуальных элементов для последующего гидратационного процесса на клиенте.
Гидратация

После SSR важно «оживить» компонент на клиенте:

import { component, html, useState } from 'haunted';
import { hydrate } from 'haunted/ssr';

function Greeting({ name }) {
  const [count, setCount] = useState(0);
  return html`
    <p>Привет, ${name}! Кликнули ${count} раз.</p>
    <button @click=${() => setCount(count + 1)}>Клик</button>
  `;
}

customElements.define('my-greeting', component(Greeting));

const serverHtml = `<my-greeting name="Мир"></my-greeting>`;
hydrate(document.body, serverHtml);
  • hydrate связывает существующий DOM с реактивной системой Haunted.
  • Все состояния и события становятся активными без повторного рендеринга на клиенте.

Работа с атрибутами и свойствами

В Haunted важно различать атрибуты и свойства веб-компонентов при SSR:

function UserCard({ user }) {
  return html`<div>
    <h2>${user.name}</h2>
    <p>${user.email}</p>
  </div>`;
}

customElements.define('user-card', component(UserCard));

// SSR
const { html: ssrHtml } = renderSSR(UserCard, { user: { name: 'Анна', email: 'anna@example.com' } });
  • Передача объектов через атрибуты невозможна напрямую, поэтому для сложных данных используется свойство компонента или JSON-сериализация.
  • Для статичных данных проще отдавать их сразу через HTML, а для динамичных — гидрировать на клиенте.

Асинхронные данные и SSR

Haunted поддерживает асинхронные хуки. Для SSR нужно учитывать, что сервер должен дождаться всех промисов перед выдачей HTML:

import { useState, useEffect } from 'haunted';

function AsyncData() {
  const [data, setData] = useState(null);

  useEffect(async () => {
    const res = await fetch('https://jsonplaceholder.typicode.com/todos/1');
    const json = await res.json();
    setData(json);
  }, []);

  return html`${data ? html`<pre>${JSON.stringify(data)}</pre>` : html`Загрузка...`}`;
}
  • На сервере можно предварительно получить данные и передать их как пропсы для компонента.
  • Это позволяет избежать мигания загрузки на клиенте.

Стилизация SSR-компонентов

Shadow DOM изначально изолирует стили, но при SSR можно использовать:

  • Inline CSS для серверного рендеринга.
  • CSS-in-JS решения, которые генерируют стили при гидратации.
  • Внешние стили подключаются отдельно, но важно помнить, что Shadow DOM не наследует глобальные стили.
const style = html`<style>
  p { color: blue; font-weight: bold; }
</style>`;

function StyledParagraph({ text }) {
  return html`${style}<p>${text}</p>`;
}

Интеграция с фреймворками

Haunted-компоненты легко вставляются в существующие SSR-фреймворки:

  • Next.js / Express: использовать renderSSR для генерации HTML на сервере.
  • Sapper / SvelteKit: вставлять как сторонние веб-компоненты с гидратацией.
  • Возможна совместная работа с React через кастомные элементы, без потери реактивности.

Основные преимущества SSR с Haunted

  • Быстрая загрузка и рендеринг на стороне сервера.
  • SEO-оптимизация благодаря готовому HTML.
  • Полная совместимость с веб-компонентами.
  • Гибкая интеграция с асинхронными данными и хуками.
  • Минимальный размер библиотеки и простой API по сравнению с React.

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