Transform: транспиляция без сборки

esbuild предоставляет отдельный механизм преобразования кода без этапа бандлинга, предназначенный для быстрой транспиляции файлов. Этот режим работает значительно быстрее классических транспайлеров за счёт реализации на Go и минимального количества абстракций вокруг AST.

Функция преобразования в esbuild выполняет работу над единичным фрагментом кода, не затрагивая систему модулей, граф зависимостей или процесс сборки проекта.

Ключевая особенность заключается в том, что transform:

  • работает только с входной строкой кода;
  • не строит dependency graph;
  • не выполняет разрешение импортов;
  • не выполняет бандлинг;
  • возвращает преобразованный код и, при необходимости, source map.

Это делает его эквивалентом высокопроизводительного транспилятора, а не сборщика.

Базовый API transform

Основной интерфейс выглядит следующим образом:

import { transform } from "esbuild";

const result = await transform("const x: number = 42;", {
  loader: "ts"
});

console.log(result.code);

Результат выполнения содержит:

  • code — преобразованный JavaScript;
  • map — source map (если включён);
  • warnings — предупреждения компиляции.

Синхронного варианта API в esbuild нет: transform всегда асинхронен, что упрощает внутреннюю архитектуру и позволяет эффективно использовать потоковую обработку.

Поддерживаемые языки и загрузчики

Transform поддерживает различные типы входных данных через механизм loader:

  • js — JavaScript
  • ts — TypeScript
  • jsx — JSX без TypeScript
  • tsx — TypeScript с JSX
  • json — JSON как модуль
  • css — CSS преобразования

Пример обработки TypeScript:

await transform(`
  interface User {
    name: string;
  }

  const user: User = { name: "Alex" };
`, {
  loader: "ts"
});

TypeScript удаляется без выполнения типизации — esbuild не выполняет полноценную проверку типов, фокусируясь только на синтаксическом преобразовании.

JSX-трансформация

esbuild включает встроенную поддержку JSX без необходимости Babel.

await transform(`<h1>Hello</h1>`, {
  loader: "jsx"
});

Можно настроить runtime JSX:

await transform(`<App />`, {
  loader: "jsx",
  jsxFactory: "h",
  jsxFragment: "Fragment"
});

Также поддерживается автоматический runtime:

await transform(`<App />`, {
  loader: "jsx",
  jsx: "automatic"
});

В этом режиме esbuild самостоятельно вставляет импорты JSX runtime.

Sourcemaps

Source maps позволяют сопоставлять скомпилированный код с исходным:

await transform("const x: number = 1;", {
  loader: "ts",
  sourcemap: "inline"
});

Доступные режимы:

  • inline — встроенная карта в код;
  • external — отдельный файл map;
  • both — комбинация.

Sourcemaps особенно полезны при использовании transform как этапа препроцессинга перед выполнением кода в браузере или Node.js.

Минификация в transform

Transform может использоваться как быстрый минификатор отдельного файла:

await transform(`function add(a, b) { return a + b; }`, {
  minify: true
});

Минификация включает:

  • удаление пробелов;
  • сокращение имён локальных переменных;
  • упрощение выражений;
  • устранение мёртвого кода (частично).

Однако полноценная агрессивная оптимизация, характерная для bundle-режима, в transform ограничена отсутствием контекста проекта.

Подстановка значений (define)

Механизм define позволяет выполнять compile-time замену идентификаторов:

await transform(`
  if (process.env.NODE_ENV === "development") {
    console.log("dev mode");
  }
`, {
  define: {
    "process.env.NODE_ENV": "\"production\""
  }
});

После трансформации условие полностью упрощается на этапе компиляции.

Target и совместимость

Опция target управляет уровнем синтаксиса выходного Jav * aScript:

await transform(`const fn = () => {}`, {
  target: "es5"
});

Поддерживаются значения:

  • es2015
  • es2016esnext
  • node12, node16, node18 и др.

esbuild автоматически понижает синтаксис, включая:

  • стрелочные функции;
  • optional chaining;
  • nullish coalescing;
  • классы.

Работа с платформами

Хотя transform не выполняет бандлинг, он учитывает платформенные особенности:

await transform("import fs from 'fs';", {
  platform: "node"
});

В режиме browser некоторые Node-specific конструкции могут быть преобразованы или оставлены без изменений в зависимости от контекста loader.

Ограничения transform-режима

Transform принципиально отличается от build-режима и имеет ряд ограничений:

  • отсутствует система модулей;
  • нет разрешения import и require;
  • отсутствуют плагины esbuild;
  • нет tree-shaking между файлами;
  • невозможна работа с несколькими входными точками.

Импорты остаются текстовыми и не обрабатываются как зависимости:

await transform(`import x from "./mod.js";`, {
  loader: "js"
});

В этом случае строка импорта не резолвится, а только синтаксически валидируется.

Отличие от build API

Transform можно рассматривать как изолированный слой внутри экосистемы esbuild:

  • build API → граф модулей, бандлинг, плагины;
  • transform API → одиночный файл, быстрый синтаксический препроцессинг.

Это разделение делает transform полезным в сценариях:

  • онлайн-компиляторы;
  • playground-среды;
  • SSR-препроцессинг;
  • встроенные редакторы кода;
  • микротрансформации кода перед выполнением.

Производительность

Transform достигает высокой скорости за счёт:

  • отсутствия анализа зависимостей;
  • минимального AST-прохода;
  • реализации на Go без интерпретируемых слоёв;
  • отсутствия плагинной системы.

В типичных сценариях преобразование TypeScript или JSX выполняется за миллисекунды даже для значительных фрагментов кода.

Практические сценарии использования

Часто transform применяется как промежуточный слой:

  • преобразование TypeScript в рантайме разработки;
  • обработка JSX в браузерных playground;
  • быстрый минификатор отдельных функций;
  • серверный препроцессинг шаблонов кода;
  • интеграция в системы live-reload.

В таких случаях esbuild заменяет более тяжёлые цепочки инструментов, сохраняя только необходимую часть компиляции.