Разграничение конфигов: .env.development, .env.production

В процессе разработки приложения окружение постоянно меняется. Локальная разработка, тестовый сервер и production используют разные API, базы данных, режимы логирования, флаги оптимизации и ключи доступа. Хранение всех переменных в одном файле быстро приводит к ошибкам и путанице.

Webpack позволяет организовать разделение окружений через несколько .env-файлов:

.env
.env.development
.env.production
.env.local
.env.test

Наиболее распространённая схема:

.env.development
.env.production

Такой подход позволяет автоматически подставлять нужные значения в зависимости от режима сборки.


Проблемы единого .env

Один общий файл окружения создаёт множество рисков:

  • production API случайно используется в development;
  • тестовые ключи попадают в production;
  • отключается минификация;
  • включается debug-режим на боевом сервере;
  • разработчики перезаписывают настройки друг друга;
  • CI/CD использует неправильные значения.

Пример проблемного файла:

API_URL=https://api.production.com
DEBUG=true
ENABLE_DEVTOOLS=true

При запуске локальной сборки приложение уже работает с production API.


Типичная структура env-файлов

.env.development

Используется во время локальной разработки.

NODE_ENV=development

API_URL=http://localhost:3000/api
DEBUG=true
ENABLE_DEVTOOLS=true
ENABLE_LOGS=true

Особенности:

  • подробные логи;
  • source maps;
  • локальные API;
  • отключённая минификация;
  • инструменты разработчика.

.env.production

Используется при production-сборке.

NODE_ENV=production

API_URL=https://api.site.com
DEBUG=false
ENABLE_DEVTOOLS=false
ENABLE_LOGS=false

Особенности:

  • production API;
  • минимизация кода;
  • отключённые debug-инструменты;
  • повышенная производительность.

Установка dotenv-webpack

Для загрузки env-переменных часто используется плагин dotenv-webpack.

Установка:

npm install dotenv-webpack --save-dev

Либо:

yarn add dotenv-webpack -D

Базовое подключение dotenv-webpack

webpack.config.js

const Dotenv = require('dotenv-webpack');

module.exports = {
    plugins: [
        new Dotenv()
    ]
};

По умолчанию плагин ищет файл:

.env

Использование переменных в коде

После подключения плагина переменные доступны через process.env.

Пример

console.log(process.env.API_URL);

Webpack заменяет значения ещё на этапе сборки.

Результат:

console.log("https://api.site.com");

Это важно понимать: в браузере объекта process.env не существует. Webpack делает compile-time подстановку.


Выбор env-файла по mode

Наиболее правильный подход — динамически выбирать env-файл в зависимости от режима сборки.


Конфигурация через функцию

webpack.config.js

const Dotenv = require('dotenv-webpack');

module.exports = (env, argv) => {

    const isProduction = argv.mode === 'production';

    return {
        mode: isProduction ? 'production' : 'development',

        plugins: [
            new Dotenv({
                path: isProduction
                    ? './.env.production'
                    : './.env.development'
            })
        ]
    };
};

Сборка development

npx webpack --mode development

Webpack загрузит:

.env.development

Сборка production

npx webpack --mode production

Webpack загрузит:

.env.production

Использование нескольких webpack-конфигов

Крупные проекты часто используют отдельные конфиги:

webpack.common.js
webpack.dev.js
webpack.prod.js

webpack.dev.js

const { merge } = require('webpack-merge');
const common = require('./webpack.common');
const Dotenv = require('dotenv-webpack');

module.exports = merge(common, {

    mode: 'development',

    plugins: [
        new Dotenv({
            path: './.env.development'
        })
    ]
});

webpack.prod.js

const { merge } = require('webpack-merge');
const common = require('./webpack.common');
const Dotenv = require('dotenv-webpack');

module.exports = merge(common, {

    mode: 'production',

    plugins: [
        new Dotenv({
            path: './.env.production'
        })
    ]
});

