esbuild.stop(): завершение дочернего процесса

В основе работы esbuild лежит отдельный компилирующий сервис, реализованный на языке Go и запускаемый как дочерний процесс. JavaScript-обёртка взаимодействует с ним через IPC (межпроцессное взаимодействие), передавая задачи сборки и получая результаты обратно. Такой подход обеспечивает высокую скорость, так как основная нагрузка выполняется вне V8 и не блокирует event loop.

При инициализации через esbuild.startService() создаётся долгоживущий процесс, который хранит состояние между сборками: кеши, модули, граф зависимостей и внутренние структуры оптимизации. Это позволяет значительно ускорять повторные сборки.

Однако наличие постоянного дочернего процесса требует явного управления его жизненным циклом. Для этого используется метод завершения сервиса — stop().


Назначение stop(): завершение жизненного цикла сервиса

Метод stop() предназначен для корректного завершения работы фонового процесса esbuild, созданного через startService().

Ключевая функция:

  • завершение дочернего процесса Go-сервиса
  • освобождение системных ресурсов (память, файловые дескрипторы)
  • закрытие IPC-канала между Node.js и esbuild
  • предотвращение утечек процессов при длительной работе приложения

После вызова stop() сервис становится недоступным для дальнейших операций сборки.


Внутренний механизм остановки процесса

При вызове service.stop() происходит последовательность операций:

  1. Отправка сигнала завершения через IPC-канал
  2. Завершение всех текущих задач сборки (если они активны)
  3. Очистка внутренних очередей команд
  4. Закрытие соединения между Node.js и дочерним процессом
  5. Завершение процесса Go runtime

Важно, что esbuild старается завершиться корректно, дождавшись окончания активных сборок, а не прерывая их мгновенно.


Синхронность и асинхронность stop()

Метод stop() возвращает Promise и работает асинхронно.

import * as esbuild from 'esbuild';

const service = await esbuild.startService();

// выполнение сборок
await service.build({
  entryPoints: ['src/index.js'],
  outfile: 'dist/bundle.js'
});

// завершение сервиса
await service.stop();

Асинхронная природа связана с тем, что необходимо дождаться:

  • завершения текущих задач сборки
  • подтверждения остановки от Go-процесса
  • закрытия IPC-канала

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

Если в момент вызова stop() выполняется сборка, esbuild переходит в режим контролируемого завершения:

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

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


Повторные вызовы stop()

Повторный вызов stop() после завершения сервиса:

  • не создаёт новый процесс
  • не выполняет дополнительных действий
  • обычно возвращает уже завершённое состояние Promise

Сервис после остановки считается одноразовым объектом жизненного цикла.


Ошибки и состояния гонки

Возможны ситуации, когда остановка вызывается параллельно с другими операциями:

  • build() в процессе выполнения
  • watch() режим активен
  • параллельные вызовы API сервиса

В таких случаях esbuild координирует очередь задач, однако при неправильном порядке вызовов может возникать:

  • отклонение промисов сборки
  • ошибки типа “service is stopped”
  • незавершённые файловые операции

stop() и watch-режим

Особое поведение наблюдается при использовании watch API.

При активном наблюдении за файлами:

  • watch-процесс продолжает следить за изменениями
  • при вызове stop() наблюдатель также завершается
  • file watchers освобождаются вместе с IPC-соединением

Таким образом, stop() является универсальным способом завершения всей инфраструктуры esbuild-сервиса.


Использование в долгоживущих приложениях

В серверных приложениях (например, Node.js API или SSR-серверах) esbuild часто используется как фоновый компилятор. В таких случаях stop() становится частью процедуры graceful shutdown:

  • завершение HTTP-сервера
  • остановка фоновых задач
  • завершение esbuild-сервиса
process.on('SIGTERM', async () => {
  await service.stop();
  process.exit(0);
});

Это предотвращает появление «зависших» процессов после остановки приложения.


Освобождение ресурсов

После выполнения stop() освобождаются:

  • память Go-процесса
  • кеши модулей и зависимостей
  • временные структуры графа сборки
  • файловые дескрипторы IPC
  • системные ресурсы наблюдения за файлами

На уровне операционной системы дочерний процесс полностью исчезает, что особенно важно для контейнеризированных сред и CI/CD систем.


Отличие от альтернативных стратегий завершения

Некоторые инструменты используют принудительное завершение процессов через kill. В esbuild такой подход не является основным, поскольку:

  • может привести к повреждению текущей сборки
  • оставляет временные файлы в неопределённом состоянии
  • ухудшает предсказуемость поведения в pipeline

stop() реализует мягкое завершение, ориентированное на корректность и детерминированность состояния.


Связь с жизненным циклом startService()

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

  • startService() — создание долгоживущего компиляционного процесса
  • stop() — завершение этого процесса и очистка среды

Такой подход делает esbuild пригодным для:

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

При этом каждый сервисный инстанс существует независимо и требует явного завершения через stop().