Custom JSX pragma

Stencil — это компилятор для веб-компонентов, который использует JSX для описания разметки компонентов. Одной из ключевых особенностей JSX в Stencil является возможность настройки функции, которая интерпретирует JSX-элементы. Эта настройка реализуется через custom JSX pragma. Понимание работы с pragma позволяет изменять поведение рендеринга и интегрировать сторонние библиотеки или собственные абстракции поверх JSX.


Основы JSX pragma

JSX pragma — это функция, которая вызывается при трансформации JSX в вызовы JavaScript. По умолчанию Stencil использует функцию h (hyperscript) для генерации виртуальных DOM-элементов:

import { h } from '@stencil/core';

const element = 
Привет
; // Транспилируется в const element = h('div', { class: 'container' }, 'Привет');

Функция h принимает три основных параметра:

  1. Тип элемента — строка для стандартного HTML или класс компонента для пользовательских элементов.
  2. Свойства — объект с атрибутами и обработчиками событий.
  3. Дети — один элемент, массив элементов или текстовое содержимое.

Настройка custom JSX pragma

Stencil позволяет указать свою функцию для обработки JSX через настройку jsx в tsconfig.json или через комментарий /** @jsx */. Например:

/** @jsx myCreateElement */
import { myCreateElement } from './my-jsx-pragmas';

const element = ;

При компиляции будет преобразован в:

myCreateElement('button', { disabled: true }, 'Клик');

Такой подход полезен, когда необходимо:

  • интегрировать Stencil с альтернативными библиотеками виртуального DOM;
  • оборачивать элементы дополнительной логикой или стейт-менеджером;
  • добавлять атрибуты и классы автоматически без изменения JSX-разметки.

Пример реализации собственной функции pragma

Функция pragma может выглядеть так:

export function myCreateElement(tag: any, props: any, ...children: any[]) {
  // Добавление дополнительного класса ко всем div
  if (tag === 'div') {
    props = { ...props, class: (props?.class || '') + ' custom-div' };
  }

  // Логирование для отладки
  console.log('Создаётся элемент', tag, props, children);

  // Вызов стандартного h для совместимости со Stencil
  return h(tag, props, ...children);
}

Использование:

const app = 
Содержимое
; // На выходе создается div с классом "custom-div"

Особенности работы с TypeScript

Для корректной работы кастомного JSX pragma с TypeScript необходимо обновить типизацию JSX. В Stencil это делается через глобальный JSX namespace:

declare global {
  namespace JSX {
    interface IntrinsicElements {
      div: { id?: string; class?: string };
      button: { disabled?: boolean; onClick?: () => void };
    }
  }
}

Это гарантирует:

  • подсветку и проверку атрибутов в редакторе;
  • предотвращение передачи некорректных свойств;
  • сохранение автодополнения при работе с кастомным pragma.

Преимущества использования custom JSX pragma

  1. Контроль над созданием элементов — возможность добавлять атрибуты, оборачивать элементы или модифицировать их на лету.
  2. Интеграция с внешними библиотеками — можно использовать React-подобные подходы или виртуальные DOM-фреймворки.
  3. Легкость отладки — через логирование и проверку параметров каждого JSX-элемента.
  4. Расширяемость — создаются мосты для генерации динамических компонентов и шаблонов.

Ограничения и рекомендации

  • Не рекомендуется изменять базовую функцию h для стандартных нужд проекта без строгой необходимости. Это может усложнить миграцию и совместимость с обновлениями Stencil.
  • При создании сложной логики в pragma важно учитывать производительность, так как функция вызывается для каждого JSX-элемента.
  • Типизация и проверка типов обязательны для предотвращения ошибок в больших проектах.

Интеграция с компонентами Stencil

Custom JSX pragma полностью совместим с компонентами Stencil. Например:

import { Component, Prop, h } from '@stencil/core';
import { myCreateElement } from './my-jsx-pragmas';

@Component({
  tag: 'my-button',
  shadow: true
})
export class MyButton {
  @Prop() label: string;

  render() {
    /** @jsx myCreateElement */
    return ;
  }
}

В этом случае компонент использует кастомную логику для генерации кнопки, сохраняя при этом реактивность и поддержку Shadow DOM.