Скрипты package.json

{
    "scripts": {
        "dev": "webpack serve --config webpack.dev.js",
        "build": "webpack --config webpack.prod.js"
    }
}

Общий .env

Иногда создаётся общий файл:

.env

Он содержит значения для всех окружений.

Пример

APP_NAME=My Application
APP_VERSION=1.0.0

Переопределение значений

.env.production может переопределять общий .env.

.env

API_URL=http://localhost

.env.production

API_URL=https://api.production.com

Production-файл заменит базовое значение.


Загрузка нескольких env-файлов

Иногда требуется комбинирование:

new Dotenv({
    path: './.env.production'
})

Но dotenv-webpack не умеет автоматически объединять множество env-файлов как это делает Vite или Create React App.

Для сложных схем используется библиотека dotenv.


Использование dotenv напрямую

Установка

npm install dotenv --save-dev

Загрузка env вручную

webpack.config.js

const dotenv = require('dotenv');

dotenv.config({
    path: './.env.production'
});

module.exports = {
    mode: 'production'
};

Комбинация dotenv + DefinePlugin

Часто используется вместе с DefinePlugin.

const webpack = require('webpack');
const dotenv = require('dotenv');

const env = dotenv.config({
    path: './.env.production'
}).parsed;

module.exports = {
    plugins: [
        new webpack.DefinePlugin({
            'process.env.API_URL': JSON.stringify(env.API_URL),
            'process.env.DEBUG': JSON.stringify(env.DEBUG)
        })
    ]
};

Почему значения нужно сериализовать

DefinePlugin выполняет текстовую замену.

Неправильно:

'process.env.API_URL': env.API_URL

Результат:

process.env.API_URL = https://api.com

Это синтаксическая ошибка.

Правильно:

JSON.stringify(env.API_URL)

Результат:

"https://api.com"

Использование process.env.NODE_ENV

Webpack активно использует:

process.env.NODE_ENV

Многие библиотеки меняют поведение автоматически.

Пример:

if (process.env.NODE_ENV === 'development') {
    console.log('debug');
}

В production Webpack способен удалить этот код через dead code elimination.


Оптимизация production-сборки

Production env-файлы часто управляют:

  • tree shaking;
  • минификацией;
  • source maps;
  • analytics;
  • feature flags;
  • lazy loading;
  • debug-функциями.

Пример feature flags

.env.development

ENABLE_EXPERIMENTAL_UI=true

.env.production

ENABLE_EXPERIMENTAL_UI=false

Использование

if (process.env.ENABLE_EXPERIMENTAL_UI === 'true') {
    renderExperimentalUI();
}

Опасность хранения секретов

Webpack встраивает env-переменные в итоговый bundle.

Это означает:

SECRET_KEY=123456

после сборки окажется внутри JS-файла.

Нельзя хранить в frontend:

  • пароли;
  • private keys;
  • токены администратора;
  • секреты БД;
  • внутренние API-ключи.

Что допустимо хранить

Допустимо:

API_URL=https://api.site.com
SENTRY_DSN=...
APP_VERSION=1.0.0

Недопустимо:

DB_PASSWORD=...
JWT_SECRET=...
AWS_SECRET_ACCESS_KEY=...

.env.local

Часто используется файл:

.env.local

Он не коммитится в Git.


Пример .gitignore

.env.local
.env.*.local

Назначение .env.local

Файл содержит:

  • локальные настройки разработчика;
  • персональные API;
  • временные значения;
  • machine-specific конфигурацию.

Порядок приоритетов

Обычно применяется следующая схема:

  1. .env
  2. .env.development
  3. .env.local
  4. .env.development.local

Чем файл специфичнее — тем выше приоритет.


Передача env через CLI

Webpack позволяет передавать переменные напрямую.

Linux/macOS

API_URL=https://dev.api webpack

Windows CMD

set API_URL=https://dev.api && webpack

Использование cross-env

