При анализе производительности сборщиков JavaScript важно различать два принципиально разных сценария работы:
Для современных проектов скорость именно горячей сборки часто оказывает большее влияние на продуктивность разработки, поскольку во время написания кода пересборка выполняется десятки или сотни раз в день.
Esbuild был создан с особым акцентом на минимизацию времени как холодных, так и горячих сборок.
При первом запуске Esbuild выполняет полный цикл обработки проекта:
Схематично процесс выглядит следующим образом:
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 демонстрирует результаты значительно быстрее большинства альтернативных инструментов.
Основные причины:
В отличие от многих сборщиков, написанных на 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:
Если изменился один файл:
src/components/Button.tsx
не требуется повторно анализировать весь проект.
Esbuild повторно обработает только затронутые модули и связанные зависимости.
Главный фактор высокой скорости горячих сборок — агрессивное использование внутреннего кэша.
Кэшируются:
При изменении одного файла большая часть уже вычисленной информации остается актуальной.
Пример:
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.
Создание контекста:
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 мс
Рост оказывается значительно менее заметным.
Причина заключается в том, что количество измененных файлов остается небольшим независимо от общего размера приложения.
Генерация карт исходного кода требует дополнительной обработки.
Пример:
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
sourcemap: true,
outfile: 'dist/app.js'
});
Дополнительные операции:
Это увеличивает время как холодной, так и горячей сборки.
Для производственного режима карты исходников часто отключаются:
sourcemap: false
Минификация требует дополнительного анализа и преобразования AST.
await esbuild.build({
bundle: true,
minify: true,
outfile: 'dist/app.js'
});
Во время минификации выполняются:
Поэтому холодная сборка становится медленнее.
Типичный пример:
| Режим | Время |
|---|---|
| Без минификации | 700 мс |
| С минификацией | 950 мс |
Горячая сборка также замедляется, хотя влияние обычно менее заметно.
Каждый дополнительный плагин увеличивает нагрузку на процесс сборки.
Пример:
plugins: [
myPlugin()
]
Если плагин:
то преимущества Esbuild могут существенно уменьшиться.
Наиболее быстрые сборки достигаются при использовании встроенных возможностей Esbuild без тяжелых расширений.
Для крупного проекта показатели часто выглядят следующим образом:
| Операция | Webpack | Esbuild |
|---|---|---|
| Холодная сборка | 15–40 с | 1–3 с |
| Горячая сборка | 300–3000 мс | 20–100 мс |
Причины различий:
Rollup известен качественной оптимизацией выходного кода, однако по скорости обычно уступает Esbuild.
Типичная картина:
| Операция | Rollup | Esbuild |
|---|---|---|
| Холодная сборка | 3–10 с | 1–3 с |
| Горячая сборка | 100–500 мс | 20–100 мс |
Особенно заметно преимущество Esbuild в проектах с большим количеством модулей.
Важно понимать, что Vite часто использует Esbuild внутри себя.
Типичная цепочка выглядит следующим образом:
Vite
├─ Dev Server
├─ HMR
└─ Esbuild
Поэтому высокая скорость запуска и трансформации модулей в Vite во многом обеспечивается именно Esbuild.
При прямом использовании Esbuild количество промежуточных уровней становится меньше, что дополнительно снижает накладные расходы.
При сравнении холодной и горячей сборки необходимо соблюдать одинаковые условия.
Рекомендуется:
Пример последовательности:
Сборка №1 → 1200 мс
Сборка №2 → 1180 мс
Сборка №3 → 1215 мс
Среднее:
1198 мс
Для горячей сборки:
24 мс
27 мс
23 мс
Среднее:
24.6 мс
Подобный подход позволяет получить объективную картину производительности.
await ctx.watch();
Позволяет сохранять состояние между пересборками.
Каждый дополнительный плагин увеличивает время обработки.
Меньшие по размеру файлы быстрее анализируются и проще пересобираются инкрементально.
Если сложная обработка требуется только для production-сборки, имеет смысл отключать её во время разработки.
const ctx = await esbuild.context(options);
Контекст обеспечивает наиболее быстрые горячие пересборки благодаря повторному использованию уже построенного графа зависимостей и внутренних структур данных.
| Характеристика | Холодная сборка | Горячая сборка |
|---|---|---|
| Наличие кэша | Нет | Да |
| Анализ файлов | Полный | Частичный |
| Построение графа зависимостей | Полностью | Повторное использование |
| Чтение файлов | Все файлы | Только измененные |
| Скорость | Ниже | Значительно выше |
| Основной сценарий | Первый запуск | Повседневная разработка |
| Выигрыш от Context API | Умеренный | Максимальный |
| Масштабирование проекта | Более заметное влияние | Менее заметное влияние |
Главное преимущество Esbuild заключается в том, что высокая скорость наблюдается не только при первой сборке, но и во время постоянных пересборок проекта. Благодаря сочетанию нативной реализации на Go, многопоточной обработки, инкрементального подхода и эффективного кэширования горячие сборки часто выполняются за десятки миллисекунд даже в крупных приложениях.