В реальных проектах сборка JavaScript почти всегда зависит от окружения выполнения: development, staging и production. Каждое окружение предъявляет разные требования к коду, оптимизациям, логированию и подключаемым зависимостям. Esbuild предоставляет быстрый и предсказуемый механизм управления условной логикой через параметры сборки, переменные окружения и подстановки значений на этапе компиляции.
Ключевая идея заключается в том, что различия между окружениями должны фиксироваться на этапе сборки, а не во время выполнения. Это позволяет:
Esbuild реализует это через опцию define, переменные
окружения и конфигурационные скрипты Node.js.
Наиболее распространённый подход — использование переменных окружения:
NODE_ENV=development node build.js
NODE_ENV=production node build.js
В Windows часто применяется cross-env:
cross-env NODE_ENV=production node build.js
Внутри конфигурации esbuild:
import * as esbuild from 'esbuild';
const env = process.env.NODE_ENV;
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/bundle.js',
define: {
'process.env.NODE_ENV': JSON.stringify(env)
}
});
Ключевой момент: значение должно быть сериализовано через
JSON.stringify, иначе в код попадёт некорректный
литерал.
После подстановки process.env.NODE_ENV код можно
разделять на ветки:
if (process.env.NODE_ENV === 'production') {
console.log = () => {};
}
В production-сборке Esbuild заменит выражение на строку, после чего возможна дальнейшая оптимизация и удаление мёртвого кода при minify.
Опция define является центральным инструментом условной
компиляции.
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
minify: true,
outfile: 'dist/app.js',
define: {
__DEV__: JSON.stringify(process.env.NODE_ENV !== 'production'),
__PROD__: JSON.stringify(process.env.NODE_ENV === 'production')
}
});
Использование в коде:
if (__DEV__) {
console.log('Development mode');
}
При production-сборке условие становится статическим:
if (false) {
console.log('Development mode');
}
После минификации блок полностью удаляется.
Для сложных проектов применяется разделение конфигураций:
build/
esbuild.dev.js
esbuild.staging.js
esbuild.prod.js
import * as esbuild from 'esbuild';
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
sourcemap: true,
outfile: 'dist/bundle.js',
define: {
'process.env.NODE_ENV': '"development"'
}
});
Особенности:
import * as esbuild from 'esbuild';
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
minify: true,
sourcemap: false,
outfile: 'dist/bundle.js',
define: {
'process.env.NODE_ENV': '"production"'
}
});
Особенности:
Staging часто используется как промежуточная среда для тестирования production-поведения:
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
minify: true,
sourcemap: true,
outfile: 'dist/bundle.js',
define: {
'process.env.NODE_ENV': '"staging"'
}
});
Esbuild позволяет реализовать feature flags без runtime-логики:
define: {
__FEATURE_CHAT__: 'true',
__FEATURE_ANALYTICS__: 'false'
}
Использование:
if (__FEATURE_CHAT__) {
import('./chat.js').then(initChat);
}
При сборке неподдерживаемые ветки исчезают после tree-shaking.
Esbuild поддерживает динамическое разбиение кода, но условность должна быть статически определима:
let logger;
if (__DEV__) {
logger = await import('./logger.dev.js');
} else {
logger = await import('./logger.prod.js');
}
При правильной конфигурации сборщик создаёт отдельные чанки для каждого варианта.
Иногда используется подход с разными entry points:
entryPoints: [
'src/index.dev.js'
]
Или динамическое переключение:
const entryPoints =
process.env.NODE_ENV === 'production'
? ['src/index.prod.js']
: ['src/index.dev.js'];
Плагины позволяют централизованно управлять условной логикой.
Пример плагина подстановки API URL:
const envPlugin = {
name: 'env-plugin',
setup(build) {
const env = process.env.NODE_ENV;
build.onResolve({ filter: /^@env$/ }, () => ({
path: '@env',
namespace: 'env-ns'
}));
build.onLoad({ filter: /.*/, namespace: 'env-ns' }, () => {
return {
contents: `
export const API_URL =
${env === 'production'
? '"https://api.prod.com"'
: '"http://localhost:3000"'}
`
};
});
}
};
Использование:
import { API_URL } from '@env';
Типовая структура build-скрипта:
import * as esbuild from 'esbuild';
const isProd = process.env.NODE_ENV === 'production';
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
minify: isProd,
sourcemap: !isProd,
outfile: 'dist/app.js',
define: {
__DEV__: JSON.stringify(!isProd),
__PROD__: JSON.stringify(isProd),
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}
});
Такая схема централизует всю условную логику в одном месте, исключая расхождение конфигураций.
При использовании TypeScript условные переменные объявляются через
global.d.ts:
declare const __DEV__: boolean;
declare const __PROD__: boolean;
Это позволяет сохранять статическую проверку без runtime-определений.
Esbuild выполняет несколько уровней оптимизации:
Пример:
if (false) {
expensiveFunction();
}
После сборки:
// блок удалён
if (__DEV__) {
console.debug('Debug info');
}
if (__PROD__) {
import('./analytics.js');
}
const API =
process.env.NODE_ENV === 'production'
? 'https://api.service.com'
: 'http://localhost:4000';
JSON.stringify в defineprocess.env во время выполненияУсловная логика в Esbuild работает эффективно только при детерминированных значениях. Сборщик опирается на статический анализ, поэтому любые условия должны быть вычислимы до этапа bundling. Это позволяет превращать конфигурацию окружений в механизм компиляции, а не выполнения.