R
Reface
Documentation

Template API Documentation

Основное использование

Template API предоставляет способ создания HTML-шаблонов с поддержкой атрибутов и вложенного содержимого.

В конечном итоге, Reface работает на уровне строк и генерирует чистый HTML, что делает его идеальным для серверного рендеринга. JSX - это просто удобная обертка для разработки, которая транслируется в те же вызовы Template API.

Создание шаблона

Есть два способа получить Template для HTML-элементов:

  1. Создание через функцию createTemplate:

example.typescripttypescript
1import { createTemplate } from "@reface/template";
2
3// Базовое создание элемента
4const div = createTemplate({ tag: "div" });
5const span = createTemplate({ tag: "span" });
  1. Использование готовых элементов из @reface/elements:

example.typescripttypescript
1import { button, div, span } from "@reface/elements";
2
3// Элементы уже готовы к использованию
4div`Hello world`; // <div>Hello world</div>
5button`Click me`; // <button>Click me</button>

@reface/elements предоставляет готовые Template-функции для всех HTML-элементов, что избавляет от необходимости создавать их вручную.

Принцип работы

Template построен на концепции цепочки трансформаций:

example.undefined
1createTemplateFactory() createTemplate() Template Template Template
2(создание фабрики) (базовый (с атрибутами) (с доп. (с контентом)
3 ​шаблон) атрибутами)

Каждый вызов в цепочке создает новый экземпляр:

example.typescripttypescript
1// 1. Создаем фабрику шаблонов
2const buttonFactory = createTemplateFactory({
3 type: "button",
4 transformer: (attrs, children) => createTemplate({ tag: "button", ... }),
5});
6
7// 2. Создаем базовый шаблон
8const baseButton = buttonFactory({ tag: "button" });
9 ​// Template<ButtonAttrs, ButtonPayload>
10
11// 3. Специализируем шаблон
12const primaryButton = baseButton({ class: "primary" });
13 ​// Новый Template с добавленными атрибутами
14
15// 4. Дальнейшая специализация
16const largeButton = primaryButton({ size: "large" });
17 ​// Еще один Template с накопленными атрибутами
18
19// 5. Финальное использование
20largeButton`Click me`; // <button class="primary" size="large">Click me</button>

Правила использования

Template поддерживает два типа вызовов:

  1. Передача атрибутов - через объект:

example.typescripttypescript
1// Атрибуты накапливаются при каждом вызове
2const btn = createTemplate({ class: "button" })(
3 ​// class="button"
4 { id: "submit" },
5)(
6 ​// class="button" id="submit"
7 { "data-type": "primary" },
8); // class="button" id="submit" data-type="primary"
  1. Передача содержимого - через tagged template literal:

example.typescripttypescript
1// Важно: каждый вызов с `` полностью заменяет предыдущее содержимое
2const btn = div({ class: "button" })`First content`;
3btn`New content`; // Предыдущее содержимое "First content" полностью заменено

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

Template предоставляет удобные способы работы с атрибутами:

  1. Классы можно передавать объектом с флагами:

example.typescripttypescript
1div({
2 class: {
3 button: true,
4 primary: true,
5 disabled: false,
6 large: someCondition,
7 },
8})`Click me`; // <div class="button primary">Click me</div>
  1. Стили передаются в camelCase:

example.typescripttypescript
1div({
2 style: {
3 backgroundColor: "red",
4 fontSize: "14px",
5 },
6})`Content`; // <div style="background-color: red; font-size: 14px">Content</div>
  1. Кавычки в атрибутах выбираются автоматически:

example.typescripttypescript
1div({
2 title: 'Contains "quotes"',
3 "data-message": "Simple text",
4})`Content`; // <div title='Contains "quotes"' data-message="Simple text">Content</div>

Безопасность

По умолчанию все строковые значения и переменные автоматически экранируются для предотвращения XSS-атак:

example.typescripttypescript
1const userInput = '<script>alert("XSS")</script>';
2div`${userInput}`; // <div>&lt;script&gt;alert("XSS")&lt;/script&gt;</div>
3
4// Переменные тоже безопасны
5const message = "<b>Hello</b>";
6span`${message}`; // <span>&lt;b&gt;Hello&lt;/b&gt;</span>

