JSX в режиме automatic (React 17+)

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

Начиная с React 17, JSX больше не требует явного импорта React в каждом файле. Новый трансформ JSX разделяет ответственность между компилятором и рантаймом:

  • JSX больше не превращается в React.createElement
  • используется специализированный runtime (react/jsx-runtime)
  • импорт React выполняется автоматически только там, где он нужен
  • уменьшается шаблонный код и повышается оптимизация сборки

Esbuild поддерживает этот режим через опцию jsx: "automatic", которая генерирует современный JSX transform без необходимости вручную подключать React.


Базовая настройка Esbuild для automatic JSX

Основная конфигурация включает выбор JSX-режима:

import esbuild from 'esbuild';

esbuild.build({
  entryPoints: ['src/index.jsx'],
  bundle: true,
  outfile: 'dist/bundle.js',
  jsx: 'automatic'
});

В этом режиме Esbuild:

  • убирает необходимость import React from "react"
  • использует новый JSX runtime
  • корректно разделяет production и development трансформации

Для TypeScript используется аналогичная конфигурация:

esbuild.build({
  entryPoints: ['src/index.tsx'],
  bundle: true,
  outfile: 'dist/bundle.js',
  loader: {
    '.tsx': 'tsx'
  },
  jsx: 'automatic'
});

Разница между classic и automatic JSX

Classic runtime

jsx: 'transform'

JSX:

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

Трансформируется в:

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

Требуется:

import React from "react";

Automatic runtime

jsx: 'automatic'

Тот же JSX:

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

Трансформируется в:

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

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

Ключевые отличия:

  • нет необходимости импортировать React
  • используется react/jsx-runtime
  • улучшенная оптимизация tree-shaking
  • более компактный итоговый код

jsxImportSource и альтернативные рантаймы

Esbuild позволяет переопределить источник JSX runtime через jsxImportSource.

Это особенно важно при использовании альтернатив React-совместимых библиотек, например Preact.

Пример с Preact

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

JSX:

const app = <h1>Hello</h1>;

Будет преобразован в:

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

const app = _jsx("h1", { children: "Hello" });

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

TypeScript 4.1+ также поддерживает automatic JSX runtime через:

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

Однако при использовании Esbuild TypeScript используется только как транспайлер, поэтому важно согласовать настройки:

  • Esbuild: jsx: "automatic"
  • TS: jsx: "react-jsx" (если используется type-checking отдельно)

Несовпадение режимов может приводить к:

  • дублированию импортов React
  • неконсистентному runtime
  • ошибкам типов JSX namespace

Режим development и production

Esbuild автоматически различает режимы сборки:

esbuild.build({
  entryPoints: ['src/index.jsx'],
  bundle: true,
  outfile: 'dist/bundle.js',
  jsx: 'automatic',
  minify: true,
  sourcemap: true
});

В development режиме JSX может включать дополнительные проверки:

  • имена компонентов для DevTools
  • расширенные ошибки
  • debug-метаданные

Production-режим:

  • минимизированный runtime
  • сокращённые вызовы _jsx
  • удаление проверок

Особенности генерации JSX в Esbuild

Esbuild реализует собственный трансформ JSX, который отличается от Babel:

  • отсутствует сложная плагинная система
  • трансформация выполняется на уровне AST без расширяемости
  • поддержка JSX ограничена стандартом React и совместимыми runtime

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

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

Результат:

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

_jsx(_Fragment, {
  children: [
    _jsx("span", { children: "A" }),
    _jsx("span", { children: "B" })
  ]
});

Работа с кастомными JSX фабриками

Хотя automatic runtime предпочтителен, Esbuild позволяет использовать старые подходы через jsxFactory и jsxFragment.

esbuild.build({
  entryPoints: ['src/index.jsx'],
  bundle: true,
  outfile: 'dist/bundle.js',
  jsx: 'transform',
  jsxFactory: 'h',
  jsxFragment: 'Fragment'
});

Однако при jsx: "automatic" эти параметры игнорируются, поскольку runtime управляется через jsxImportSource.


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

Отсутствие React в bundle

При использовании automatic runtime React может не попасть в итоговый бандл, если:

  • нет явных импортов
  • используется внешняя поставка React (CDN / externals)

Это нормальное поведение tree-shaking системы Esbuild.


Несовместимость старых библиотек

Некоторые библиотеки ожидают classic runtime:

  • используют React.createElement
  • зависят от React в scope

В таких случаях требуется либо:

  • оставить jsx: "transform"
  • либо обеспечить совместимый runtime слой

JSX без расширения файлов

Esbuild не обрабатывает JSX в .js по умолчанию без указания loader:

loader: {
  '.js': 'jsx'
}

Иначе JSX код может интерпретироваться как обычный JavaScript и вызывать ошибки синтаксиса.


Производительность трансформации

Esbuild реализует JSX transform на Go-ядре, что даёт:

  • минимальное время сборки даже на больших проектах
  • отсутствие необходимости в кэшировании Babel
  • линейную масштабируемость при росте количества файлов

Особенно заметно на проектах с:

  • тысячами компонентов
  • монорепозиториями
  • SSR-бандлами

Взаимодействие с оптимизацией бандла

Automatic JSX напрямую влияет на итоговую оптимизацию:

  • более эффективный tree-shaking
  • меньше глобальных зависимостей
  • сокращение runtime overhead

Пример:

const Button = () => <button>OK</button>;

После сборки:

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

const Button = () => _jsx("button", { children: "OK" });

Без необходимости подключения React как глобальной зависимости.