Во время сборки frontend-приложения часто требуется разделять конфигурацию для разных окружений:
В каждом окружении используются разные значения:
Хранение таких значений непосредственно в исходном коде приводит к ряду проблем:
Для решения этой задачи используются .env файлы и
библиотека dotenv-webpack.
.env.env — текстовый файл с переменными окружения в
формате:
API_URL=https://api.example.com
APP_MODE=development
ENABLE_ANALYTICS=false
Каждая строка представляет собой пару:
КЛЮЧ=ЗНАЧЕНИЕ
Такие файлы широко используются:
process.env в WebpackВ Node.js объект process.env существует глобально.
Пример:
console.log(process.env.NODE_ENV);
Однако браузер не имеет объекта process.
Webpack заменяет обращения к process.env.* во время
сборки. Без специальных плагинов переменные окружения внутри
frontend-кода работать не будут.
Плагин dotenv-webpack автоматически:
.env файл;process.env.*.Установка:
npm install dotenv-webpack --save-dev
project/
├── src/
│ └── index.js
├── .env
├── webpack.config.js
└── package.json
.envAPI_URL=https://api.site.com
MODE=development
const Dotenv = require('dotenv-webpack');
module.exports = {
plugins: [
new Dotenv()
]
};
console.log(process.env.API_URL);
console.log(process.env.MODE);
После сборки Webpack заменит выражения на строковые литералы:
console.log("https://api.site.com");
console.log("development");
dotenv-webpack выполняет замену на этапе компиляции.
Это означает:
.env;.env не попадает в runtime;Фактически плагин работает аналогично DefinePlugin.
Все переменные, используемые во frontend-коде, становятся доступны пользователю.
Например:
SECRET_KEY=my-secret
Если переменная используется:
console.log(process.env.SECRET_KEY);
то значение попадёт в итоговый JS-файл.
Поэтому нельзя хранить во frontend:
.env файловЧасто проект содержит разные конфигурации.
Пример:
.env
.env.development
.env.production
.env.local
const Dotenv = require('dotenv-webpack');
module.exports = {
plugins: [
new Dotenv({
path: './.env.production'
})
]
};
.env.developmentAPI_URL=http://localhost:3000
DEBUG=true
.env.productionAPI_URL=https://api.site.com
DEBUG=false
const Dotenv = require('dotenv-webpack');
module.exports = (env, argv) => {
const isProd = argv.mode === 'production';
return {
plugins: [
new Dotenv({
path: isProd
? './.env.production'
: './.env.development'
})
]
};
};
Можно использовать файл .env.example.
.env.exampleAPI_URL=https://default.api
DEBUG=false
new Dotenv({
safe: true
})
Режим safe проверяет наличие всех обязательных
переменных.
Если какая-либо переменная отсутствует — сборка завершится ошибкой.
По умолчанию dotenv-webpack читает
.env.
Для подключения системных переменных используется:
new Dotenv({
systemvars: true
})
Linux/macOS:
API_URL=https://prod.api npm run build
Windows CMD:
set API_URL=https://prod.api && npm run build
При использовании systemvars: true:
.env;new Dotenv({
allowEmptyValues: true
})
Позволяет использовать пустые значения:
API_KEY=
function App() {
return (
<div>
{process.env.API_URL}
</div>
);
}
После сборки строка будет встроена в bundle.
export default {
mounted() {
console.log(process.env.API_URL);
}
}
Angular чаще использует собственную систему environments, однако
Webpack-конфигурации также могут применять
dotenv-webpack.
Webpack умеет удалять неиспользуемый код.
Пример:
if (process.env.NODE_ENV === 'development') {
console.log('debug');
}
После production-сборки:
if ('production' === 'development') {
console.log('debug');
}
Минификатор удалит недостижимую ветку.
.env часто используется для feature flags.
.envENABLE_CHAT=true
ENABLE_ADMIN=false
if (process.env.ENABLE_CHAT === 'true') {
initChat();
}
.envAPI_URL=https://api.site.com
export const api = {
baseURL: process.env.API_URL
};
import axios from 'axios';
export default axios.create({
baseURL: process.env.API_URL
});
fetch(`${process.env.API_URL}/users`)
NODE_ENVWebpack автоматически поддерживает mode.
webpack --mode production
Однако .env может дополнять поведение:
NODE_ENV=production
mode от
.envmodeУправляет внутренними оптимизациями Webpack:
.envУправляет пользовательскими переменными проекта.
.env.localФайл:
.env.local
обычно:
.gitignore..gitignore.env.local
.env.production.local
expandНекоторые проекты используют переменные внутри переменных.
.envHOST=localhost
PORT=3000
API_URL=http://${HOST}:${PORT}
new Dotenv({
expand: true
})
process.env.API_URL
будет заменён на:
"http://localhost:3000"
defaultsПодключение файла со значениями по умолчанию:
new Dotenv({
defaults: true
})
.env.defaultsAPI_URL=https://default.api
DEBUG=false
silentnew Dotenv({
silent: true
})
Отключает вывод предупреждений.
if (!process.env.API_URL) {
throw new Error('API_URL not found');
}
export const config = {
apiUrl: process.env.API_URL,
debug: process.env.DEBUG === 'true'
};
Все значения из .env являются строками.
.envPORT=3000
DEBUG=true
const port = Number(process.env.PORT);
const debug = process.env.DEBUG === 'true';
new Dotenv()Ошибка:
process is not defined
или:
undefined
new Dotenv({
path: './config/.env'
})
Webpack Dev Server может не подхватить изменения .env
автоматически.
Часто требуется перезапуск:
npm run dev
Нельзя:
DB_PASSWORD=123456
JWT_SECRET=secret
во frontend-проекте.
Преимущества:
.env;Требует ручного описания:
new webpack.DefinePlugin({
'process.env.API_URL': JSON.stringify('https://api.com')
})
dotenv-webpack автоматизирует работу с
.env, а DefinePlugin является низкоуровневым
механизмом замены констант.
Установка:
npm install cross-env --save-dev
{
"scripts": {
"build": "cross-env NODE_ENV=production webpack"
}
}
Windows и Unix-системы имеют разные способы задания переменных окружения.
cross-env делает команды кроссплатформенными.
В monorepo .env может располагаться:
root/
├── packages/
│ ├── app/
│ └── admin/
└── .env
new Dotenv({
path: '../. ./.env'
})
dotenv-webpack практически не влияет на скорость сборки,
поскольку:
.env файлы имеют маленький размер;В production .env часто не хранится в репозитории.
Переменные передаются:
env:
API_URL: https://api.production.com
.env.exampleAPI_URL=
DEBUG=
.env
.env.local
.env.development
.env.staging
.env.production
export const ENV = {
API_URL: process.env.API_URL
};
Крупные проекты обычно используют:
src/
├── config/
│ ├── env.js
│ ├── api.js
│ └── featureFlags.js
export const env = {
mode: process.env.NODE_ENV,
apiUrl: process.env.API_URL,
analytics: process.env.ENABLE_ANALYTICS === 'true'
};
const required = [
'API_URL',
'NODE_ENV'
];
required.forEach(key => {
if (!process.env[key]) {
throw new Error(`${key} is required`);
}
});
Иногда .env недостаточно.
Например:
В таких случаях используют:
.env ориентирован прежде всего на compile-time
конфигурацию.