Скрипт сборки вместо конфигурационного файла

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

Программная сборка в Esbuild основана на использовании функций build(), context() и дополнительных API, позволяющих управлять процессом без декларативных конфигураций.


Базовая идея программного подхода

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

Классическая конфигурация:

// esbuild.config.js
module.exports = {
  entryPoints: ['src/index.js'],
  bundle: true,
  outdir: 'dist',
  minify: true
};

Программный эквивалент:

import { build } from 'esbuild';

build({
  entryPoints: ['src/index.js'],
  bundle: true,
  outdir: 'dist',
  minify: true
});

Разница заключается не только в синтаксисе, но и в том, что параметры могут вычисляться во время выполнения.


Динамическая генерация параметров сборки

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

import { build } from 'esbuild';

const isProd = process.env.NODE_ENV === 'production';

build({
  entryPoints: ['src/index.js'],
  bundle: true,
  outdir: 'dist',
  minify: isProd,
  sourcemap: !isProd
});

В таком подходе логика сборки становится частью кода, а не внешним описанием.


Разделение сборки на логические блоки

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

import { build } from 'esbuild';

const sharedConfig = {
  bundle: true,
  sourcemap: true
};

const client = build({
  ...sharedConfig,
  entryPoints: ['src/client.js'],
  outfile: 'dist/client.js'
});

const server = build({
  ...sharedConfig,
  entryPoints: ['src/server.js'],
  platform: 'node',
  outfile: 'dist/server.js'
});

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


Использование контекста сборки

Современный API Esbuild предоставляет context(), который используется для инкрементальной сборки, наблюдения за файлами и управления жизненным циклом процесса.

import { context } from 'esbuild';

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

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

await ctx.watch();

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


Разработка сервера через программный API

Esbuild способен поднимать встроенный dev-сервер, что часто заменяет отдельные инструменты разработки.

await ctx.serve({
  port: 3000,
  servedir: 'dist'
});

В этом режиме сборка и HTTP-сервер объединяются в одном процессе. Это исключает необходимость конфигурации отдельного dev-server инструмента.


Условная сборка по окружению и аргументам

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

import { build } from 'esbuild';

const mode = process.argv.includes('--debug') ? 'debug' : 'release';

build({
  entryPoints: ['src/index.js'],
  bundle: true,
  outdir: 'dist',
  minify: mode === 'release',
  define: {
    __MODE__: JSON.stringify(mode)
  }
});

Здесь сборка становится реакцией на входные параметры, а не фиксированной схемой.


Разделение окружений внутри одного скрипта

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

import { build } from 'esbuild';

const target = process.env.TARGET;

if (target === 'node') {
  build({
    entryPoints: ['src/node.js'],
    platform: 'node',
    outfile: 'dist/node.js'
  });
} else {
  build({
    entryPoints: ['src/browser.js'],
    platform: 'browser',
    outfile: 'dist/browser.js'
  });
}

Такой подход уменьшает дублирование конфигураций и централизует логику сборки.


Интеграция плагинов в программном скрипте

Плагины в Esbuild подключаются как обычные JavaScript-объекты, что упрощает их композицию.

const examplePlugin = {
  name: 'example',
  setup(build) {
    build.onResolve({ filter: /.*/ }, args => {
      return { path: args.path, namespace: 'custom' };
    });
  }
};

Использование в сборке:

import { build } from 'esbuild';

build({
  entryPoints: ['src/index.js'],
  bundle: true,
  outfile: 'dist/app.js',
  plugins: [examplePlugin]
});

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


Композиция конфигурации через функции

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

import { build } from 'esbuild';

function createConfig({ minify }) {
  return {
    entryPoints: ['src/index.js'],
    bundle: true,
    outdir: 'dist',
    minify,
    sourcemap: true
  };
}

build(createConfig({ minify: true }));

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


Параллельные сборки

Так как build-функции возвращают Promise, несколько сборок могут выполняться одновременно.

import { build } from 'esbuild';

Promise.all([
  build({
    entryPoints: ['src/app.js'],
    outfile: 'dist/app.js',
    bundle: true
  }),
  build({
    entryPoints: ['src/admin.js'],
    outfile: 'dist/admin.js',
    bundle: true
  })
]);

Это ускоряет процесс сборки в монорепозиториях и многомодульных приложениях.


Обработка ошибок в скрипте сборки

Программная модель позволяет использовать стандартные механизмы JavaScript для контроля ошибок.

import { build } from 'esbuild';

try {
  await build({
    entryPoints: ['src/index.js'],
    bundle: true,
    outdir: 'dist'
  });
} catch (err) {
  console.error('Ошибка сборки:', err);
  process.exit(1);
}

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


Использование ESM и CommonJS в скриптах сборки

Esbuild-скрипты могут быть написаны как в формате ESM, так и CommonJS, что позволяет интегрировать их в разные типы проектов.

ESM:

import { build } from 'esbuild';

CommonJS:

const { build } = require('esbuild');

Выбор формата влияет только на способ импорта, не ограничивая функциональность API.


Управление выводом и форматами сборки

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

build({
  entryPoints: ['src/index.js'],
  bundle: true,
  outdir: 'dist',
  format: 'esm',
  splitting: true,
  chunkNames: 'chunks/[name]-[hash]'
});

Такая настройка особенно важна для приложений с динамическими импортами и код-сплиттингом.


Инкрементальная архитектура сборки

Использование context() и rebuild() позволяет строить систему сборки, работающую как долгоживущий процесс.

import { context } from 'esbuild';

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

await ctx.watch();

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

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


Замена конфигурационного файла как архитектурный сдвиг

Отказ от конфигурационного файла в пользу скрипта меняет саму модель управления сборкой. Конфигурация перестаёт быть статическим описанием и превращается в исполняемую программу, где доступны:

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

Это превращает процесс сборки в полноценный программируемый слой инфраструктуры, а не в декларативный набор настроек.