Для вставки доверенного HTML-контента используйте обертку html:

example.typescripttypescript
1import { html } from "@reface/template";
2
3const trusted = '<span class="highlight">Важный текст</span>';
4div`${html`${trusted}`}`; // <div><span class="highlight">Важный текст</span></div>
5
6⚠️ Используйте html только для доверенного контента!

Продвинутые возможности

Создание собственных типов Template

example.typescripttypescript
1const buttonFactory = createTemplateFactory<ButtonAttributes, ButtonPayload>({
2 type: "button",
3
4 create: {
5 ​// template - это RawTemplate без callable сигнатуры
6 transform: ({ attrs, children, manager }) => ({
7 tag: "button",
8 attributes: {
9 ...attrs,
10 class: manager.combineClasses("btn", attrs.class),
11 },
12 children,
13 payload: {
14 clickCount: 0,
15 },
16 }),
17
18 defaults: {
19 attributes: {
20 class: "default-class",
21 type: "button",
22 },
23 payload: {
24 clickCount: 0,
25 },
26 },
27 },
28
29 process: {
30 ​// template - это RawTemplate, содержит только данные
31 attributes: ({ oldAttrs, newAttrs, template, manager }) => ({
32 ...oldAttrs,
33 ...newAttrs,
34 class: manager.combineClasses(oldAttrs.class, newAttrs.class),
35 }),
36
37 children: ({ oldChildren, newChildren, template }) => {
38 return [...oldChildren, ...newChildren];
39 },
40 },
41
42 methods: {
43 ​// template - это RawTemplate, для работы с данными
44 incrementClicks: ({ template }) => {
45 template.payload.clickCount++;
46 return template;
47 },
48 getClickCount: ({ template }) => template.payload.clickCount,
49 },
50
51 render: {
52 ​// template - это RawTemplate, только для чтения данных
53 template: ({ template, manager }) => {
54 return `<custom-button>${
55 manager.renderChildren(
56 template.children,
57 )
58 }</custom-button>`;
59 },
60
61 attributes: ({ attrs, template, manager }) =>
62 manager.renderAttributes(attrs, { prefix: "data-" }),
63
64 styles: ({ styles, template, manager }) => manager.renderStyles(styles),
65
66 classes: ({ classes, template, manager }) => manager.renderClasses(classes),
67 },
68});

Важно:

  1. Во всех методах параметр template является RawTemplate - это объект, содержащий только данные шаблона, без возможности вызова. Callable сигнатура (template() и template``) доступна только в финальном Template после создания.

  2. RawTemplate хранит данные в нормализованном виде:

    • attributes - обычные атрибуты

    • attributes.classes - массив классов

    • attributes.styles - объект стилей

    • children - массив дочерних элементов

    • payload - пользовательские данные

Это позволяет удобно работать с данными внутри методов фабрики, а RenderManager предоставляет утилиты для работы с этими нормализованными данными.

Основные разделы API:

  1. type - идентификатор типа шаблона

  2. create - настройки создания шаблона

    • transform({ attrs, children, manager }) - трансформация входных параметров

    • defaults - значения по умолчанию

  3. process - обработка вызовов

    • attributes({ oldAttrs, newAttrs, template, manager }) - обработка атрибутов

    • children({ oldChildren, newChildren, template }) - обработка содержимого

  4. methods - методы экземпляра, получают { template }

  5. render - настройки рендеринга, все методы получают { template, manager }

    • template({ template, manager }) - рендеринг всего шаблона

    • attributes({ attrs, template, manager }) - рендеринг атрибутов

    • styles({ styles, template, manager }) - рендеринг стилей

    • classes({ classes, template, manager }) - рендеринг классов

RenderManager предоставляет полезные утилиты для работы с шаблонами:

  • combineClasses - объединение классов

  • renderChildren - рендеринг дочерних элементов

  • renderAttributes - рендеринг атрибутов

  • renderStyles - рендеринг стилей

  • renderClasses - рендеринг классов

Пример: Создание styled компонентов

Пример того, как можно использовать createTemplateFactory для создания styled-components (уже реализовано в @reface/styled):

example.typescripttypescript
1// 1. Создаем фабрику для styled компонентов
2const styledFactory = createTemplateFactory<StyledAttributes, StyledPayload>({
3 type: "styled",
4
5 create: {
6 transform: ({ attrs, children, manager }) => {
7 ​// Парсим CSS из children и создаем уникальный класс
8 const css = parseCss(children);
9 const className = generateUniqueClassName();
10
11 return {
12 tag: attrs.tag || "div",
13 attributes: {
14 ...attrs,
15 class: manager.combineClasses(className, attrs.class),
16 },
17 children: [],
18 payload: {
19 css,
20 className,
21 },
22 };
23 },
24 },
25
26 render: {
27 template: ({ template, manager }) => {
28 ​// CSS добавится в head при рендеринге
29 return `<${template.tag} ${
30 manager.renderAttributes(
31 template.attributes,
32 )
33 }>${manager.renderChildren(template.children)}</${template.tag}>
34 ​<style>.${template.payload.className} { ${template.payload.css} }</style>`;
35 },
36 },
37});
38
39// 2. Создаем API для styled
40const styled = {
41 div: (strings: TemplateStringsArray, ...values: any[]) => {
42 return styledFactory({
43 tag: "div",
44 payload: { styles: strings },
45 });
46 },
47 span: (strings: TemplateStringsArray, ...values: any[]) => {
48 return styledFactory({
49 tag: "span",
50 payload: { styles: strings },
51 });
52 },
53 ​// ... другие элементы
54};
55
56// Использование (уже доступно в @reface/styled)
57const RedButton = styled.div`
58 ​& {
59 color: red;
60 padding: 10px;
61 }
62
63 ​&:hover {
64 color: darkred;
65 }
66`;
67
68RedButton`Click me!`; // <div class="styled-xxx">Click me!</div>
69// + <style>.styled-xxx { color: red; ... }</style>

⚠️ Примечание: Это упрощенный пример. Полная реализация styled-components уже доступна в пакете @reface/styled.

Альтернативные способы создания

  1. Использование createTemplate без указания тега:

example.typescripttypescript
1import { createTemplate } from "@reface/template";
2
3// Создание шаблона без тега - будет использоваться DocumentFragment
4const fragment = createTemplate({});
5fragment`Hello world`; // Hello world (без обертки)
6
7// Удобно для группировки элементов
8fragment`
9 ​${div`First`}
10 ​${div`Second`}
11`; // <div>First</div><div>Second</div>

Типы

TemplatePayload

Базовый интерфейс для пользовательских данных, который могут расширять плагины:

example.typescripttypescript
1interface TemplatePayload {
2 ​// Базовые поля, которые могут использовать все шаблоны
3 [key: string]: any;
4}

RawTemplate

Базовая структура данных шаблона:

example.typescripttypescript
1interface RawTemplate<
2 Attributes = any,
3 Payload extends TemplatePayload = TemplatePayload,
4> {
5 ​// Тип шаблона
6 type: string;
7 ​// HTML тег
8 tag?: string;
9 ​// Нормализованные атрибуты
10 attributes: {
11 ​// Обычные HTML атрибуты
12 [key: string]: any;
13 ​// Массив классов
14 classes?: string[];
15 ​// Объект стилей
16 styles?: Record<string, string>;
17 };
18 ​// Массив дочерних элементов
19 children: any[];
20 ​// Пользовательские данные
21 payload: Payload;
22}

Template

Callable интерфейс для работы с шаблоном:

example.typescripttypescript
1interface Template<Attributes = any, Payload = any> {
2 ​// Вызов с атрибутами - возвращает новый Template
3 (attributes: Attributes): Template<Attributes, Payload>;
4
5 ​// Вызов с содержимым - возвращает новый Template
6 (strings: TemplateStringsArray, ...values: any[]): Template<
7 Attributes,
8 Payload
9 ​>;
10
11 ​// Доступ к RawTemplate
12 raw: RawTemplate<Attributes, Payload>;
13}

TemplateFactory

Функция для создания шаблонов с кастомной логикой:

