Bundle: объединение модулей в один файл

Bundling в Esbuild основан на построении графа зависимостей и последующей трансформации этого графа в оптимизированный набор выходных файлов. Каждый импорт в коде рассматривается как ребро графа, а каждый модуль — как узел. Входная точка запускает рекурсивный анализ импортов, после чего формируется полная структура зависимостей.

Ключевая особенность подхода Esbuild заключается в том, что вся работа по построению и оптимизации графа выполняется на уровне одного прохода компиляции, без разделения на множество фаз, характерных для более медленных инструментов.

Основные этапы процесса:

  • разбор входных файлов (entry points)
  • построение графа зависимостей
  • трансформация модулей
  • объединение в выходные чанки
  • генерация финального бандла

Входные точки и построение графа

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

esbuild src/index.js --bundle --outfile=dist/bundle.js

При анализе входного файла Esbuild:

  1. считывает исходный код
  2. находит все import и require
  3. разрешает пути модулей
  4. добавляет найденные модули в граф

Пример структуры:

index.js
 ├── utils.js
 │    └── format.js
 └── api.js

Каждый файл становится узлом, а зависимости формируют связи.

Объединение модулей в единый файл

При включённой опции --bundle Esbuild выполняет инлайнинг модулей. Это означает, что содержимое зависимостей встраивается в итоговый файл.

esbuild src/index.js --bundle --outfile=dist/app.js

Результат представляет собой единый JavaScript-файл, где:

  • модули превращаются в функции или области видимости
  • экспорт/импорт преобразуются в ссылки между внутренними структурами
  • устраняются лишние обёртки модульной системы

Механизм изоляции модулей

Каждый модуль в процессе bundling помещается в изолированную область видимости. Esbuild использует функции-обёртки для предотвращения конфликтов переменных.

Упрощённая модель:

(() => {
  // module A
  const value = 42;
})();
(() => {
  // module B
  const value = 100;
})();

Такая модель позволяет сохранять независимость модулей при объединении.

Обработка import и export

Esbuild трансформирует ESM-синтаксис в внутреннюю систему ссылок.

Исходный код:

// a.js
export const a = 1;

// b.js
import { a } from './a.js';
export const b = a + 1;

После bundling логика преобразуется в единое пространство имён, где зависимости становятся прямыми ссылками:

  • экспорт превращается в присваивание внутреннему объекту экспорта
  • импорт заменяется на чтение из этого объекта

Это позволяет избежать выполнения дополнительных runtime-обёрток.

Tree shaking при объединении

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

Удаляются:

  • неиспользуемые экспорты
  • мёртвые ветки кода
  • модули, не достижимые из entry point

Пример:

export const used = 1;
export const unused = 2;

Если unused нигде не импортируется, он исключается из итогового бандла.

Форматы выходных файлов

Bundling в Esbuild поддерживает несколько форматов:

  • esm — ES Modules
  • cjs — CommonJS
  • iife — самовызывающаяся функция
  • umd — универсальный формат

Пример генерации IIFE-бандла:

esbuild src/index.js --bundle --format=iife --outfile=dist/app.js

IIFE-формат часто используется для браузерных приложений без модульной системы.

Code splitting при объединении

Хотя базовый bundling предполагает один файл, Esbuild поддерживает разбиение на чанки при наличии динамических импортов.

import('./module.js').then(m => {
  m.run();
});

В этом случае:

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

Внешние зависимости

Опция external исключает модули из bundling.

esbuild src/index.js --bundle --external:react

Такой модуль:

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

Это важно при работе с CDN или системными зависимостями.

Минификация при сборке

Bundling часто комбинируется с минификацией:

esbuild src/index.js --bundle --minify --outfile=dist/app.js

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

  • удаление пробелов
  • сокращение идентификаторов
  • упрощение выражений
  • инлайнинг констант

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

const result = 1 + 2;

становится:

const r=3;

Sourcemaps в процессе bundling

Esbuild генерирует sourcemaps, связывающие итоговый бандл с исходными модулями.

esbuild src/index.js --bundle --sourcemap --outfile=dist/app.js

Это позволяет:

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

Метаданные сборки

Esbuild может генерировать метафайл, описывающий процесс bundling:

esbuild src/index.js --bundle --metafile=meta.json --outfile=dist/app.js

В метафайле содержится:

  • список модулей
  • зависимости
  • размеры файлов
  • структура графа

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

Поведение runtime после объединения

После bundling код выполняется как единый скрипт. При этом:

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

Пример циклической структуры:

// a.js
import { b } from './b.js';
export const a = 1;

// b.js
import { a } from './a.js';
export const b = 2;

Esbuild обеспечивает корректную обработку через отложенное связывание экспортов.

Оптимизация структуры бандла

В процессе объединения применяются дополнительные оптимизации:

  • hoisting констант
  • инлайнинг функций
  • объединение соседних модулей
  • устранение промежуточных обёрток

Эти операции уменьшают итоговый размер и повышают скорость выполнения.

Режим watch и инкрементальная сборка

При использовании режима наблюдения Esbuild не пересобирает граф полностью. Вместо этого:

  • отслеживаются изменённые файлы
  • пересчитываются только затронутые ветви графа
  • повторно генерируются affected chunks
esbuild src/index.js --bundle --watch

Это существенно ускоряет процесс разработки при больших проектах.