Для кроссплатформенности применяется cross-env.

Установка

npm install cross-env --save-dev

package.json

{
    "scripts": {
        "build": "cross-env NODE_ENV=production webpack"
    }
}

Разделение логики по окружению

Пример

const isDev = process.env.NODE_ENV === 'development';

if (isDev) {
    enableDevTools();
}

Conditional imports

Webpack позволяет исключать модули из production.

if (process.env.NODE_ENV === 'development') {
    require('./debug-panel');
}

В production-сборке модуль может быть полностью удалён.


Source maps для development

webpack.dev.js

module.exports = {
    devtool: 'eval-source-map'
};

Source maps для production

webpack.prod.js

module.exports = {
    devtool: false
};

Либо:

devtool: 'source-map'

если требуется production debugging.


Проверка текущего окружения

Пример

console.log(process.env.NODE_ENV);
console.log(process.env.API_URL);

Runtime и compile-time переменные

Важно различать два типа env:

Compile-time

Подставляются Webpack во время сборки.

process.env.API_URL

Runtime

Получаются во время работы приложения.

Например:

window.__CONFIG__

или запросом к серверу.


Ограничения compile-time env

После production-сборки изменить значения нельзя без нового build.

Если bundle содержит:

"https://api.site.com"

то переключить API уже невозможно.


Runtime-конфигурация

Иногда используется отдельный конфигурационный файл.

config.json

{
    "apiUrl": "https://api.site.com"
}

Приложение загружает его динамически.

Это позволяет:

  • менять API без пересборки;
  • конфигурировать Docker-контейнеры;
  • использовать один build для разных серверов.

Использование EnvironmentPlugin

Webpack имеет встроенный плагин:

webpack.EnvironmentPlugin

Пример

const webpack = require('webpack');

module.exports = {
    plugins: [
        new webpack.EnvironmentPlugin([
            'NODE_ENV',
            'API_URL'
        ])
    ]
};

Плагин читает переменные из текущего окружения Node.js.


Значения по умолчанию

new webpack.EnvironmentPlugin({
    NODE_ENV: 'development',
    DEBUG: false
})

Отличие dotenv-webpack от EnvironmentPlugin

dotenv-webpack

  • читает .env файлы;
  • автоматически подставляет значения;
  • удобен для frontend.

EnvironmentPlugin

  • работает с process.env;
  • не читает .env;
  • подходит для CI/CD.

Использование в CI/CD

Production env-переменные часто задаются через:

  • GitHub Actions;
  • GitLab CI;
  • Jenkins;
  • Docker;
  • Kubernetes.

Пример GitHub Actions

env:
  NODE_ENV: production
  API_URL: https://api.production.com

Docker и env

Dockerfile

ENV NODE_ENV=production

docker-compose.yml

environment:
  NODE_ENV: production
  API_URL: https://api.site.com

Проверка env в bundle

После сборки можно увидеть:

"https://api.site.com"

внутри итогового JS.

Это нормальное поведение compile-time env.


Частые ошибки

Ошибка: переменная undefined

Причины:

  • env-файл не найден;
  • неправильный path;
  • отсутствует перезапуск dev server;
  • переменная не объявлена.

Ошибка: process is not defined

Причина:

Webpack не заменил env-переменные.

Обычно:

  • отсутствует DefinePlugin;
  • не подключён dotenv-webpack.

Перезапуск dev server

После изменения .env требуется перезапуск:

npm run dev

Webpack Dev Server не всегда отслеживает env-файлы автоматически.


Проверка пути к env

Неправильно:

path: '.env.production'

при запуске из другой директории.

Надёжнее:

const path = require('path');

path: path.resolve(__dirname, '.env.production')

Рекомендуемая структура проекта

project/
│
├── src/
├── dist/
├── .env
├── .env.development
├── .env.production
├── .env.local
├── webpack.common.js
├── webpack.dev.js
├── webpack.prod.js
└── package.json