Архитектура сборки в esbuild строится вокруг идеи быстрых инкрементальных пересборок и минимальной конфигурационной сложности. При этом реальные проекты неизбежно требуют различного поведения в режиме разработки и в продакшене: разные уровни оптимизации, отладочные инструменты, стратегия работы с исходным кодом и ассетами.
Ключевая идея заключается в том, что конфигурация не должна быть монолитной. Вместо этого она разделяется на:
В esbuild это реализуется либо через программный API, либо через скрипты CLI с параметризацией.
Общий слой включает параметры, которые не зависят от окружения:
// build/base.js
export const baseConfig = {
entryPoints: ['src/index.ts'],
bundle: true,
platform: 'browser',
format: 'esm',
target: ['es2020'],
outdir: 'dist',
loader: {
'.png': 'file',
'.svg': 'file',
'.css': 'css'
}
};
Этот слой определяет фундамент:
Режим разработки в esbuild ориентирован на минимальное время цикла «изменение → результат».
// build/dev.js
import esbuild from 'esbuild';
import { baseConfig } from './base.js';
esbuild.context({
...baseConfig,
sourcemap: true,
minify: false,
define: {
'process.env.NODE_ENV': '"development"'
}
}).then(async (ctx) => {
await ctx.watch();
await ctx.serve({
port: 3000,
servedir: 'public'
});
});
Source maps
Отладка становится прозрачной за счёт карт исходников:
sourcemap: true
Варианты:
true — отдельные файлы sourcemapinline — встроенные в JSexternal — отдельные .map файлыWatch-режим
Esbuild пересобирает только изменённые модули:
await ctx.watch();
Механизм основан на инкрементальной компиляции, что обеспечивает минимальные задержки.
Dev-сервер
Встроенный сервер используется без дополнительного инструментария:
await ctx.serve({
port: 3000,
servedir: 'public'
});
Он не является полноценным backend-сервером, но достаточен для фронтенд-разработки.
Incremental build
Дополнительное ускорение достигается за счёт кеширования:
esbuild.build({
...baseConfig,
incremental: true
});
Продакшен-сборка ориентирована на минимальный размер и максимальную производительность.
// build/prod.js
import esbuild from 'esbuild';
import { baseConfig } from './base.js';
esbuild.build({
...baseConfig,
minify: true,
sourcemap: false,
splitting: true,
format: 'esm',
define: {
'process.env.NODE_ENV': '"production"'
},
metafile: true,
chunkNames: 'chunks/[name]-[hash]'
});
Минификация включает:
minify: true
В esbuild минификация реализована на уровне Go-движка, что делает её значительно быстрее большинства JS-минификаторов.
При включённом бандлинге esbuild автоматически удаляет неиспользуемые экспорты:
bundle: true
Особенно эффективно при использовании ES Modules:
export const used = () => {};
export const unused = () => {};
Если unused не импортируется, он исключается из
финального бандла.
Для крупных приложений используется разделение чанков:
splitting: true,
format: 'esm'
Важно:
esmoutdir, а не outfileMetafile позволяет анализировать структуру бандла:
import fs from 'fs';
const result = await esbuild.build({
...baseConfig,
metafile: true,
minify: true
});
fs.writeFileSync(
'meta.json',
JSON.stringify(result.metafile)
);
Использование metafile:
Часто используется единый входной файл с переключением режима:
const mode = process.env.NODE_ENV;
const isProd = mode === 'production';
esbuild.build({
...baseConfig,
minify: isProd,
sourcemap: !isProd,
define: {
'process.env.NODE_ENV': JSON.stringify(mode)
}
});
Dev-сборка:
esbuild src/index.ts --bundle --servedir=public --sourcemap --watch
Prod-сборка:
esbuild src/index.ts --bundle --minify --outdir=dist --format=esm
CLI используется для простых проектов или CI-скриптов без сложной логики.
Структура проекта часто выглядит так:
build/
base.js
dev.js
prod.js
И единый вход:
import './build/dev.js';
или
import './build/prod.js';
CSS в dev:
CSS в prod:
loader: {
'.css': 'css'
},
minify: true
В prod часто фиксируется более строгий target:
target: ['es2019']
Это влияет на:
Dev может использовать более современный target:
target: ['esnext']
В некоторых случаях зависимости исключаются из бандла:
external: ['react', 'react-dom']
Используется в prod при:
Dev-режим активно использует кеш:
let ctx = await esbuild.context(baseConfig);
await ctx.watch();
Особенность:
Смешивание dev и prod флагов
Нежелательно включать одновременно:
watch и minifysourcemap: true и splitting без ESMtarget без необходимостиНеверный формат при splitting
format: 'cjs' // не поддерживает splitting
Правильно:
format: 'esm'
Использование outfile с code splitting
outfile: 'dist/app.js' // конфликтует со splitting
Нужно:
outdir: 'dist'
Типовая структура:
export function createConfig(mode) {
return {
...baseConfig,
minify: mode === 'production',
sourcemap: mode !== 'production'
};
}
Такой подход обеспечивает: