Файлы .ts, .tsx и опция jsx

esbuild поддерживает TypeScript как часть встроенного пайплайна трансформации, не прибегая к отдельному этапу компиляции через tsc. Основной принцип работы заключается в удалении типов и преобразовании синтаксиса в JavaScript без проверки типов.

Файлы с расширениями .ts и .tsx обрабатываются через встроенный загрузчик ts и tsx, которые активируются автоматически при указании соответствующих расширений или явно через конфигурацию loader.

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

esbuild выполняет:

  • удаление всех TypeScript-типов (type annotations, interfaces, enums в некоторых режимах);
  • преобразование современных конструкций ECMAScript;
  • трансформацию JSX (для .tsx);
  • сохранение семантики модулей ES.

При этом проверка типов не выполняется. Это ключевое архитектурное отличие от tsc. Вся ответственность за типовую корректность переносится на внешние инструменты или IDE.

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

// input.ts
function sum(a: number, b: number): number {
  return a + b;
}
// output.js
function sum(a, b) {
  return a + b;
}

Типы полностью удаляются без анализа их корректности.


Ограничения TypeScript в esbuild

Несмотря на поддержку синтаксиса, esbuild не реализует полноценный TypeScript-компилятор. Это приводит к ряду ограничений:

  • отсутствует проверка типов (type checking);
  • игнорируются tsconfig-правила, связанные с типами (например, noImplicitAny);
  • не поддерживаются генерация .d.ts;
  • не обрабатываются const enums в строгом режиме так, как это делает tsc.

При этом esbuild корректно обрабатывает большинство синтаксических конструкций:

  • type и interface полностью удаляются;
  • enum преобразуются в JavaScript-объекты;
  • namespace поддерживаются ограниченно и сводятся к IIFE-структурам.

Loader для .ts и .tsx

Тип загрузчика определяется либо автоматически, либо явно:

export default {
  loader: {
    '.ts': 'ts',
    '.tsx': 'tsx'
  }
}

Встроенные режимы:

  • ts — для обычных TypeScript-файлов без JSX;
  • tsx — для TypeScript с JSX-разметкой;
  • jsx — для JavaScript с JSX;
  • js — стандартный JavaScript.

Loader определяет не только парсинг, но и необходимость JSX-трансформации.


Обработка JSX в .tsx файлах

Файлы .tsx представляют собой комбинацию TypeScript и JSX. При их обработке esbuild выполняет две ключевые операции:

  • удаление типов TypeScript;
  • трансформация JSX в JavaScript вызовы.

JSX преобразуется в зависимости от конфигурации параметра jsx.


Опция jsx в esbuild

Параметр jsx определяет стратегию трансформации JSX-кода.

Доступные значения:

  • transform
  • preserve
  • automatic

jsx: transform

Классический режим React 16 и ниже. JSX преобразуется в вызовы функции создания элементов.

jsx: 'transform'

Пример:

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

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

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

Для этого режима часто требуется jsxFactory.


jsx: automatic

Современный режим React 17+ (новый JSX Transform).

jsx: 'automatic'

JSX преобразуется без необходимости явного импорта React.

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

Результат:

import { jsx as _jsx } from "react/jsx-runtime";
const el = _jsx("div", { children: "Hello" });

Поддерживает оптимизированные runtime-вызовы и tree-shaking.


jsx: preserve

JSX остаётся нетронутым и передаётся дальше по цепочке инструментов.

jsx: 'preserve'

Используется, если дальнейшую трансформацию выполняет Babel или TypeScript compiler.


jsxFactory и jsxFragment

При использовании режима transform часто требуется настройка фабрик JSX.

jsxFactory

Определяет функцию создания элементов:

jsxFactory: 'h'
const el = <div />;

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

const el = h("div", null);

Часто используется в Preact:

jsxFactory: 'h'

jsxFragment

Определяет обработчик Fragment:

jsxFragment: 'Fragment'
const el = <>Text</>;

Результат:

const el = Fragment(null, "Text");

jsxImportSource

Используется в режиме automatic для определения источника JSX runtime.

jsx: 'automatic',
jsxImportSource: 'preact'

Тогда импорт будет изменён:

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

Это позволяет переключаться между React, Preact и другими совместимыми рантаймами без изменения исходного кода.


Взаимодействие с tsconfig.json

esbuild не выполняет полноценное чтение tsconfig.json для компиляции, однако учитывает некоторые параметры через внешние плагины или ручную настройку.

Игнорируются:

  • paths (без плагина);
  • baseUrl (без плагина);
  • большинство strict-режимов.

Частично совместимы:

  • target (через target esbuild);
  • jsx (через jsx в esbuild-конфигурации).

Пример конфигурации esbuild для TypeScript + JSX

import { build } from 'esbuild';

build({
  entryPoints: ['src/index.tsx'],
  bundle: true,
  outfile: 'dist/app.js',
  target: ['es2018'],
  loader: {
    '.ts': 'ts',
    '.tsx': 'tsx'
  },
  jsx: 'automatic',
  jsxImportSource: 'react'
});

Обработка импортов типов

TypeScript-специфичные импорты полностью удаляются:

import type { User } from './types';

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

// удалено полностью

Это снижает размер выходного кода и устраняет необходимость дополнительных проходов анализа.


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

При работе с .tsx важно учитывать:

  • JSX всегда активирует парсер JSX независимо от контекста;
  • TypeScript-типизация не влияет на AST JSX;
  • generics в JSX корректно удаляются до стадии JS;
  • conditional types и mapped types не попадают в выходной код.

Пример:

const el = <Component<T> value={42} />;
const el = jsx(Component, { value: 42 });

Поведение enum и namespace

enum

enum Status {
  Active,
  Inactive
}

Преобразуется в объект:

var Status;
(function (Status2) {
  Status2[Status2["Active"] = 0] = "Active";
  Status2[Status2["Inactive"] = 1] = "Inactive";
})(Status || (Status = {}));

namespace

namespace Utils {
  export function sum(a: number, b: number) {
    return a + b;
  }
}

Преобразуется в IIFE-структуру с вложенными объектами.


Влияние режима isolatedModules

При использовании esbuild важно учитывать концепцию isolatedModules из TypeScript. Поскольку каждый файл обрабатывается независимо, конструкции, требующие глобального анализа типов, могут вести себя иначе, чем в tsc.

Типичные ограничения:

  • невозможность rely на cross-file type inference;
  • потенциальные расхождения с результатами tsc;
  • необходимость избегать сложных type-level вычислений в логике сборки.

Итоговая модель обработки .ts/.tsx

Процесс трансформации можно представить как последовательность шагов:

  1. Парсинг исходного файла;
  2. Удаление TypeScript-типов;
  3. Преобразование ESNext-синтаксиса;
  4. Обработка JSX (при наличии);
  5. Генерация JavaScript без типовой информации.

Такой подход обеспечивает высокую скорость сборки за счёт отказа от семантического анализа типов и фокусировки исключительно на синтаксической трансформации.