Опция jsxImportSource: автоматический импорт runtime

Поведение JSX при automatic runtime

При использовании JSX-трансформации современного формата (automatic) код JSX больше не требует явного подключения React в каждом файле. Вместо этого компилятор преобразует JSX-выражения в вызовы функций из специального runtime-модуля.

JSX:

const element = <div>Hello</div>;

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

import { jsx as _jsx } from "react/jsx-runtime";

const element = _jsx("div", { children: "Hello" });

Ключевая особенность заключается в том, что источник этих функций (jsx, jsxs, Fragment) задаётся через параметр runtime-библиотеки. Именно за это отвечает jsxImportSource.


Назначение jsxImportSource

jsxImportSource определяет модуль, из которого Esbuild импортирует функции JSX runtime при использовании режима automatic.

По умолчанию используется:

react

То есть:

import { jsx } from "react/jsx-runtime";

Если изменить источник, меняется сам модуль, откуда берётся runtime:

  • React: react/jsx-runtime
  • Preact: preact/jsx-runtime
  • Emotion: @emotion/react/jsx-runtime

Связь с jsx: automatic

Параметр jsxImportSource имеет смысл только при включённой автоматической трансформации:

jsx: "automatic"

Если используется classic runtime, то jsxImportSource игнорируется, так как код генерируется через React.createElement.


Конфигурация Esbuild (JavaScript API)

Пример настройки через API:

import * as esbuild from "esbuild";

await esbuild.build({
  entryPoints: ["src/index.jsx"],
  bundle: true,
  outfile: "dist/bundle.js",

  jsx: "automatic",
  jsxImportSource: "react"
});

В этом случае JSX:

const App = () => <h1>Title</h1>;

преобразуется в:

import { jsx as _jsx } from "react/jsx-runtime";

const App = () => _jsx("h1", { children: "Title" });

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

Preact

await esbuild.build({
  entryPoints: ["src/app.jsx"],
  bundle: true,
  outfile: "dist/app.js",
  jsx: "automatic",
  jsxImportSource: "preact"
});

Результат трансформации:

import { jsx as _jsx } from "preact/jsx-runtime";

const App = () => _jsx("div", { children: "Hello Preact" });

Emotion (CSS-in-JS)

jsxImportSource: "@emotion/react"
import { jsx as _jsx } from "@emotion/react/jsx-runtime";

Это позволяет автоматически подключать поддержку css пропсов без дополнительного импорта jsx.


CLI-конфигурация

При использовании CLI можно передать опцию через флаг:

esbuild src/index.jsx --bundle --outfile=dist/bundle.js --jsx=automatic --jsx-import-source=react

Важно: имя флага соответствует camelCase-параметру API.


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

В TypeScript существует аналогичная настройка:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "react"
  }
}

Однако при использовании Esbuild TypeScript не выполняет трансформацию JSX, если включена сборка через Esbuild. В этом случае:

  • TypeScript используется только для проверки типов
  • JSX трансформируется Esbuild
  • jsxImportSource должен быть синхронизирован между tsconfig и esbuild-конфигом

Отличие jsxImportSource от jsxFactory

Существуют два разных подхода к генерации JSX:

Classic runtime

jsx: "classic",
jsxFactory: "React.createElement"

Результат:

React.createElement("div", null, "text");

Automatic runtime

jsx: "automatic",
jsxImportSource: "react"

Результат:

import { jsx } from "react/jsx-runtime";

Ключевое различие:

  • jsxFactory управляет функцией создания элементов в classic-режиме
  • jsxImportSource управляет модулем runtime в automatic-режиме

Fragment и его источник

При использовании automatic runtime Fragment также импортируется из runtime-модуля:

import { Fragment as _Fragment } from "react/jsx-runtime";

Пример JSX:

<>
  <span>A</span>
  <span>B</span>
</>

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

import { Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";

const App = () =>
  _jsxs(_Fragment, {
    children: [
      _jsx("span", { children: "A" }),
      _jsx("span", { children: "B" })
    ]
  });

Множественные JSX runtime в одном проекте

В сложных сборках возможно разделение по разным источникам:

  • UI-библиотека использует react
  • дизайн-система использует @emotion/react

Но Esbuild применяет jsxImportSource глобально для текущей сборки, поэтому для гибридных проектов требуется:

  • либо разделение сборок
  • либо единый runtime-слой-адаптер

Поведение при отсутствии runtime экспортов

Если выбран неверный jsxImportSource, например:

jsxImportSource: "unknown-lib"

результатом будет генерация:

import { jsx } from "unknown-lib/jsx-runtime";

и ошибка возникнет уже на этапе выполнения, а не компиляции, так как Esbuild не проверяет наличие модулей.


Оптимизации и влияние на бандл

Использование automatic runtime с jsxImportSource даёт следующие эффекты:

  • устранение необходимости import React from "react"
  • уменьшение повторяющегося кода
  • более агрессивная tree-shaking оптимизация
  • снижение веса бандла за счёт точечных импортов jsx/jsxs

Особенно заметно при больших приложениях с тысячами JSX-узлов.


Совместимость с JSX-плагинами

Esbuild позволяет комбинировать:

  • JSX трансформацию
  • плагинную обработку (например, макросы)
  • алиасы модулей runtime

В таких случаях jsxImportSource становится частью общей цепочки трансформации AST, определяя базовый источник функций JSX runtime до применения дополнительных плагинов.


Типовые ошибки при настройке

Несоответствие runtime в разных инструментах

  • Esbuild: react
  • TypeScript: preact

Результат: некорректные импорты jsx-runtime


Использование classic runtime при ожидании automatic

jsx: "classic",
jsxImportSource: "react"

jsxImportSource игнорируется, что приводит к неожиданному React.createElement.


Отсутствие установленного runtime пакета

Например:

npm install react

без него генерация кода корректна, но выполнение завершится ошибкой.


Поведение внутри монорепозиториев

В монорепозиториях часто используется единая настройка:

  • root esbuild config задаёт jsxImportSource
  • пакеты наследуют runtime через общую сборку

При этом возможна переопределяемость на уровне отдельных build-скриптов для пакетов UI и приложений.


Итоговая модель трансформации

При включённой настройке:

jsx: "automatic",
jsxImportSource: "react"

Esbuild следует следующему принципу:

  1. JSX распознаётся как синтаксический сахар
  2. Определяется runtime-модуль из jsxImportSource
  3. Генерируются импорты jsx/jsxs/Fragment
  4. JSX заменяется вызовами runtime-функций
  5. Выполняется bundling с учётом tree-shaking

Этот механизм делает jsxImportSource центральным элементом управления JSX-экосистемой внутри Esbuild, определяющим не только синтаксис, но и архитектуру взаимодействия с runtime-библиотеками.