Callbacks при пересборке

Пересборка в esbuild реализуется через режим наблюдения за файловой системой и инкрементальную компиляцию, при которой повторные запуски сборки используют уже построенные графы модулей и кэшированные результаты трансформаций. Основной механизм, отвечающий за реакцию на изменения исходного кода, связан с watch-режимом и функцией обратного вызова onRebuild.

В отличие от классических сборщиков, где пересборка часто представляет собой повторный полный прогон пайплайна, esbuild старается минимизировать объём повторной работы. Это достигается за счёт сохранения состояния зависимостей модулей и повторного использования уже разобранных AST-подобных структур внутри собственного представления.


Watch-режим и callback onRebuild

Режим наблюдения включается через параметр watch при запуске build. Внутри этого режима можно определить функцию onRebuild, которая вызывается каждый раз при обнаружении изменений в файловой системе и завершении повторной сборки.

import * as esbuild from 'esbuild';

esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  outfile: 'dist/bundle.js',
  watch: {
    onRebuild(error, result) {
      if (error) {
        console.error('Ошибка пересборки:', error);
        return;
      }
      console.log('Пересборка завершена');
    }
  }
});

Callback onRebuild является ключевым механизмом интеграции esbuild в среды разработки. Он позволяет подключать любые внешние процессы: обновление браузера, инвалидацию кэша, перезапуск серверов или логирование состояния сборки.


Сигнатура onRebuild и обработка результата

Функция onRebuild принимает два аргумента:

  • error — объект ошибки, возникающий при неуспешной пересборке
  • result — результат успешной сборки, аналогичный результату первичного build

Логика обработки обычно строится вокруг проверки наличия ошибки, поскольку esbuild не выбрасывает исключения в привычном синхронном смысле внутри callback.

onRebuild(error, result) {
  if (error) {
    // Ошибка компиляции или проблема файловой системы
    return;
  }

  // Доступ к метаданным сборки
  const { warnings, outputFiles } = result;
}

Объект result содержит метаданные текущей сборки: список предупреждений, карту модулей, информацию о маппинге исходников, а также результаты генерации выходных файлов при использовании in-memory режима.


Поведение при ошибках пересборки

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

При синтаксических ошибках error передаётся в onRebuild, при этом esbuild продолжает наблюдение за файлами. Это важно: система не прекращает работу при ошибках компиляции, позволяя разработчику исправить код без перезапуска процесса.

Системные ошибки (например, потеря доступа к файлам или сбой watcher’а) могут привести к остановке режима наблюдения, в зависимости от окружения и платформы.


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

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

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

При последующих изменениях esbuild не пересчитывает весь граф заново. Вместо этого он:

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

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


Поведение result при пересборке

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

Типичные поля результата:

  • warnings — предупреждения компиляции
  • errors — ошибки (если не переданы через callback)
  • metafile — метаданные графа зависимостей
  • outputFiles — массив сгенерированных файлов (при соответствующей настройке)

При использовании write: false результат может содержать данные в памяти, что позволяет интегрировать esbuild в кастомные пайплайны без записи на диск.


Интеграция с dev-серверами

Callbacks пересборки часто используются как слой интеграции с HTTP-серверами разработки. Типичный сценарий включает:

  • запуск esbuild в watch-режиме
  • запуск dev-server (Express, Fastify или кастомный сервер)
  • инвалидацию или горячую замену модулей при onRebuild

Простейшая схема взаимодействия:

let server;

esbuild.build({
  entryPoints: ['src/app.js'],
  bundle: true,
  outfile: 'dist/app.js',
  watch: {
    onRebuild(error) {
      if (error) return;

      if (server) {
        server.invalidate();
      }
    }
  }
});

В более сложных системах callback используется для реализации HMR (Hot Module Replacement), где пересобранные модули заменяются без полной перезагрузки страницы.


Ограничения callback-модели

Несмотря на гибкость, callback onRebuild имеет ряд архитектурных ограничений:

  • отсутствует детализация изменений на уровне отдельных модулей в callback (только факт пересборки)
  • нет встроенного debounce уровня API (его приходится реализовывать вручную)
  • callback не предоставляет дифф между предыдущим и текущим результатом
  • параллельные пересборки не допускаются в рамках одного watcher-инстанса

Это делает API предсказуемым, но требует дополнительной логики при построении сложных dev-инструментов.


Инкрементальные контексты и управление жизненным циклом

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

В такой модели пересборка становится частью долгоживущего процесса:

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

Callback onRebuild сохраняет роль точки синхронизации между внутренним механизмом пересборки и внешним приложением, но управление состоянием выносится в контекст.


Последовательность событий при изменении файла

При изменении исходного файла внутри watch-режима происходит последовательность шагов:

  1. файловая система фиксирует изменение
  2. watcher передаёт событие в esbuild
  3. определяется набор затронутых модулей
  4. помечаются устаревшие узлы графа зависимостей
  5. выполняется пересборка только изменённых частей
  6. формируется новый результат сборки
  7. вызывается onRebuild

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


Поведение при множественных изменениях

При серии быстрых изменений файлов esbuild агрегирует события файловой системы. Вместо запуска отдельной пересборки на каждое изменение, система объединяет изменения в один цикл пересборки.

Это снижает нагрузку и предотвращает каскадные перестройки графа модулей. Callback onRebuild при этом вызывается только один раз после завершения агрегированной сборки.


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

Результат, передаваемый в onRebuild, часто используется как источник данных для внешних систем:

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

Интеграция строится вокруг идеи, что esbuild предоставляет только вычислительный слой, а вся оркестрация процессов остаётся на стороне приложения.


Типичные архитектурные паттерны

В реальных проектах callback пересборки используется в нескольких устойчивых паттернах:

Логирование и метрики Сбор времени пересборки и количества затронутых модулей через анализ result.

HMR-посредник Проксирование изменений в браузер через WebSocket канал.

Fail-safe режим Игнорирование ошибок пересборки при сохранении предыдущего состояния приложения в памяти.

Кэш-инвалидация Сброс локальных кэшей серверной логики при успешной пересборке.


Особенности поведения в больших проектах

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

При этом onRebuild остаётся стабильной точкой входа для внешней логики, независимо от сложности внутреннего графа.