Сравнение скорости холодной и горячей сборки

При анализе производительности сборщиков JavaScript важно различать два принципиально разных сценария работы:

  • Холодная сборка (Cold Build) — первый запуск сборщика, когда отсутствуют кэшированные данные и все файлы обрабатываются заново.
  • Горячая сборка (Hot Build) — повторная сборка после изменения одного или нескольких файлов при уже работающем процессе сборки и наличии внутреннего кэша.

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

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


Что происходит во время холодной сборки

При первом запуске Esbuild выполняет полный цикл обработки проекта:

  1. Поиск точки входа.
  2. Построение графа зависимостей.
  3. Чтение файлов с диска.
  4. Парсинг JavaScript, TypeScript, JSX или TSX.
  5. Разрешение импортов.
  6. Трансформация кода.
  7. Минификация (если включена).
  8. Генерация итоговых файлов.

Схематично процесс выглядит следующим образом:

Entry Point
     │
     ▼
Dependency Graph
     │
     ▼
File Reading
     │
     ▼
Parsing
     │
     ▼
Transformation
     │
     ▼
Bundling
     │
     ▼
Output Files

На этом этапе отсутствует информация о предыдущих сборках, поэтому весь проект анализируется полностью.

Пример обычной холодной сборки:

import * as esbuild from 'esbuild';

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

Каждый запуск данного кода создает новую независимую сборку.


Причины высокой скорости холодной сборки в Esbuild

Даже при полном отсутствии кэша Esbuild демонстрирует результаты значительно быстрее большинства альтернативных инструментов.

Основные причины:

Реализация на Go

В отличие от многих сборщиков, написанных на JavaScript, Esbuild реализован на языке Go.

Преимущества:

  • нативное выполнение;
  • эффективная работа с памятью;
  • низкие накладные расходы;
  • быстрая многопоточность.

Параллельная обработка файлов

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

Если проект содержит сотни модулей:

module1.ts
module2.ts
module3.ts
...
module500.ts

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

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

CPU Core 1 → module1.ts
CPU Core 2 → module2.ts
CPU Core 3 → module3.ts
CPU Core 4 → module4.ts

Подобный подход существенно сокращает время полной сборки.


Высокопроизводительный парсер

Парсер Esbuild разрабатывался специально для максимально быстрого чтения JavaScript и TypeScript.

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


Минимальное количество абстракций

Многие сборщики предоставляют сложную архитектуру плагинов, которая увеличивает время обработки.

Esbuild ориентирован на:

  • компактную архитектуру;
  • минимальное число промежуточных операций;
  • быстрое прохождение данных через конвейер обработки.

Измерение времени холодной сборки

Простейший способ измерения — использование встроенного таймера.

console.time('build');

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

console.timeEnd('build');

Возможный результат:

build: 420ms

Для проекта средних размеров это уже считается очень хорошим показателем.


Что происходит во время горячей сборки

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

Например:

const ctx = await esbuild.context({
  entryPoints: ['src/index.ts'],
  bundle: true,
  outfile: 'dist/app.js'
});

await ctx.watch();

После запуска процесса Esbuild:

  1. строит граф зависимостей;
  2. сохраняет результаты анализа;
  3. отслеживает изменения файлов;
  4. выполняет частичную пересборку.

Если изменился один файл:

src/components/Button.tsx

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

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


Внутренний кэш Esbuild

Главный фактор высокой скорости горячих сборок — агрессивное использование внутреннего кэша.

Кэшируются:

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

При изменении одного файла большая часть уже вычисленной информации остается актуальной.

Пример:

src/
 ├─ index.ts
 ├─ api.ts
 ├─ auth.ts
 ├─ Button.tsx
 └─ Header.tsx

Изменяется только:

Button.tsx

Тогда:

index.ts   → cache hit
api.ts     → cache hit
auth.ts    → cache hit
Header.tsx → cache hit
Button.tsx → rebuild

Именно это обеспечивает почти мгновенные пересборки.


Инкрементальная сборка

Основой горячей сборки является инкрементальный подход.

Вместо выполнения полного цикла:

1000 файлов
↓
полная обработка
↓
результат

используется модель:

1000 файлов
↓
изменился 1 файл
↓
обработка 1 файла
↓
обновление результата

Чем крупнее проект, тем заметнее преимущество.


Использование Context API

Для получения максимальной производительности рекомендуется использовать Context API.

Создание контекста:

const ctx = await esbuild.context({
  entryPoints: ['src/index.ts'],
  bundle: true,
  outfile: 'dist/app.js'
});

Пересборка:

await ctx.rebuild();

Завершение работы:

await ctx.dispose();

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


Практическое сравнение

Предположим проект содержит:

1200 модулей
350 000 строк кода
TypeScript + React

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

