Опция jsxFactory и jsxFragment

В стандартной трансформации JSX в JavaScript используется функция React.createElement, однако в различных средах и библиотеках JSX может компилироваться в вызовы других фабричных функций. В esbuild за это отвечает опция jsxFactory.

По умолчанию esbuild преобразует JSX примерно следующим образом:

const element = <div>Hello</div>

в:

const element = React.createElement("div", null, "Hello");

Такое поведение актуально только при использовании React-совместимого режима и стандартной фабрики React.createElement.

Назначение jsxFactory

Опция jsxFactory позволяет заменить функцию, которая используется для создания JSX-элементов.

Это критично в случаях:

  • использования альтернативных библиотек (Preact, Inferno, Nerv и др.)
  • кастомных runtime JSX-функций
  • оптимизированных сборок, где h или аналог заменяет React

Синтаксис конфигурации

В esbuild настройка задаётся через API или CLI.

API (JavaScript)

require("esbuild").build({
  entryPoints: ["src/app.jsx"],
  bundle: true,
  outfile: "dist/bundle.js",
  jsxFactory: "h"
})

CLI

esbuild src/app.jsx --bundle --outfile=dist/bundle.js --jsx-factory=h

Пример трансформации

Исходный код:

const App = () => <div className="box">Text</div>

При jsxFactory: "h":

const App = () => h("div", { className: "box" }, "Text");

Использование с разными библиотеками

Preact

Preact использует функцию h вместо React.createElement.

jsxFactory: "h"

или с импортом:

import { h } from "preact";

В этом случае важно, чтобы runtime действительно содержал h.

Hyperscript-подобные реализации

Некоторые библиотеки используют сигнатуру:

h(type, props, ...children)

esbuild генерирует вызовы строго в таком формате.


jsxFragment в esbuild

JSX поддерживает фрагменты, позволяющие группировать элементы без дополнительного DOM-узла:

<>
  <div>A</div>
  <div>B</div>
</>

В стандартной React-сборке это превращается в:

React.createElement(React.Fragment, null,
  React.createElement("div", null, "A"),
  React.createElement("div", null, "B")
);

Опция jsxFragment управляет тем, чем заменяется React.Fragment.

Назначение jsxFragment

jsxFragment задаёт идентификатор или функцию, используемую для фрагментов JSX.

Это важно при:

  • использовании альтернатив React runtime
  • кастомных JSX трансформерах
  • оптимизации сборки под лёгкие runtime-библиотеки

Конфигурация jsxFragment

API

require("esbuild").build({
  entryPoints: ["src/app.jsx"],
  bundle: true,
  outfile: "dist/bundle.js",
  jsxFactory: "h",
  jsxFragment: "Fragment"
})

CLI

esbuild src/app.jsx --bundle --outfile=dist/bundle.js --jsx-factory=h --jsx-fragment=Fragment

Пример трансформации фрагментов

Исходный код:

const App = () => (
  <>
    <span>1</span>
    <span>2</span>
  </>
);

При настройках:

jsxFactory: "h"
jsxFragment: "Fragment"

Результат:

const App = () => h(Fragment, null,
  h("span", null, "1"),
  h("span", null, "2")
);

Взаимодействие jsxFactory и jsxFragment

Обе опции работают совместно и определяют поведение всей JSX-трансформации.

  • jsxFactory — создаёт элементы
  • jsxFragment — создаёт группы элементов без контейнера

Их комбинация определяет конечный runtime-вызов.

React-совместимый режим

Типичная конфигурация для React:

jsxFactory: "React.createElement",
jsxFragment: "React.Fragment"

Результат полностью совместим с React 16+.

Preact-совместимый режим

jsxFactory: "h",
jsxFragment: "Fragment"

При этом обычно импортируется:

import { h, Fragment } from "preact";

Особенности трансформации в esbuild

1. Отсутствие импорта React автоматически

esbuild не добавляет импорт React самостоятельно при использовании кастомных фабрик.

Если используется:

jsxFactory: "h"

необходимо явно импортировать h.


2. Статическая подстановка

esbuild не выполняет runtime-анализ JSX. Все замены происходят на этапе компиляции.


3. Единообразие сигнатуры

Все JSX-элементы преобразуются в одинаковую сигнатуру:

factory(type, props, ...children)

Для фрагментов:

factory(Fragment, null, children)

4. Совместимость с TypeScript

При использовании TypeScript важно синхронизировать настройки компилятора:

{
  "compilerOptions": {
    "jsx": "react",
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment"
  }
}

Если используется jsx: "react-jsx" (новый runtime), esbuild может конфликтовать с TS трансформацией, если не согласовать runtime.


Типичные сценарии использования

Альтернативные UI-библиотеки

Библиотеки, заменяющие React, часто требуют кастомной фабрики:

  • Preact (h)
  • Nerv
  • Inferno (createElement или h)

Лёгкие runtime без виртуального DOM

Некоторые фреймворки используют JSX как синтаксический сахар для прямых DOM-вызовов.

Пример:

jsxFactory: "dom"
jsxFragment: "fragment"

Встраиваемые DSL

JSX может использоваться не только для UI, но и для генерации структур:

<query>
  <select>name</select>
</query>

Тогда jsxFactory может быть любой функцией, например:

jsxFactory: "createNode"

Влияние на итоговый бандл

Выбор jsxFactory и jsxFragment напрямую влияет на:

  • размер итогового кода
  • наличие внешних зависимостей
  • возможность tree-shaking
  • совместимость с runtime

Минимальные фабрики (h) обычно дают более компактный результат, чем React.createElement.


Ошибки конфигурации

Неопределённая фабрика

Если указана строка:

jsxFactory: "h"

но h не существует в runtime, возникнет ReferenceError.


Несовпадение Fragment

Если используется:

jsxFragment: "Fragment"

но Fragment не импортирован, фрагменты ломают выполнение.


Конфликт с automatic JSX runtime

TypeScript с jsx: react-jsx уже генерирует вызовы к _jsx и _Fragment, что может конфликтовать с esbuild, если одновременно заданы кастомные фабрики.