До появления Context API типичный сценарий работы с esbuild выглядел
как последовательные вызовы build(). Каждый новый запуск
сборки создавал отдельный процесс сборки, заново анализировал граф
зависимостей и выполнял все этапы компиляции.
Для задач разработки такой подход не всегда эффективен. Необходимо поддерживать:
Для решения этих задач в esbuild был реализован Context API,
центральным элементом которого является функция
esbuild.context().
Она создаёт специальный объект контекста сборки, внутри которого хранится состояние проекта, кэшированные данные и параметры сборки. После создания контекста можно многократно выполнять пересборки, запускать сервер разработки и отслеживать изменения файлов без повторной инициализации всей системы сборки.
Создание контекста выглядит следующим образом:
import * as esbuild from 'esbuild';
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js'
});
Метод возвращает объект контекста:
const ctx = await esbuild.context(options);
Где:
options — обычные параметры сборки esbuild;ctx — экземпляр контекста.После создания контекста становятся доступны дополнительные методы управления жизненным циклом сборки.
build()Обычная сборка:
await esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js'
});
Сборка через контекст:
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js'
});
await ctx.rebuild();
Ключевое отличие состоит в том, что:
build() выполняет единичную сборку;context() создаёт долговременный объект для
многократных операций.Контекст особенно полезен во время разработки, когда сборка выполняется десятки или сотни раз.
Работа с Context API обычно состоит из четырёх этапов:
Схема выглядит следующим образом:
context()
↓
rebuild()
↓
watch()
↓
serve()
↓
dispose()
Необязательно использовать все этапы одновременно.
rebuild()После создания контекста сборка автоматически не запускается.
Необходимо вызвать метод:
await ctx.rebuild();
Полный пример:
import * as esbuild from 'esbuild';
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js'
});
await ctx.rebuild();
Такое поведение отличается от build(), где сборка
начинается сразу после вызова функции.
rebuild()Метод rebuild() запускает сборку повторно, используя уже
существующий контекст.
await ctx.rebuild();
Преимущества:
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js'
});
await ctx.rebuild();
await ctx.rebuild();
await ctx.rebuild();
Каждая последующая сборка обычно выполняется быстрее первой.
const result = await ctx.rebuild();
console.log(result.errors);
console.log(result.warnings);
Объект результата аналогичен возвращаемому значению
build().
watch()Одним из наиболее распространённых сценариев использования Context API является автоматическая пересборка проекта при изменении файлов.
Для этого используется метод:
await ctx.watch();
Пример:
import * as esbuild from 'esbuild';
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js'
});
await ctx.watch();
После запуска наблюдения esbuild:
watch()При изменении файла:
src/index.js
происходит следующий процесс:
Изменение файла
↓
Обнаружение события
↓
Пересборка
↓
Обновление output-файлов
При этом разработчику не требуется вручную запускать команду сборки.
Сам метод watch() не предоставляет обработчиков
событий.
Для логирования часто используются плагины:
const logPlugin = {
name: 'logger',
setup(build) {
build.onEnd(result => {
console.log(
`Build completed with ${result.errors.length} errors`
);
});
}
};
Подключение:
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js',
plugins: [logPlugin]
});
await ctx.watch();
serve()Контекст может запускать встроенный HTTP-сервер.
Метод:
await ctx.serve();
Пример:
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outdir: 'dist'
});
const server = await ctx.serve({
servedir: 'dist'
});
Результат:
console.log(server.host);
console.log(server.port);
После запуска сервера возвращается объект:
{
hosts: [...],
port: 8000
}
Пример:
const result = await ctx.serve({
servedir: 'dist'
});
console.log(result.port);
Можно получить порт и использовать его в дополнительных инструментах автоматизации.
watch() и
serve()Наиболее популярная конфигурация разработки выглядит следующим образом:
import * as esbuild from 'esbuild';
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outdir: 'dist'
});
await ctx.watch();
await ctx.serve({
servedir: 'dist'
});
В результате:
Подобная схема фактически превращает esbuild в полноценный инструмент разработки.
dispose()После завершения работы контекст рекомендуется уничтожать.
Для этого используется метод:
await ctx.dispose();
Пример:
await ctx.dispose();
Метод выполняет:
dispose()Без корректного завершения могут возникать:
Корректный шаблон:
const ctx = await esbuild.context(options);
try {
await ctx.watch();
}
finally {
await ctx.dispose();
}
Для CLI-инструментов часто используется перехват системных сигналов.
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js'
});
await ctx.watch();
process.on('SIGINT', async () => {
await ctx.dispose();
process.exit(0);
});
При нажатии Ctrl+C ресурсы будут освобождены
корректно.
Контекст полностью совместим с системой плагинов esbuild.
const plugin = {
name: 'example',
setup(build) {
build.onStart(() => {
console.log('Build started');
});
build.onEnd(() => {
console.log('Build finished');
});
}
};
Использование:
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js',
plugins: [plugin]
});
Все пересборки будут автоматически использовать подключённые плагины.
Основное преимущество Context API связано с повторным использованием уже построенного состояния проекта.
Во время первой сборки esbuild:
При использовании rebuild() значительная часть этой
информации уже находится в памяти.
Упрощённо процесс выглядит так:
Первая сборка
↓
Создание графа зависимостей
↓
Кэширование
Изменение файла
↓
Повторный анализ только изменённых частей
↓
Быстрая пересборка
На крупных проектах выигрыш во времени может составлять несколько порядков по сравнению с полным перезапуском сборки.
import * as esbuild from 'esbuild';
async function start() {
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
sourcemap: true,
outdir: 'dist'
});
await ctx.watch();
const server = await ctx.serve({
servedir: 'dist'
});
console.log(
`Server running on http://localhost:${server.port}`
);
}
start();
Такой сценарий обеспечивает:
После создания контекста параметры сборки становятся фиксированными.
Нельзя выполнить:
const ctx = await esbuild.context({
outfile: 'a.js'
});
а затем изменить:
ctx.outfile = 'b.js';
Для новой конфигурации требуется новый контекст.
Если необходимо собирать проект в разных режимах:
development
production
library
создаются отдельные контексты:
const devCtx = await esbuild.context(devConfig);
const prodCtx = await esbuild.context(prodConfig);
| Возможность | build() | context() |
|---|---|---|
| Разовая сборка | Да | Через rebuild() |
| Пересборка | Нет | Да |
| Watch Mode | Нет | Да |
| Встроенный сервер | Нет | Да |
| Повторное использование кэша | Нет | Да |
| Управление жизненным циклом | Ограничено | Полное |
| Подходит для dev-среды | Частично | Да |
build()Подходит для:
Пример:
await esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
minify: true,
outfile: 'dist/app.js'
});
context()Подходит для:
Пример типичной структуры:
const ctx = await esbuild.context(config);
await ctx.watch();
await ctx.serve({
servedir: 'dist'
});
В современной экосистеме esbuild именно
esbuild.context() является основой для создания
производительных сред разработки, поскольку объединяет управление
сборкой, отслеживание изменений, встроенный сервер и механизмы
повторного использования внутренних кэшей в едином жизненном цикле
приложения.