Разница между bundle и transform на практике

Внутри Esbuild существует два фундаментальных режима обработки исходного кода: transform и bundle. Оба режима используют один и тот же парсер, систему плагинов и быстрый движок компиляции на Go, но решают принципиально разные задачи.

Ключевое различие заключается в уровне работы:

  • transform работает с отдельным модулем (файлом)
  • bundle работает с графом зависимостей проекта

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


Transform: работа с единичным модулем

Режим transform предназначен для преобразования одного файла без анализа его зависимостей. Он не строит граф импортов и не знает ничего о других модулях проекта.

Основные задачи transform:

  • транспиляция TypeScript → JavaScript
  • преобразование JSX / TSX
  • минимизация одного файла
  • применение синтаксических плагинов (например, макросов)
  • генерация sourcemap для одного модуля

Пример использования transform через API

import { transform } from "esbuild";

const result = await transform(`
  const App = () => <div>Hello</div>;
`, {
  loader: "jsx",
  jsx: "automatic",
});

console.log(result.code);

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

Transform работает строго локально:

  • отсутствует разрешение импортов
  • отсутствует анализ зависимостей
  • отсутствует объединение файлов
  • каждый файл обрабатывается независимо

Это делает transform чрезвычайно быстрым и предсказуемым инструментом для изолированных преобразований.


Bundle: работа с графом модулей

Bundle — это режим, в котором Esbuild строит полный граф зависимостей приложения, начиная с entry-point и рекурсивно подключая все импортируемые модули.

Основные задачи bundle:

  • объединение модулей в один или несколько файлов
  • разрешение импортов (node_modules, относительные пути)
  • tree-shaking (удаление неиспользуемого кода)
  • код-сплиттинг
  • управление внешними зависимостями
  • генерация production-ready бандла

Пример bundle через API

import { build } from "esbuild";

await build({
  entryPoints: ["src/index.js"],
  bundle: true,
  outfile: "dist/app.js",
});

Ключевая архитектурная разница

1. Область видимости

Transform:

  • один файл
  • отсутствует контекст проекта

Bundle:

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

2. Работа с импортами

Transform:

// import игнорируется как зависимость системы сборки
import { sum } from "./math.js";

Esbuild в transform-режиме не пытается загрузить math.js. Он просто преобразует код внутри файла.

Bundle:

import { sum } from "./math.js";

Esbuild:

  • находит math.js
  • анализирует его зависимости
  • добавляет в граф
  • может исключить при tree-shaking

3. Tree-shaking

Transform:

  • невозможен
  • нет информации о том, что используется в других модулях

Bundle:

  • доступен на уровне графа
  • удаляет неиспользуемые экспорты

Пример:

// math.js
export const a = 1;
export const b = 2;

// index.js
import { a } from "./math.js";
console.log(a);

В bundle-режиме b будет удалён из финального результата.


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

Transform:

  • O(1) относительно числа файлов проекта
  • масштабируется линейно по количеству файлов при параллельной обработке

Bundle:

  • зависит от размера графа зависимостей
  • требует построения дерева модулей
  • включает анализ импортов и резолвинг

Несмотря на это, Esbuild остаётся одним из самых быстрых bundler’ов благодаря реализации на Go и агрессивному кешированию.


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

Transform применяется в случаях:

  • встроенные редакторы кода (on-the-fly трансформация)
  • обработка отдельных файлов перед загрузкой
  • плагины, модифицирующие AST одного файла
  • интеграция в пайплайны без сборки проекта

Пример CLI-подхода:

esbuild input.ts --loader=ts --outfile=out.js --transform

Bundle применяется в случаях:

  • сборка SPA/SSR приложений
  • подготовка production-бандла
  • оптимизация зависимостей
  • код-сплиттинг для динамической загрузки

Влияние external и platform на bundle

Bundle-режим получает дополнительные параметры, которые transform не использует в таком виде.

external зависимости

build({
  entryPoints: ["src/index.js"],
  bundle: true,
  external: ["react", "react-dom"],
});

Эти модули:

  • не включаются в бандл
  • остаются как import в итоговом коде

platform

build({
  platform: "node",
  bundle: true,
});

Влияет на:

  • формат резолвинга модулей
  • поведение встроенных Node.js API
  • формат вывода (CommonJS vs ESM)

Transform не использует эти правила, так как не выполняет резолвинг.


Минимизация: различие в контексте

Transform:

transform(code, {
  minify: true,
});

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

Bundle:

build({
  entryPoints: ["src/index.js"],
  bundle: true,
  minify: true,
});

Здесь минификация происходит после:

  • объединения модулей
  • tree-shaking
  • устранения дубликатов кода

Итоговый результат обычно значительно компактнее.


Sourcemap: различие уровней

Transform:

  • sourcemap привязан к одному файлу
  • нет цепочки исходников

Bundle:

  • sourcemap отражает весь граф модулей
  • поддерживает mapping до исходных файлов проекта
  • может включать inline и external режимы

Код-сплиттинг как исключительная функция bundle

Transform не поддерживает разделение кода, так как не имеет графа зависимостей.

Bundle поддерживает:

build({
  entryPoints: ["src/app.js"],
  bundle: true,
  splitting: true,
  format: "esm",
  outdir: "dist",
});

Результат:

  • несколько файлов chunks
  • динамические import() становятся отдельными чанками
  • оптимизация загрузки

Плагинная система: различие поведения

Transform-плагины:

  • работают на уровне одного файла
  • могут изменять содержимое AST
  • не могут влиять на резолвинг зависимостей проекта

Bundle-плагины:

  • участвуют в процессе резолвинга
  • могут подменять модули
  • влияют на граф зависимостей
  • могут предотвращать или перенаправлять импорт

Границы применимости transform внутри bundle

В bundle-режиме transform используется как внутренний этап обработки каждого модуля.

Процесс выглядит так:

  1. построение графа зависимостей
  2. загрузка модуля
  3. transform для каждого файла
  4. объединение результата
  5. оптимизация

Таким образом transform — это базовый строительный блок bundle, но не наоборот.


Сравнение поведения на уровне результата

Transform:

  • 1 вход → 1 выход
  • структура проекта не изменяется
  • отсутствует оптимизация между файлами

Bundle:

  • N входов → 1 или несколько выходов
  • структура проекта трансформируется
  • применяется межмодульная оптимизация

Типичные ошибки при выборе режима

Ошибка: использование transform вместо bundle

  • попытка обработать проект с импортами через transform
  • ожидание, что зависимости будут автоматически подтянуты

Результат: отсутствующие модули в runtime


Ошибка: использование bundle для единичных файлов

  • лишняя нагрузка на граф зависимостей
  • медленнее, чем transform
  • избыточная работа резолвера

Итоговое функциональное разделение

Transform:

  • локальная трансформация
  • синтаксическая обработка
  • минимальная стоимость вычислений

Bundle:

  • системная сборка приложения
  • анализ зависимостей
  • оптимизация структуры проекта
  • генерация production-артефактов