Начиная с версии 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:
Схема работы:
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();
В этом случае разработчик получает автоматически обновляемую сборку при каждом сохранении файлов.
Ранее использовался параметр:
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
}
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'
});
В результате:
Встроенный сервер предназначен прежде всего для разработки.
Его возможности ограничены:
Для производственной среды обычно используются специализированные решения:
dispose()Метод dispose() завершает работу контекста и освобождает
все связанные ресурсы.
После вызова прекращается работа:
await context.dispose();
После выполнения контекст считается уничтоженным.
Контекст удерживает системные ресурсы:
Если приложение больше не нуждается в сборке, ресурсы должны быть освобождены.
const context = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js'
});
await context.watch();
/* работа приложения */
await context.dispose();
После вызова наблюдение прекращается.
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().