Сценарий Время
Холодная сборка 900–1500 мс
Горячая сборка 20–80 мс

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

  • мощности процессора;
  • количества файлов;
  • сложности зависимостей;
  • включенной минификации;
  • используемых плагинов.

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


Влияние размера проекта

Рост проекта влияет на холодную и горячую сборку по-разному.

Холодная сборка

При увеличении количества файлов время растет почти линейно:

100 файлов  → 100 мс
500 файлов  → 400 мс
1000 файлов → 800 мс
2000 файлов → 1500 мс

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


Горячая сборка

Если изменяется один файл:

100 файлов  → 15 мс
500 файлов  → 20 мс
1000 файлов → 25 мс
2000 файлов → 30 мс

Рост оказывается значительно менее заметным.

Причина заключается в том, что количество измененных файлов остается небольшим независимо от общего размера приложения.


Влияние Source Maps

Генерация карт исходного кода требует дополнительной обработки.

Пример:

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

Дополнительные операции:

  • вычисление соответствий строк;
  • создание карты преобразований;
  • запись файлов source map.

Это увеличивает время как холодной, так и горячей сборки.

Для производственного режима карты исходников часто отключаются:

sourcemap: false

Влияние минификации

Минификация требует дополнительного анализа и преобразования AST.

await esbuild.build({
  bundle: true,
  minify: true,
  outfile: 'dist/app.js'
});

Во время минификации выполняются:

  • сокращение имен;
  • удаление лишних конструкций;
  • оптимизация выражений;
  • уплотнение кода.

Поэтому холодная сборка становится медленнее.

Типичный пример:

Режим Время
Без минификации 700 мс
С минификацией 950 мс

Горячая сборка также замедляется, хотя влияние обычно менее заметно.


Влияние плагинов

Каждый дополнительный плагин увеличивает нагрузку на процесс сборки.

Пример:

plugins: [
  myPlugin()
]

Если плагин:

  • выполняет файловые операции;
  • обращается к сети;
  • анализирует AST;
  • запускает внешние процессы,

то преимущества Esbuild могут существенно уменьшиться.

Наиболее быстрые сборки достигаются при использовании встроенных возможностей Esbuild без тяжелых расширений.


Сравнение с Webpack

Для крупного проекта показатели часто выглядят следующим образом:

Операция Webpack Esbuild
Холодная сборка 15–40 с 1–3 с
Горячая сборка 300–3000 мс 20–100 мс

Причины различий:

  • нативная реализация Esbuild;
  • более компактная архитектура;
  • агрессивное использование многопоточности;
  • оптимизированный парсер.

Сравнение с Rollup

Rollup известен качественной оптимизацией выходного кода, однако по скорости обычно уступает Esbuild.

Типичная картина:

Операция Rollup Esbuild
Холодная сборка 3–10 с 1–3 с
Горячая сборка 100–500 мс 20–100 мс

Особенно заметно преимущество Esbuild в проектах с большим количеством модулей.


Сравнение с Vite

Важно понимать, что Vite часто использует Esbuild внутри себя.

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

Vite
 ├─ Dev Server
 ├─ HMR
 └─ Esbuild

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

При прямом использовании Esbuild количество промежуточных уровней становится меньше, что дополнительно снижает накладные расходы.


Методика корректного бенчмаркинга

При сравнении холодной и горячей сборки необходимо соблюдать одинаковые условия.

Рекомендуется:

  1. Использовать одинаковый набор файлов.
  2. Отключать сторонние процессы.
  3. Выполнять несколько запусков.
  4. Усреднять результаты.
  5. Измерять отдельно холодную и горячую сборку.

Пример последовательности:

Сборка №1 → 1200 мс
Сборка №2 → 1180 мс
Сборка №3 → 1215 мс

Среднее:

1198 мс

Для горячей сборки:

24 мс
27 мс
23 мс

Среднее:

24.6 мс

Подобный подход позволяет получить объективную картину производительности.


Практические рекомендации для максимальной скорости

Использование режима watch

await ctx.watch();

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

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

Каждый дополнительный плагин увеличивает время обработки.

Разделение крупных модулей

Меньшие по размеру файлы быстрее анализируются и проще пересобираются инкрементально.

Ограничение дорогостоящих преобразований

Если сложная обработка требуется только для production-сборки, имеет смысл отключать её во время разработки.

Использование Context API

const ctx = await esbuild.context(options);

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


Ключевые различия холодной и горячей сборки

Характеристика Холодная сборка Горячая сборка
Наличие кэша Нет Да
Анализ файлов Полный Частичный
Построение графа зависимостей Полностью Повторное использование
Чтение файлов Все файлы Только измененные
Скорость Ниже Значительно выше
Основной сценарий Первый запуск Повседневная разработка
Выигрыш от Context API Умеренный Максимальный
Масштабирование проекта Более заметное влияние Менее заметное влияние

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