Поддержка JSX

JSX представляет собой синтаксис, позволяющий описывать структуру пользовательского интерфейса в виде декларативного дерева, внешне похожего на XML. На уровне компиляции JSX не является исполняемым JavaScript и требует трансформации в вызовы функций. В контексте SWC (Speedy Web Compiler) эта трансформация реализуется как часть этапа обработки AST и тесно интегрирована с JavaScript/TypeScript парсером.

SWC выполняет преобразование JSX в стандартные JavaScript-выражения с высокой скоростью, сохраняя при этом совместимость с экосистемой React и другими библиотеками, использующими JSX-синтаксис.


Парсинг JSX в SWC

Обработка JSX начинается на этапе синтаксического анализа. SWC использует собственный парсер, который поддерживает JSX как часть грамматики JavaScript/TypeScript.

Ключевая настройка включения JSX-парсинга находится в конфигурации jsc.parser:

{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": true
    }
  }
}

или для Jav * aScript:

{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": true
    }
  }
}

При включении jsx: true парсер начинает интерпретировать конструкции вида:

const element = <div className="box">Hello</div>;

как JSXElement AST-узлы вместо обычного сравнения операторов «меньше/больше».


Базовая трансформация JSX

SWC преобразует JSX в вызовы функций, которые создают виртуальные элементы. Классическая схема трансформации основана на функции React.createElement.

Пример входного кода:

const element = <button disabled>Click</button>;

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

const element = React.createElement(
  "button",
  { disabled: true },
  "Click"
);

Параметры трансформации определяются настройками:

  • jsxFactory — функция создания элемента
  • jsxFragmentFactory — функция для фрагментов
  • pragma — устаревший аналог для классического JSX

Режим classic и automatic runtime

SWC поддерживает два основных режима JSX-трансформации:

Classic runtime

Использует явный вызов React.createElement. Требует наличия React в области видимости.

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

{
  "jsc": {
    "transform": {
      "react": {
        "runtime": "classic"
      }
    }
  }
}

Особенности:

  • требуется импорт React
  • полностью контролируемая генерация вызовов
  • совместимость со старыми проектами

Automatic runtime

Новый механизм трансформации, введённый в React 17+, устраняет необходимость явного импорта React.

{
  "jsc": {
    "transform": {
      "react": {
        "runtime": "automatic",
        "importSource": "react"
      }
    }
  }
}

В этом режиме SWC генерирует импорт функций из JSX runtime:

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

const element = _jsx("button", {
  disabled: true,
  children: "Click"
});

Обработка JSX-фрагментов

JSX-фрагменты:

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

в classic runtime:

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

в automatic runtime:

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

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

Параметры трансформации React в SWC

Конфигурация jsc.transform.react управляет поведением JSX-трансформации:

{
  "jsc": {
    "transform": {
      "react": {
        "runtime": "automatic",
        "development": false,
        "refresh": false,
        "throwIfNamespace": true,
        "useBuiltins": false
      }
    }
  }
}

development

Флаг development включает дополнительные проверки и расширенную отладочную информацию:

  • добавление displayName для компонентов
  • более подробные ошибки
  • дополнительный код для диагностики

Пример:

{
  "development": true
}

throwIfNamespace

Контролирует поведение при использовании XML-неймспейсов:

<svg:rect />

При true — выбрасывается ошибка, при false — допускается обработка.


importSource

Определяет источник JSX runtime функций:

{
  "importSource": "preact"
}

В этом случае SWC генерирует:

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

JSX в TypeScript (TSX)

SWC поддерживает TSX без дополнительных трансформеров. При использовании TypeScript парсер автоматически расширяет грамматику.

{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": true
    }
  }
}

Типизация удаляется на этапе компиляции, а JSX остаётся для трансформации.

Пример:

type Props = {
  title: string;
};

const Component = (props: Props) => {
  return <h1>{props.title}</h1>;
};

После компиляции:

const Component = (props) => {
  return _jsx("h1", {
    children: props.title
  });
};

Вложенные JSX-структуры

SWC рекурсивно обходит дерево JSX и преобразует каждый узел.

const tree = (
  <div>
    <header>
      <h1>Title</h1>
    </header>
    <main>
      <article>
        <p>Text</p>
      </article>
    </main>
  </div>
);

Результат представляет собой вложенные вызовы функций, где каждый уровень дерева превращается в аргументы children.


Обработка выражений внутри JSX

JSX поддерживает встроенные JavaScript-выражения:

const value = 10;
const el = <div>{value * 2}</div>;

SWC преобразует выражения в аргументы children:

const value = 10;
const el = _jsx("div", {
  children: value * 2
});

Для массивов:

<ul>
  {items.map(i => <li>{i}</li>)}
</ul>

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

_jsx("ul", {
  children: items.map(i => _jsx("li", {
    children: i
  }))
});

Стабильность ключей и оптимизация

SWC не выполняет React-специфическую оптимизацию ключей, но сохраняет key как обычное свойство:

const list = items.map(i => <div key={i.id}>{i.name}</div>);

Трансформация:

items.map(i =>
  _jsx("div", {
    key: i.id,
    children: i.name
  })
);

Отличия трансформации SWC от Babel

Поведение SWC в JSX-трансформации близко к Babel, но отличается реализацией:

  • SWC использует Rust-реализацию AST, что ускоряет парсинг
  • меньше промежуточных преобразований
  • более агрессивная оптимизация на уровне компиляции
  • единый pipeline для JS и TS

Babel чаще использует последовательность плагинов, SWC — монолитный трансформер.


Интеграция JSX-трансформации в сборочные системы

SWC применяется как транспайлер в различных окружениях:

  • Next.js (через встроенный SWC compiler)
  • webpack (через swc-loader)
  • Vite (через плагины SWC)
  • standalone CLI (@swc/cli)

Пример использования в webpack:

module.exports = {
  module: {
    rules: [
      {
        test: /\.[jt]sx?$/,
        use: {
          loader: "swc-loader",
          options: {
            jsc: {
              parser: {
                syntax: "typescript",
                tsx: true
              },
              transform: {
                react: {
                  runtime: "automatic"
                }
              }
            }
          }
        }
      }
    ]
  }
};

Генерация source maps при JSX трансформации

SWC поддерживает генерацию source maps для JSX-кода, сохраняя соответствие между исходным JSX и итоговым JavaScript.

{
  "sourceMaps": true,
  "jsc": {
    "transform": {
      "react": {
        "runtime": "automatic"
      }
    }
  }
}

Source maps особенно важны при отладке вложенных JSX-структур и динамических выражений.


Поведение JSX при минификации

В режиме минификации SWC может:

  • сокращать вспомогательные переменные JSX runtime
  • упрощать структуру children
  • удалять dev-only свойства при development: false

Пример оптимизации:

<div><span /></div>

может быть преобразован в минимизированный вызов без промежуточных идентификаторов:

_jsx("div", { children: _jsx("span", {}) });

Особенности обработки вложенных компонентов

JSX-компоненты с пользовательскими функциями трансформируются аналогично HTML-тегам:

const App = () => <Header />;
const App = () => _jsx(Header, {});

SWC не различает функциональные и классовые компоненты на уровне трансформации — различие проявляется только во время исполнения.