Template API Documentation
Основное использование
Template API предоставляет способ создания HTML-шаблонов с поддержкой атрибутов и вложенного содержимого.
В конечном итоге, Reface работает на уровне строк и генерирует чистый HTML, что делает его идеальным для серверного рендеринга. JSX - это просто удобная обертка для разработки, которая транслируется в те же вызовы Template API.
Создание шаблона
Есть два способа получить Template для HTML-элементов:
Создание через функцию createTemplate:
1import { createTemplate } from "@reface/template";23// Базовое создание элемента4const div = createTemplate({ tag: "div" });5const span = createTemplate({ tag: "span" });
Использование готовых элементов из @reface/elements:
1import { button, div, span } from "@reface/elements";23// Элементы уже готовы к использованию4div`Hello world`; // <div>Hello world</div>5button`Click me`; // <button>Click me</button>
@reface/elements предоставляет готовые Template-функции для всех HTML-элементов, что избавляет от необходимости создавать их вручную.
Принцип работы
Template построен на концепции цепочки трансформаций:
1createTemplateFactory() createTemplate() Template Template Template2(создание фабрики) (базовый (с атрибутами) (с доп. (с контентом)3 шаблон) атрибутами)
Каждый вызов в цепочке создает новый экземпляр:
1// 1. Создаем фабрику шаблонов2const buttonFactory = createTemplateFactory({3 type: "button",4 transformer: (attrs, children) => createTemplate({ tag: "button", ... }),5});67// 2. Создаем базовый шаблон8const baseButton = buttonFactory({ tag: "button" });9 // Template<ButtonAttrs, ButtonPayload>1011// 3. Специализируем шаблон12const primaryButton = baseButton({ class: "primary" });13 // Новый Template с добавленными атрибутами1415// 4. Дальнейшая специализация16const largeButton = primaryButton({ size: "large" });17 // Еще один Template с накопленными атрибутами1819// 5. Финальное использование20largeButton`Click me`; // <button class="primary" size="large">Click me</button>
Правила использования
Template поддерживает два типа вызовов:
Передача атрибутов - через объект:
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"
Передача содержимого - через tagged template literal:
1// Важно: каждый вызов с `` полностью заменяет предыдущее содержимое2const btn = div({ class: "button" })`First content`;3btn`New content`; // Предыдущее содержимое "First content" полностью заменено
Работа с атрибутами
Template предоставляет удобные способы работы с атрибутами:
Классы можно передавать объектом с флагами:
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>
Стили передаются в camelCase:
1div({2 style: {3 backgroundColor: "red",4 fontSize: "14px",5 },6})`Content`; // <div style="background-color: red; font-size: 14px">Content</div>
Кавычки в атрибутах выбираются автоматически:
1div({2 title: 'Contains "quotes"',3 "data-message": "Simple text",4})`Content`; // <div title='Contains "quotes"' data-message="Simple text">Content</div>
Безопасность
По умолчанию все строковые значения и переменные автоматически экранируются для предотвращения XSS-атак:
1const userInput = '<script>alert("XSS")</script>';2div`${userInput}`; // <div><script>alert("XSS")</script></div>34// Переменные тоже безопасны5const message = "<b>Hello</b>";6span`${message}`; // <span><b>Hello</b></span>
Для вставки доверенного HTML-контента используйте обертку html:
1import { html } from "@reface/template";23const trusted = '<span class="highlight">Важный текст</span>';4div`${html`${trusted}`}`; // <div><span class="highlight">Важный текст</span></div>56⚠️ Используйте html только для доверенного контента!
Продвинутые возможности
Создание собственных типов Template
1const buttonFactory = createTemplateFactory<ButtonAttributes, ButtonPayload>({2 type: "button",34 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 }),1718 defaults: {19 attributes: {20 class: "default-class",21 type: "button",22 },23 payload: {24 clickCount: 0,25 },26 },27 },2829 process: {30 // template - это RawTemplate, содержит только данные31 attributes: ({ oldAttrs, newAttrs, template, manager }) => ({32 ...oldAttrs,33 ...newAttrs,34 class: manager.combineClasses(oldAttrs.class, newAttrs.class),35 }),3637 children: ({ oldChildren, newChildren, template }) => {38 return [...oldChildren, ...newChildren];39 },40 },4142 methods: {43 // template - это RawTemplate, для работы с данными44 incrementClicks: ({ template }) => {45 template.payload.clickCount++;46 return template;47 },48 getClickCount: ({ template }) => template.payload.clickCount,49 },5051 render: {52 // template - это RawTemplate, только для чтения данных53 template: ({ template, manager }) => {54 return `<custom-button>${55 manager.renderChildren(56 template.children,57 )58 }</custom-button>`;59 },6061 attributes: ({ attrs, template, manager }) =>62 manager.renderAttributes(attrs, { prefix: "data-" }),6364 styles: ({ styles, template, manager }) => manager.renderStyles(styles),6566 classes: ({ classes, template, manager }) => manager.renderClasses(classes),67 },68});
Важно:
Во всех методах параметр
template
являетсяRawTemplate
- это объект, содержащий только данные шаблона, без возможности вызова. Callable сигнатура (template()
и template``) доступна только в финальном Template после создания.RawTemplate хранит данные в нормализованном виде:
attributes
- обычные атрибутыattributes.classes
- массив классовattributes.styles
- объект стилейchildren
- массив дочерних элементовpayload
- пользовательские данные
Это позволяет удобно работать с данными внутри методов фабрики, а RenderManager предоставляет утилиты для работы с этими нормализованными данными.
Основные разделы API:
type
- идентификатор типа шаблонаcreate
- настройки создания шаблонаtransform({ attrs, children, manager })
- трансформация входных параметровdefaults
- значения по умолчанию
process
- обработка вызововattributes({ oldAttrs, newAttrs, template, manager })
- обработка атрибутовchildren({ oldChildren, newChildren, template })
- обработка содержимого
methods
- методы экземпляра, получают{ template }
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):
1// 1. Создаем фабрику для styled компонентов2const styledFactory = createTemplateFactory<StyledAttributes, StyledPayload>({3 type: "styled",45 create: {6 transform: ({ attrs, children, manager }) => {7 // Парсим CSS из children и создаем уникальный класс8 const css = parseCss(children);9 const className = generateUniqueClassName();1011 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 },2526 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});3839// 2. Создаем API для styled40const 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};5556// Использование (уже доступно в @reface/styled)57const RedButton = styled.div`58 & {59 color: red;60 padding: 10px;61 }6263 &:hover {64 color: darkred;65 }66`;6768RedButton`Click me!`; // <div class="styled-xxx">Click me!</div>69// + <style>.styled-xxx { color: red; ... }</style>
⚠️ Примечание: Это упрощенный пример. Полная реализация styled-components уже доступна в пакете @reface/styled.
Альтернативные способы создания
Использование createTemplate без указания тега:
1import { createTemplate } from "@reface/template";23// Создание шаблона без тега - будет использоваться DocumentFragment4const fragment = createTemplate({});5fragment`Hello world`; // Hello world (без обертки)67// Удобно для группировки элементов8fragment`9 ${div`First`}10 ${div`Second`}11`; // <div>First</div><div>Second</div>
Типы
TemplatePayload
Базовый интерфейс для пользовательских данных, который могут расширять плагины:
1interface TemplatePayload {2 // Базовые поля, которые могут использовать все шаблоны3 [key: string]: any;4}
RawTemplate
Базовая структура данных шаблона:
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 интерфейс для работы с шаблоном:
1interface Template<Attributes = any, Payload = any> {2 // Вызов с атрибутами - возвращает новый Template3 (attributes: Attributes): Template<Attributes, Payload>;45 // Вызов с содержимым - возвращает новый Template6 (strings: TemplateStringsArray, ...values: any[]): Template<7 Attributes,8 Payload9 >;1011 // Доступ к RawTemplate12 raw: RawTemplate<Attributes, Payload>;13}
TemplateFactory
Функция для создания шаблонов с кастомной логикой:
1interface TemplateFactory<Attributes = any, Payload = any> {2 // Создание нового шаблона3 (config: TemplateConfig): Template<Attributes, Payload>;45 // Тип создаваемых шаблонов6 type: string;7}89// Конфигурация для создания шаблона10interface TemplateConfig<Attributes = any, Payload = any> {11 tag?: string;12 attributes?: Attributes;13 children?: any[];14 payload?: Payload;15}
TemplateFactoryConfig
Конфигурация для создания фабрики шаблонов:
1interface TemplateFactoryConfig<Attributes = any, Payload = any> {2 // Уникальный тип шаблона3 type: string;45 // Настройки создания шаблона6 create: {7 // Трансформация входных параметров8 transform: ({9 attrs: Attributes,10 children: any[],11 manager: RenderManager12 }) => RawTemplate<Attributes, Payload>;1314 // Значения по умолчанию15 defaults?: {16 attributes?: Attributes;17 payload?: Payload;18 };19 };2021 // Настройки обработки вызовов22 process?: {23 // Обработка атрибутов24 attributes?: ({25 oldAttrs: Attributes,26 newAttrs: Attributes,27 template: RawTemplate<Attributes, Payload>,28 manager: RenderManager29 }) => Attributes;3031 // Обработка children32 children?: ({33 oldChildren: any[],34 newChildren: any[],35 template: RawTemplate<Attributes, Payload>36 }) => any[];37 };3839 // Методы экземпляра40 methods?: {41 [key: string]: ({42 template: RawTemplate<Attributes, Payload>43 }) => any;44 };4546 // Настройки рендеринга47 render?: {48 // Рендеринг всего шаблона49 template?: ({50 template: RawTemplate<Attributes, Payload>,51 manager: RenderManager52 }) => string;5354 // Рендеринг атрибутов55 attributes?: ({56 attrs: Attributes,57 template: RawTemplate<Attributes, Payload>,58 manager: RenderManager59 }) => string;6061 // Рендеринг стилей62 styles?: ({63 styles: Record<string, string>,64 template: RawTemplate<Attributes, Payload>,65 manager: RenderManager66 }) => string;6768 // Рендеринг классов69 classes?: ({70 classes: string[],71 template: RawTemplate<Attributes, Payload>,72 manager: RenderManager73 }) => string;74 };75}
Эти типы формируют основу системы шаблонов:
RawTemplate
- хранит данные в нормализованном видеTemplate
- предоставляет интерфейс для работы с шаблономTemplateFactory
- создает шаблоны с кастомной логикойTemplateFactoryConfig
- конфигурация для создания фабрики шаблонов
JSX Поддержка
@reface/jsx предоставляет возможность использовать JSX синтаксис с Template API:
1import { createElement, Fragment } from "@reface/jsx";2import { button, div } from "@reface/elements";34// JSX транслируется в вызовы template5const App = () => (6 <>7 <div className="container">8 <button type="button">Click me</button>9 </div>10 </>11);1213// Эквивалентно:14const App = () =>15 div({ class: "container" })`16 ${button({ type: "button" })`Click me`}17 `;1819// Можно комбинировать JSX и Template API20const Header = () => (21 <div className="header">{button({ class: "primary" })`Submit`}</div>22);2324// И наоборот25const Container = () =>26 div({ class: "container" })`27 ${<button type="button">Click me</button>}28 `;
JSX это просто синтаксический сахар, который транслируется в вызовы Template API:
createElement
преобразует JSX в вызовы templateFragment
позволяет группировать элементы без оберткиМожно свободно комбинировать JSX и Template API в одном компоненте
⚠️ Для использования JSX необходимо настроить TypeScript/Babel для работы с @reface/jsx.
1{2 "compilerOptions": {3 "jsx": "react",4 "jsxFactory": "createElement",5 "jsxFragmentFactory": "Fragment"6 }7}