Переменная process.env.NODE_ENV давно стала стандартным
механизмом определения режима работы JavaScript-приложения. Большинство
библиотек экосистемы автоматически меняют поведение в зависимости от её
значения:
development — режим разработки;production — режим продакшена;test — режим тестирования.Во время сборки приложения значение этой переменной обычно подставляется непосредственно в исходный код. Благодаря этому инструменты оптимизации способны удалять ненужные ветви кода и уменьшать размер итогового бандла.
Пример:
if (process.env.NODE_ENV === 'development') {
console.log('Режим разработки');
}
После подстановки значения:
if ('production' === 'development') {
console.log('Режим разработки');
}
Так как условие всегда ложно, Esbuild сможет полностью удалить этот блок из итоговой сборки.
В браузере объект process отсутствует.
Код:
console.log(process.env.NODE_ENV);
выполнится корректно в Node.js, но в браузере приведёт к ошибке:
ReferenceError: process is not defined
Поэтому при создании фронтенд-приложений необходимо заменить обращение к переменной окружения на конкретное строковое значение ещё на этапе сборки.
Esbuild предоставляет встроенный механизм такой подстановки.
defineДля замены выражений используется параметр define.
Простейший пример:
await esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js',
define: {
'process.env.NODE_ENV': '"production"'
}
});
После сборки каждое вхождение:
process.env.NODE_ENV
будет заменено на:
"production"
Важно обратить внимание на двойное экранирование строкового литерала.
Правильно:
define: {
'process.env.NODE_ENV': '"production"'
}
Неправильно:
define: {
'process.env.NODE_ENV': 'production'
}
Во втором случае Esbuild будет воспринимать значение как JavaScript-идентификатор, а не как строку.
Исходный код:
if (process.env.NODE_ENV === 'production') {
enableAnalytics();
} else {
enableDebugTools();
}
Конфигурация:
define: {
'process.env.NODE_ENV': '"production"'
}
После преобразования:
if ("production" === 'production') {
enableAnalytics();
} else {
enableDebugTools();
}
После оптимизации:
enableAnalytics();
Ветка разработки полностью исчезнет.
Настройка может выполняться без JavaScript-конфигурации.
Пример:
esbuild src/index.js \
--bundle \
--outfile=dist/app.js \
--define:process.env.NODE_ENV=\"production\"
Для режима разработки:
esbuild src/index.js \
--bundle \
--outfile=dist/app.js \
--define:process.env.NODE_ENV=\"development\"
Это особенно удобно при создании npm-скриптов.
Пример файла package.json:
{
"scripts": {
"dev": "esbuild src/index.js --bundle --outfile=dist/app.js --define:process.env.NODE_ENV=\\\"development\\\"",
"build": "esbuild src/index.js --bundle --minify --outfile=dist/app.js --define:process.env.NODE_ENV=\\\"production\\\""
}
}
Чаще всего значение зависит от окружения запуска.
const production = process.env.NODE_ENV === 'production';
await esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js',
define: {
'process.env.NODE_ENV': JSON.stringify(
production ? 'production' : 'development'
)
}
});
Функция JSON.stringify() позволяет автоматически
формировать корректный строковый литерал.
Фактически код превращается в:
define: {
'process.env.NODE_ENV': '"production"'
}
или
define: {
'process.env.NODE_ENV': '"development"'
}
Одно из главных преимуществ замены переменной окружения связано с механизмом Tree Shaking.
Рассмотрим пример:
export function logger(message) {
if (process.env.NODE_ENV !== 'production') {
console.log(message);
}
}
После подстановки:
export function logger(message) {
if ("production" !== 'production') {
console.log(message);
}
}
Условие становится заведомо ложным.
После минимизации:
export function logger() {}
или даже полностью удаляется, если функция нигде не используется.
В крупных проектах подобная оптимизация позволяет существенно уменьшить объём итогового JavaScript-кода.
Многие библиотеки ориентируются именно на значение
process.env.NODE_ENV.
Например:
if (process.env.NODE_ENV !== 'production') {
warning();
}
Подобные конструкции используются в:
Без корректной подстановки переменной итоговый код может содержать дополнительные проверки и отладочную функциональность.
minifyПри включённой минификации Esbuild может автоматически подставлять значение переменной в некоторых сценариях.
Пример:
await esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
minify: true,
outfile: 'dist/app.js'
});
Однако полагаться на автоматическое поведение не рекомендуется.
Явное указание:
define: {
'process.env.NODE_ENV': '"production"'
}
делает конфигурацию предсказуемой и одинаковой во всех версиях сборщика.
Часто используются разные параметры для разработки и продакшена.
await esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
sourcemap: true,
outfile: 'dist/app.js',
define: {
'process.env.NODE_ENV': '"development"'
}
});
Особенности:
await esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
minify: true,
outfile: 'dist/app.js',
define: {
'process.env.NODE_ENV': '"production"'
}
});
Особенности:
Механизм define не ограничивается только
NODE_ENV.
Пример:
define: {
'process.env.NODE_ENV': '"production"',
'process.env.API_URL': '"https://api.example.com"',
'process.env.APP_VERSION': '"1.5.0"'
}
Код:
fetch(process.env.API_URL + '/users');
превратится в:
fetch("https://api.example.com" + '/users');
А значение версии:
console.log(process.env.APP_VERSION);
станет:
console.log("1.5.0");
Для большого количества переменных удобно формировать объект динамически.
const define = {};
for (const [key, value] of Object.entries(process.env)) {
define[`process.env.${key}`] = JSON.stringify(value);
}
Далее:
await esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js',
define
});
Такой подход напоминает работу webpack-плагина DefinePlugin.
define выполняет простую текстовую замену выражений на
этапе компиляции.
Например:
process.env.NODE_ENV
заменится успешно.
Однако код:
process.env['NODE_ENV']
не будет обработан.
Аналогично:
const key = 'NODE_ENV';
process.env[key];
не подлежит замене, поскольку значение свойства определяется динамически.
Для корректной работы рекомендуется использовать прямое обращение:
process.env.NODE_ENV
processИногда сторонние библиотеки ожидают существование объекта
process.
Вместо подстановки отдельных переменных можно определить весь объект:
define: {
process: JSON.stringify({
env: {
NODE_ENV: 'production'
}
})
}
После преобразования:
console.log(process.env.NODE_ENV);
станет:
console.log({
env: {
NODE_ENV: 'production'
}
}.env.NODE_ENV);
Подобный подход используется редко, поскольку увеличивает размер бандла и уступает точечной замене по эффективности.
Наиболее распространённая конфигурация выглядит следующим образом:
import * as esbuild from 'esbuild';
const isProduction = process.argv.includes('--production');
await esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
minify: isProduction,
sourcemap: !isProduction,
outfile: 'dist/app.js',
define: {
'process.env.NODE_ENV': JSON.stringify(
isProduction ? 'production' : 'development'
)
}
});
Преимущества такого решения:
process в браузере.