Хук onStart и onEnd

Плагин в Esbuild представляет собой объект с функцией setup, внутри которой регистрируются обработчики событий сборки. Эти обработчики привязываются к различным этапам жизненного цикла компиляции через build.onStart и build.onEnd.

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

export const myPlugin = () => ({
  name: 'my-plugin',
  setup(build) {
    build.onStart(() => {
      // действия перед началом сборки
    });

    build.onEnd((result) => {
      // действия после завершения сборки
    });
  }
});

Оба хука относятся к глобальному процессу сборки и не привязаны к отдельным файлам напрямую. Их задача — реагировать на начало и завершение процесса компиляции, включая повторные сборки в watch-режиме и при использовании incremental API.


Хук onStart: момент инициализации сборочного цикла

Хук onStart вызывается перед каждой новой компиляцией. Это включает:

  • первую сборку;
  • пересборку при изменении файлов в watch-режиме;
  • повторные инкрементальные сборки.

Сигнатура и поведение

build.onStart(async () => {
  // подготовка данных
});

Ключевые особенности:

  • поддерживает синхронную и асинхронную логику (Promise);
  • выполняется до анализа графа зависимостей;
  • может использоваться для подготовки внешних ресурсов;
  • не имеет доступа к результатам текущей сборки.

Типичные задачи onStart

Очистка или подготовка кэшированных данных

build.onStart(() => {
  cache.clear();
});

Предварительная загрузка данных

build.onStart(async () => {
  const config = await loadRemoteConfig();
  globalThis.__BUILD_CONFIG__ = config;
});

Логирование начала сборки

build.onStart(() => {
  console.log('Сборка началась');
});

Поведение в watch-режиме

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

Важно учитывать, что:

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

Хук onEnd: завершение компиляционного цикла

onEnd вызывается после завершения сборки, независимо от того, успешна она или содержит ошибки.

Сигнатура

build.onEnd((result) => {
  // обработка результата
});

Аргумент result содержит:

  • errors — массив ошибок компиляции;
  • warnings — предупреждения;
  • metafile — метаданные сборки (если включены);
  • outputFiles — при использовании write: false.

Пример анализа результата

build.onEnd((result) => {
  if (result.errors.length > 0) {
    console.error('Сборка завершилась с ошибками');
    return;
  }

  console.log('Сборка успешна');
});

Работа с метаинформацией

build.onEnd((result) => {
  if (result.metafile) {
    const meta = result.metafile;

    for (const file in meta.outputs) {
      console.log('Сгенерирован файл:', file);
    }
  }
});

Метаданные позволяют анализировать:

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

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

При конфигурации write: false результат сборки доступен в памяти:

build.onEnd((result) => {
  result.outputFiles.forEach(file => {
    console.log(file.path, file.contents);
  });
});

Это часто используется для:

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

Взаимодействие onStart и onEnd в одном цикле сборки

Хуки образуют замкнутый цикл:

  1. onStart — подготовка среды;
  2. анализ зависимостей;
  3. компиляция;
  4. onEnd — обработка результата.

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

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

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

build.onStart(() => {
  console.log('A: start');
});

build.onStart(() => {
  console.log('B: start');
});

build.onEnd(() => {
  console.log('A: end');
});

build.onEnd(() => {
  console.log('B: end');
});

Асинхронность и ограничения выполнения

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

Асинхронный onStart

build.onStart(async () => {
  await new Promise(r => setTimeout(r, 100));
});
  • блокирует начало компиляции до завершения Promise;
  • влияет на latency сборки.

Асинхронный onEnd

build.onEnd(async (result) => {
  await writeReport(result);
});
  • не блокирует следующую сборку напрямую;
  • используется для побочных эффектов (логирование, уведомления, аналитика).

Практические сценарии использования

Кэширование между сборками

let cache = new Map();

build.onStart(() => {
  cache.set('startedAt', Date.now());
});

build.onEnd((result) => {
  cache.set('lastBuildErrors', result.errors.length);
});

Отправка уведомлений после сборки

build.onEnd((result) => {
  if (result.errors.length === 0) {
    notify('Build succeeded');
  } else {
    notify('Build failed');
  }
});

Измерение времени сборки

let startTime;

build.onStart(() => {
  startTime = Date.now();
});

build.onEnd(() => {
  console.log('Время сборки:', Date.now() - startTime);
});

Поведение в incremental и watch API

При использовании:

  • context.watch()
  • context.rebuild()

хуки работают как часть повторяющегося цикла.

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

  • onStart вызывается при каждом rebuild;
  • onEnd соответствует каждому результату;
  • состояние между итерациями сохраняется в замыканиях плагина.

Ошибки и устойчивость выполнения

Если в onStart или onEnd возникает исключение:

  • оно не всегда прерывает сборку полностью;
  • может быть зафиксировано как ошибка плагина;
  • последующие плагины могут не выполниться в текущем цикле.

Рекомендуется изолировать критические операции:

build.onEnd(() => {
  try {
    riskyOperation();
  } catch (e) {
    console.error('Ошибка в плагине:', e);
  }
});

Ограничения хуков

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

Роль хуков в архитектуре плагинов Esbuild

onStart и onEnd формируют каркас управления сборочным процессом, вокруг которого строятся более специализированные хуки:

  • onResolve — управление разрешением модулей;
  • onLoad — загрузка и трансформация файлов.

В отличие от них, данные хуки:

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