example.typescripttypescript
1interface TemplateFactory<Attributes = any, Payload = any> {
2 ​// Создание нового шаблона
3 (config: TemplateConfig): Template<Attributes, Payload>;
4
5 ​// Тип создаваемых шаблонов
6 type: string;
7}
8
9// Конфигурация для создания шаблона
10interface TemplateConfig<Attributes = any, Payload = any> {
11 tag?: string;
12 attributes?: Attributes;
13 children?: any[];
14 payload?: Payload;
15}

TemplateFactoryConfig

Конфигурация для создания фабрики шаблонов:

example.typescripttypescript
1interface TemplateFactoryConfig<Attributes = any, Payload = any> {
2 ​// Уникальный тип шаблона
3 type: string;
4
5 ​// Настройки создания шаблона
6 create: {
7 ​// Трансформация входных параметров
8 transform: ({
9 attrs: Attributes,
10 children: any[],
11 manager: RenderManager
12 }) => RawTemplate<Attributes, Payload>;
13
14 ​// Значения по умолчанию
15 defaults?: {
16 attributes?: Attributes;
17 payload?: Payload;
18 };
19 };
20
21 ​// Настройки обработки вызовов
22 process?: {
23 ​// Обработка атрибутов
24 attributes?: ({
25 oldAttrs: Attributes,
26 newAttrs: Attributes,
27 template: RawTemplate<Attributes, Payload>,
28 manager: RenderManager
29 }) => Attributes;
30
31 ​// Обработка children
32 children?: ({
33 oldChildren: any[],
34 newChildren: any[],
35 template: RawTemplate<Attributes, Payload>
36 }) => any[];
37 };
38
39 ​// Методы экземпляра
40 methods?: {
41 [key: string]: ({
42 template: RawTemplate<Attributes, Payload>
43 }) => any;
44 };
45
46 ​// Настройки рендеринга
47 render?: {
48 ​// Рендеринг всего шаблона
49 template?: ({
50 template: RawTemplate<Attributes, Payload>,
51 manager: RenderManager
52 }) => string;
53
54 ​// Рендеринг атрибутов
55 attributes?: ({
56 attrs: Attributes,
57 template: RawTemplate<Attributes, Payload>,
58 manager: RenderManager
59 }) => string;
60
61 ​// Рендеринг стилей
62 styles?: ({
63 styles: Record<string, string>,
64 template: RawTemplate<Attributes, Payload>,
65 manager: RenderManager
66 }) => string;
67
68 ​// Рендеринг классов
69 classes?: ({
70 classes: string[],
71 template: RawTemplate<Attributes, Payload>,
72 manager: RenderManager
73 }) => string;
74 };
75}

Эти типы формируют основу системы шаблонов:

  1. RawTemplate - хранит данные в нормализованном виде

  2. Template - предоставляет интерфейс для работы с шаблоном

  3. TemplateFactory - создает шаблоны с кастомной логикой

  4. TemplateFactoryConfig - конфигурация для создания фабрики шаблонов

JSX Поддержка

@reface/jsx предоставляет возможность использовать JSX синтаксис с Template API:

example.typescripttypescript
1import { createElement, Fragment } from "@reface/jsx";
2import { button, div } from "@reface/elements";
3
4// JSX транслируется в вызовы template
5const App = () => (
6 ​<>
7 ​<div className="container">
8 ​<button type="button">Click me</button>
9 ​</div>
10 ​</>
11);
12
13// Эквивалентно:
14const App = () =>
15 div({ class: "container" })`
16 ​${button({ type: "button" })`Click me`}
17 ​`;
18
19// Можно комбинировать JSX и Template API
20const Header = () => (
21 ​<div className="header">{button({ class: "primary" })`Submit`}</div>
22);
23
24// И наоборот
25const Container = () =>
26 div({ class: "container" })`
27 ​${<button type="button">Click me</button>}
28 ​`;

JSX это просто синтаксический сахар, который транслируется в вызовы Template API:

  1. createElement преобразует JSX в вызовы template

  2. Fragment позволяет группировать элементы без обертки

  3. Можно свободно комбинировать JSX и Template API в одном компоненте

⚠️ Для использования JSX необходимо настроить TypeScript/Babel для работы с @reface/jsx.

example.jsonjson
1{
2 "compilerOptions": {
3 "jsx": "react",
4 "jsxFactory": "createElement",
5 "jsxFragmentFactory": "Fragment"
6 }
7}