Сравнение режимов: bundle vs prebundle

В экосистеме esbuild терминология «bundle» и «prebundle» не является строго формализованными режимами CLI с отдельными флагами, однако отражает два принципиально разных подхода к обработке модулей: полная сборка приложения и предварительная упаковка зависимостей для ускорения последующих этапов работы.

Разделение этих подходов особенно заметно в современных инструментальных цепочках (Vite, Snowpack-подобные архитектуры, кастомные dev-server решения), где esbuild используется как низкоуровневый движок трансформации и компоновки модулей.


Бандлинг (bundle): полная сборка графа модулей

Базовая модель

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

Основная идея:

  • анализируется entry point
  • рекурсивно обходятся import/require зависимости
  • все модули включаются в единый граф
  • применяется трансформация (JSX, TypeScript, ESM → CJS при необходимости)
  • выполняется минификация и tree-shaking (если включено)
  • формируется финальный бандл

Поведение esbuild в режиме bundle

При использовании esbuild.build с параметром bundle: true происходит:

  • объединение локальных модулей проекта
  • инлайнинг зависимостей (если не marked as external)
  • устранение неиспользуемого кода (dead code elimination)
  • оптимизация импортов

Пример конфигурации:

import * as esbuild from 'esbuild';

esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  outfile: 'dist/app.js',
  platform: 'browser',
  format: 'esm',
});

Особенности поведения

1. Полная инклюзия зависимостей

Все зависимости, не отмеченные как external, включаются в итоговый файл.

2. Единый граф оптимизации

Tree-shaking и minification работают на уровне всего графа, а не отдельных пакетов.

3. Более высокая стоимость сборки

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

  • обхода графа
  • трансформации модулей
  • генерации итогового кода

4. Упрощённый runtime

На выходе получается минимальное количество файлов, часто один бандл.


Prebundle (предварительная упаковка зависимостей)

Концептуальная модель

Prebundle — это этап, при котором зависимости подготавливаются отдельно от основного кода приложения. Цель — ускорить последующую сборку и/или запуск dev-сервера за счёт кэширования и упрощения обработки сторонних модулей.

В контексте esbuild это чаще всего:

  • предварительная компиляция node_modules
  • преобразование CommonJS/ESM пакетов в оптимизированный формат
  • кэширование результатов трансформации

Типичный сценарий

Prebundle используется в следующих случаях:

  • dev server (например, Vite)
  • ускорение холодного старта
  • уменьшение количества ESM запросов в браузере
  • стабилизация несовместимых пакетов

Отличие prebundle от bundle

1. Область обработки

Bundle:

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

Prebundle:

  • только зависимости (обычно node_modules)
  • приложение остаётся отдельно

2. Время выполнения

Bundle:

  • дороже при каждом изменении входа
  • пересборка может затрагивать весь граф

Prebundle:

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

3. Частота выполнения

Bundle:

  • выполняется на каждый билд (или инкрементально)

Prebundle:

  • выполняется редко:

    • при установке зависимостей
    • при изменении lockfile
    • при обновлении пакетов

4. Роль в архитектуре

Bundle:

  • финальный шаг production build

Prebundle:

  • промежуточный шаг оптимизации dev-окружения

Технические различия обработки модулей

Работа с ESM и CJS

Bundle

  • преобразует смешанный граф модулей
  • может инлайнить CommonJS через трансформацию
  • решает interop на уровне всего приложения

Prebundle

  • нормализует зависимости до единого формата
  • часто приводит CJS к ESM-совместимому виду
  • фиксирует экспортную структуру для последующего использования

Кэширование

Bundle

  • кэширование ограничено внутренними механизмами incremental build
  • при изменении entry point часто пересобирается значительная часть графа

Prebundle

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

Обработка внешних пакетов

Bundle

  • зависимости включаются внутрь бандла
  • возможна настройка external

Prebundle

  • все пакеты рассматриваются как отдельные единицы
  • часто формируются оптимизированные ESM-блоки

Сценарии применения bundle

Production-сборка

Основное назначение bundle-режима — создание финального артефакта:

  • минимизация количества HTTP-запросов
  • агрегация кода
  • оптимизация загрузки

Пример:

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

Библиотеки

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

  • генерации UMD/CJS/ESM пакетов
  • создания распределяемых артефактов

Особенность:

  • часто исключаются peerDependencies через external

Сценарии применения prebundle

Dev-сервер

Prebundle особенно важен в архитектуре dev-серверов:

  • ускоряет cold start
  • снижает нагрузку на resolver
  • уменьшает число преобразований в runtime

Оптимизация node_modules

Типичный кейс:

  • десятки/сотни зависимостей
  • множество CJS-пакетов
  • необходимость привести их к ESM

Prebundle превращает их в:

  • оптимизированные ESM-модули
  • кэшированные артефакты

Устранение проблем совместимости

Некоторые пакеты:

  • используют dynamic require
  • имеют нестандартные exports
  • содержат смешанные форматы

Prebundle решает это через:

  • предварительную трансформацию
  • стабилизацию экспортов

Производительность: сравнительный анализ

Время холодного старта

  • Bundle: зависит от размера всего графа приложения
  • Prebundle: зависит только от количества зависимостей

Prebundle обычно значительно быстрее при повторных запусках dev-сервера.


Инкрементальные изменения

Bundle:

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

Prebundle:

  • изменения приложения не затрагивают уже обработанные зависимости

Узкие места

Bundle:

  • глубокие dependency chains
  • большие monorepo
  • тяжелые трансформации (TS + JSX)

Prebundle:

  • первичная обработка node_modules
  • большие библиотеки с множеством subpath exports

Влияние на архитектуру проекта

Разделение ответственности

Bundle и prebundle формируют двухуровневую модель:

  1. уровень зависимостей (prebundle)
  2. уровень приложения (bundle)

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

  • стабилизировать внешние библиотеки
  • ускорить итерации разработки
  • снизить стоимость повторных сборок

Изоляция зависимостей

Prebundle фактически фиксирует:

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

Bundle работает поверх уже стабилизированного слоя.


Особенности интеграции в современные toolchain

Vite-подобная модель

В современных сборщиках:

  • prebundle выполняется через esbuild optimizeDeps
  • bundle применяется только для production

Поток выглядит так:

  1. анализ зависимостей
  2. prebundle node_modules
  3. dev server использует кэшированные модули
  4. production build запускает полный bundle

Monorepo

В монорепозиториях:

  • prebundle может быть ограничен workspace зависимостями
  • bundle объединяет локальные пакеты

Ограничения подходов

Bundle

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

Prebundle

  • дополнительный этап в pipeline
  • возможная рассинхронизация кэша
  • необходимость контроля invalidation при обновлениях зависимостей

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

  • Bundle: динамическая сборка всего приложения

  • Prebundle: статическая подготовка зависимостей

  • Bundle: оптимизация конечного артефакта

  • Prebundle: оптимизация промежуточного слоя

  • Bundle: ориентирован на результат

  • Prebundle: ориентирован на инфраструктуру разработки