Современные frontend-приложения почти всегда используют переменные окружения и конфигурационные значения:
Webpack не предоставляет браузеру доступ к реальному
process.env, как это происходит в Node.js. Во время
выполнения клиентского JavaScript в браузере объекта окружения
операционной системы не существует.
Поэтому Webpack выполняет другую задачу: подставляет значения в код на этапе сборки.
process.env не работает в браузереВ Node.js переменные окружения доступны напрямую:
console.log(process.env.NODE_ENV);
Но браузер:
process.envЕсли собрать такой код без специальных настроек, возникнет ошибка:
process is not defined
Webpack решает проблему через:
DefinePluginEnvironmentPlugindotenv-webpackDefinePlugin выполняет текстовую замену выражений во
время компиляции.
Пример:
new webpack.DefinePlugin({
API_URL: JSON.stringify('https://api.site.com')
});
Клиентский код:
console.log(API_URL);
После сборки:
console.log("https://api.site.com");
Webpack буквально заменяет идентификатор на строковое значение.
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
API_URL: JSON.stringify('https://api.site.com')
})
]
};
Очень распространённая ошибка:
new webpack.DefinePlugin({
API_URL: 'https://api.site.com'
});
Webpack воспримет значение как JavaScript-код:
https://api.site.com
Это приведёт к синтаксической ошибке.
Правильный вариант:
new webpack.DefinePlugin({
API_URL: JSON.stringify('https://api.site.com')
});
После stringify:
"https://api.site.com"
Наиболее популярный подход:
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
});
Код приложения:
if (process.env.NODE_ENV === 'production') {
console.log('Production mode');
}
После сборки:
if ("production" === 'production') {
console.log('Production mode');
}
Webpack и Terser могут удалить неиспользуемые ветки кода во время оптимизации.
Пример:
if (process.env.NODE_ENV !== 'production') {
console.log('Debug info');
}
После production-сборки:
if ("production" !== 'production') {
console.log('Debug info');
}
Минификатор понимает, что условие всегда ложное, и удаляет блок полностью.
Это называется:
EnvironmentPlugin — упрощённая оболочка над
DefinePlugin.
Пример:
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.EnvironmentPlugin([
'NODE_ENV',
'API_URL'
])
]
};
Webpack возьмёт значения из системного окружения:
NODE_ENV=production
API_URL=https://api.site.com
console.log(process.env.API_URL);
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
API_URL: 'http://localhost:3000'
})
Если переменная отсутствует в системе, будет использовано значение по умолчанию.
| Возможность | DefinePlugin | EnvironmentPlugin |
|---|---|---|
| Произвольные глобальные константы | Да | Нет |
| Работа с process.env | Да | Да |
| Автоматическое чтение env | Нет | Да |
| Подходит для любых выражений | Да | Ограниченно |
| Гибкость | Максимальная | Средняя |
При большом количестве конфигураций неудобно:
Поэтому используются .env файлы.
API_URL=https://api.site.com
APP_VERSION=1.0.0
FEATURE_CHAT=true
Установка:
npm install dotenv-webpack --save-dev
Конфигурация:
const Dotenv = require('dotenv-webpack');
module.exports = {
plugins: [
new Dotenv()
]
};
console.log(process.env.API_URL);
Webpack автоматически подставит значения из .env.
Плагин:
.envФактически это автоматизация ручной конфигурации.
API_URL=http://localhost:3000
DEBUG=true
API_URL=https://api.production.com
DEBUG=false
Для поддержки множества окружений применяется:
npm install dotenv-flow --save-dev
Структура:
.env
.env.development
.env.production
.env.local
{
"scripts": {
"dev": "NODE_ENV=development webpack serve",
"build": "NODE_ENV=production webpack"
}
}
Команда:
NODE_ENV=production webpack
не работает в cmd.exe.
Установка:
npm install cross-env --save-dev
Скрипты:
{
"scripts": {
"dev": "cross-env NODE_ENV=development webpack serve",
"build": "cross-env NODE_ENV=production webpack"
}
}
module.exports = {
mode: 'production'
};
Webpack автоматически устанавливает:
process.env.NODE_ENV === 'production'
Webpack internally создаёт DefinePlugin:
'process.env.NODE_ENV': JSON.stringify('production')
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development')
})
Если mode: 'production', но вручную задан
development, могут возникнуть:
Значения должны совпадать.
APP_THEME=dark
ENABLE_CHAT=true
Конфигурация:
new webpack.EnvironmentPlugin([
'APP_THEME',
'ENABLE_CHAT'
])
Код:
console.log(process.env.APP_THEME);
Переменные окружения всегда являются строками.
Пример:
ENABLE_CHAT=true
В приложении:
console.log(typeof process.env.ENABLE_CHAT);
Результат:
string
const enabled = process.env.ENABLE_CHAT === 'true';
const timeout = Number(process.env.API_TIMEOUT);
APP_CONFIG={"theme":"dark"}
const config = JSON.parse(process.env.APP_CONFIG);
Все переменные, попавшие в клиентский bundle:
Нельзя хранить:
Допустимо:
Неверно:
SECRET_API_KEY=123456
fetch('/api', {
headers: {
Authorization: process.env.SECRET_API_KEY
}
});
После сборки ключ окажется внутри JS-бандла.
После production-сборки:
grep -r "SECRET_API_KEY" dist
или поиск по bundle-файлам.
Webpack подставляет значения только во время сборки.
Если требуется:
compile-time env недостаточно.
window.APP_CONFIG = {
API_URL: 'https://api.site.com'
};
<script src="/config.js"></script>
<script src="/bundle.js"></script>
console.log(window.APP_CONFIG.API_URL);
На практике часто используется смешанная схема:
Через Webpack:
Через window-конфиг:
if (process.env.FEATURE_CHAT === 'true') {
import('./chat');
}
Если:
process.env.FEATURE_CHAT === 'false'
Webpack может полностью исключить модуль из production bundle.
new webpack.DefinePlugin({
APP_VERSION: JSON.stringify(require('./package.json').version)
})
console.log(APP_VERSION);
new webpack.DefinePlugin({
BUILD_DATE: JSON.stringify(new Date().toISOString())
})
const childProcess = require('child_process');
const commitHash = childProcess
.execSync('git rev-parse --short HEAD')
.toString()
.trim();
new webpack.DefinePlugin({
COMMIT_HASH: JSON.stringify(commitHash)
});
new webpack.DefinePlugin({
__DEV__: JSON.stringify(true),
__TEST__: JSON.stringify(false)
})
if (__DEV__) {
console.log('Development');
}
Имена:
__DEV__
__TEST__
__PRODUCTION__
визуально отличаются от обычных переменных и уменьшают вероятность конфликтов.
Ошибка:
DEBUG: true
Правильно:
DEBUG: JSON.stringify(true)
Плохо:
new webpack.DefinePlugin({
process: {
env: process.env
}
})
Проблемы:
new webpack.DefinePlugin({
'process.env.API_URL': JSON.stringify(process.env.API_URL)
})
Если переменная отсутствует:
console.log(process.env.UNKNOWN_VAR);
результат может быть:
undefined
или ошибка в зависимости от конфигурации.
const apiUrl = process.env.API_URL || 'http://localhost:3000';
if (!process.env.API_URL) {
throw new Error('API_URL is required');
}
Проверка обычно выполняется до запуска сборки.
export const config = {
apiUrl: process.env.API_URL,
debug: process.env.DEBUG === 'true',
version: process.env.APP_VERSION
};
if (process.env.NODE_ENV === 'development') {
console.log('React dev mode');
}
React активно использует compile-time проверки.
Production build React сильно отличается от development.
if (process.env.NODE_ENV !== 'production') {
Vue.config.devtools = true;
}
Angular CLI также внедряет env-переменные на этапе сборки через Webpack-инфраструктуру.
| Тип | Когда определяется | Можно изменить без rebuild |
|---|---|---|
| Compile-time | Во время сборки | Нет |
| Runtime | Во время запуска | Да |
Подходит для:
Подходит для:
В крупных приложениях обычно используются:
.envdotenv-webpackВсе эти механизмы вместе формируют полноценную систему управления конфигурацией frontend-приложения.