Методы context: rebuild, watch, serve, dispose

Начиная с версии 0.17, Esbuild предоставляет API context(), предназначенный для организации длительно работающих процессов сборки. В отличие от одноразового вызова build(), который выполняет сборку и завершает работу, контекст создаёт управляемый экземпляр сборщика, способный выполнять повторные сборки, отслеживать изменения файлов, запускать локальный сервер и корректно освобождать ресурсы.

Контекст выступает центральным объектом для работы с механизмами:

  • rebuild()
  • watch()
  • serve()
  • dispose()

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

import * as esbuild from 'esbuild';

const context = await esbuild.context({
  entryPoints: ['src/index.js'],
  bundle: true,
  outfile: 'dist/app.js'
});

После создания контекста становятся доступны методы управления жизненным циклом сборки.


Метод rebuild()

Назначение

Метод rebuild() запускает повторную сборку проекта с использованием уже подготовленного контекста.

Во время первой инициализации Esbuild выполняет значительный объём работы:

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

При последующих вызовах rebuild() большая часть этих данных повторно используется, что делает сборку существенно быстрее по сравнению с новым вызовом build().


Базовое использование

import * as esbuild from 'esbuild';

const context = await esbuild.context({
  entryPoints: ['src/index.js'],
  bundle: true,
  outfile: 'dist/app.js'
});

await context.rebuild();

После вызова выполняется полноценная пересборка проекта.


Возвращаемое значение

Метод возвращает объект результата сборки аналогично build().

const result = await context.rebuild();

console.log(result.errors);
console.log(result.warnings);

Результат содержит:

{
  errors: [],
  warnings: [],
  outputFiles: [...]
}

Набор свойств зависит от используемых параметров сборки.


Практическое применение

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

Пример собственной системы мониторинга:

import fs from 'fs';
import * as esbuild from 'esbuild';

const context = await esbuild.context({
  entryPoints: ['src/index.js'],
  bundle: true,
  outfile: 'dist/app.js'
});

fs.watch('src', async () => {
  await context.rebuild();
});

При каждом изменении файлов выполняется быстрая пересборка проекта.


Производительность повторной сборки

Главное преимущество rebuild() заключается в повторном использовании внутреннего состояния.

Схема работы выглядит следующим образом:

Первая сборка
 ├─ Чтение файлов
 ├─ Построение графа зависимостей
 ├─ Анализ модулей
 └─ Генерация результата

Повторная сборка
 ├─ Проверка изменений
 ├─ Обновление изменённых модулей
 └─ Генерация результата

На крупных проектах время пересборки может уменьшаться в несколько раз.


Метод watch()

Назначение

Метод watch() переводит контекст в режим наблюдения за файловой системой.

После активации Esbuild автоматически отслеживает изменения:

  • исходных файлов;
  • импортируемых модулей;
  • ресурсов, участвующих в сборке.

При обнаружении изменений запускается автоматическая пересборка.


Базовый пример

import * as esbuild from 'esbuild';

const context = await esbuild.context({
  entryPoints: ['src/index.js'],
  bundle: true,
  outfile: 'dist/app.js'
});

await context.watch();

После запуска процесс остаётся активным и ожидает изменений файлов.


Что происходит после запуска

После вызова:

await context.watch();

Esbuild:

  1. Выполняет начальную сборку.
  2. Создаёт наблюдателей файловой системы.
  3. Отслеживает зависимости.
  4. Автоматически запускает пересборку при изменениях.

Схема работы:

watch()
    │
    ▼
Начальная сборка
    │
    ▼
Ожидание изменений
    │
    ├─ Изменение файла A
    │        ▼
    │    Пересборка
    │
    ├─ Изменение файла B
    │        ▼
    │    Пересборка
    │
    └─ ...

Использование с выводом ошибок

try {
  await context.watch();
} catch (error) {
  console.error(error);
}

Ошибки сборки не приводят к остановке режима наблюдения. После исправления файлов Esbuild попытается выполнить следующую пересборку.


Режим разработки

Наиболее распространённый сценарий:

const context = await esbuild.context({
  entryPoints: ['src/main.js'],
  bundle: true,
  sourcemap: true,
  outfile: 'dist/bundle.js'
});

await context.watch();

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


Отличия от старого API

Ранее использовался параметр:

watch: true

или специальные возможности build().

Современный подход через контекст:

const context = await esbuild.context(options);

await context.watch();

обеспечивает:

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

Метод serve()

Назначение

Метод serve() запускает встроенный HTTP-сервер Esbuild.

Он особенно полезен в процессе разработки, когда требуется быстро раздать собранные файлы без настройки внешнего веб-сервера.


Базовый пример

import * as esbuild from 'esbuild';

const context = await esbuild.context({
  entryPoints: ['src/index.js'],
  bundle: true,
  outfile: 'public/app.js'
});

const server = await context.serve({
  servedir: 'public'
});

После запуска файлы становятся доступны через HTTP.


Параметр servedir

Основной параметр:

{
  servedir: 'public'
}

Он определяет директорию, содержимое которой будет доступно клиентам.

Структура проекта:

project/
├─ public/
│  ├─ index.html
│  └─ app.js
└─ src/

После запуска сервера браузер сможет получать файлы из каталога public.


Получение информации о сервере

Метод возвращает объект с данными о запущенном сервере.

const result = await context.serve({
  servedir: 'public'
});

console.log(result);

Пример структуры:

{
  hosts: ['127.0.0.1'],
  port: 8000
}

Формирование URL

const server = await context.serve({
  servedir: 'public'
});

console.log(
  `http://${server.hosts[0]}:${server.port}`
);

Результат:

http://127.0.0.1:8000

Указание порта

Можно явно задать номер порта:

await context.serve({
  servedir: 'public',
  port: 3000
});

Теперь сервер будет использовать:

http://localhost:3000

если порт свободен.


Указание хоста

await context.serve({
  servedir: 'public',
  host: '0.0.0.0'
});

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


Совмещение watch() и serve()

Наиболее популярный сценарий разработки:

const context = await esbuild.context({
  entryPoints: ['src/index.js'],
  bundle: true,
  outfile: 'public/app.js'
});

await context.watch();

await context.serve({
  servedir: 'public'
});

В результате:

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

Ограничения встроенного сервера

Встроенный сервер предназначен прежде всего для разработки.

Его возможности ограничены:

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

Для производственной среды обычно используются специализированные решения:

  • Nginx;
  • Apache;
  • Caddy;
  • Node.js-серверы;
  • облачные платформы доставки контента.

Метод dispose()

Назначение

Метод dispose() завершает работу контекста и освобождает все связанные ресурсы.

После вызова прекращается работа:

  • наблюдателей файловой системы;
  • внутренних кэшей;
  • фоновых процессов;
  • встроенного HTTP-сервера.

Базовое использование

await context.dispose();

После выполнения контекст считается уничтоженным.


Когда требуется освобождение ресурсов

Контекст удерживает системные ресурсы:

  • файловые дескрипторы;
  • объекты мониторинга файлов;
  • память кэша;
  • сетевые сокеты сервера.

Если приложение больше не нуждается в сборке, ресурсы должны быть освобождены.


Завершение режима наблюдения

const context = await esbuild.context({
  entryPoints: ['src/index.js'],
  bundle: true,
  outfile: 'dist/app.js'
});

await context.watch();

/* работа приложения */

await context.dispose();

После вызова наблюдение прекращается.


Завершение HTTP-сервера

const context = await esbuild.context({
  entryPoints: ['src/index.js'],
  bundle: true,
  outfile: 'public/app.js'
});

await context.serve({
  servedir: 'public'
});

/* работа приложения */

await context.dispose();

Сервер закрывается автоматически.


Обработка системных сигналов

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

process.on('SIGINT', async () => {
  await context.dispose();
  process.exit(0);
});

Теперь при нажатии:

Ctrl + C

выполняется корректное освобождение ресурсов.


Поведение после уничтожения контекста

После вызова:

await context.dispose();

использование контекста становится невозможным.

Например:

await context.dispose();

await context.rebuild();

приведёт к ошибке, поскольку объект уже завершил работу.


Совместное использование методов

Контекст создаёт единый центр управления процессом сборки.

Типичная схема разработки:

import * as esbuild from 'esbuild';

const context = await esbuild.context({
  entryPoints: ['src/index.js'],
  bundle: true,
  sourcemap: true,
  outfile: 'public/app.js'
});

await context.watch();

const server = await context.serve({
  servedir: 'public'
});

console.log(
  `http://${server.hosts[0]}:${server.port}`
);

process.on('SIGINT', async () => {
  await context.dispose();
  process.exit(0);
});

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

context()
    │
    ▼
 watch()
    │
    ▼
 serve()
    │
    ▼
 Автоматические пересборки
    │
    ▼
 dispose()

Такой подход позволяет построить полноценную среду разработки на основе одного объекта контекста, используя быстрые пересборки rebuild(), автоматическое отслеживание изменений через watch(), встроенный веб-сервер serve() и корректное освобождение ресурсов посредством dispose().