EnvironmentPlugin: переменные окружения

Webpack предоставляет встроенный механизм работы с переменными окружения через EnvironmentPlugin, который позволяет передавать конфигурационные значения в сборку без необходимости жёстко прописывать их в коде приложения. Этот плагин является тонкой обёрткой над DefinePlugin и предназначен для упрощённого управления окружением, особенно в проектах, где требуется разделение конфигураций между development, staging и production.


EnvironmentPlugin подменяет значения переменных окружения во время сборки, используя process.env. При этом на этапе компиляции происходит статическая замена значений, что позволяет полностью исключить runtime-зависимость от process.env в браузерном коде.

Ключевой механизм:

  • значения читаются из process.env
  • подставляются в исходный код во время компиляции
  • отсутствующие переменные могут приводить к ошибке или получать fallback-значение

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

Минимальная конфигурация плагина включает перечисление переменных, которые должны быть доступны в приложении:

const webpack = require('webpack');

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

В этом случае Webpack ожидает, что process.env.NODE_ENV и process.env.API_URL будут определены в окружении сборки.

Если переменная отсутствует, её значение в коде станет undefined.


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

Переменные берутся из реального окружения процесса Node.js, в котором запускается сборка.

Пример запуска:

NODE_ENV=production API_URL=https://api.example.com webpack

Внутри кода:

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

После сборки код фактически преобразуется в:

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

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

EnvironmentPlugin поддерживает задание fallback-значений, что делает конфигурацию более устойчивой к отсутствию переменных.

new webpack.EnvironmentPlugin({
  NODE_ENV: 'development',
  API_URL: 'http://localhost:3000'
})

В этом варианте:

  • если NODE_ENV не задан, используется development
  • если API_URL отсутствует, применяется http://localhost:3000

Поведение при отсутствии переменных

Существует два режима работы:

Строгий режим

Если передан массив переменных:

new webpack.EnvironmentPlugin(['API_URL'])

и переменная не определена, результатом будет undefined, что может привести к ошибкам в логике приложения.


Режим с дефолтами

Если передан объект:

new webpack.EnvironmentPlugin({
  API_URL: 'http://localhost'
})

то используется указанное значение по умолчанию.


Внутреннее преобразование

EnvironmentPlugin по сути генерирует вызовы DefinePlugin:

new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
  'process.env.API_URL': JSON.stringify(process.env.API_URL)
});

Это означает, что подстановка происходит на уровне AST, а не во время выполнения.


Особенности работы в браузере

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

  • process.env не существует как объект среды выполнения
  • все обращения заменяются строковыми литералами
  • попытка динамически изменить значение невозможна

Пример:

if (process.env.NODE_ENV === 'production') {
  enableOptimizations();
}

После сборки:

if ("production" === "production") {
  enableOptimizations();
}

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

При работе с TypeScript требуется дополнительное объявление типов, иначе компилятор будет ругаться на отсутствие process.env.

declare const process: {
  env: {
    NODE_ENV: string;
    API_URL: string;
  };
};

Это не влияет на Webpack, но обеспечивает корректную проверку типов на этапе компиляции TypeScript.


Сценарии применения

Разделение окружений

Чаще всего используется для управления режимами работы приложения:

  • development
  • production
  • test
if (process.env.NODE_ENV !== 'production') {
  console.log('debug mode');
}

Конфигурация API

fetch(`${process.env.API_URL}/users`);

Позволяет переключать backend без изменения исходного кода.


Feature flags

if (process.env.ENABLE_BETA_FEATURES === 'true') {
  enableBeta();
}

Важные ограничения

Отсутствие динамики

Переменные не могут изменяться во время выполнения приложения, так как подставляются на этапе сборки.


Статическая природа

Любая конструкция:

process.env.SOME_VAR + suffix

превращается в константное выражение только частично, что может ограничивать оптимизации.


Не предназначен для секретов

Все значения попадают в клиентский бандл:

  • API ключи
  • токены
  • приватные данные

могут быть извлечены из итогового JS-кода.


Связь с DefinePlugin

EnvironmentPlugin является надстройкой над DefinePlugin, поэтому все особенности последнего сохраняются:

  • строгая статическая замена
  • отсутствие runtime-логики
  • необходимость JSON-строкирования значений

Пример комплексной конфигурации

const webpack = require('webpack');

module.exports = {
  mode: 'production',
  plugins: [
    new webpack.EnvironmentPlugin({
      NODE_ENV: 'production',
      API_URL: 'https://api.example.com',
      ENABLE_LOGS: false
    })
  ]
};

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

const apiUrl = process.env.API_URL;

if (process.env.ENABLE_LOGS === 'true') {
  console.log('logs enabled');
}

Взаимодействие с dotenv

Часто EnvironmentPlugin используется совместно с dotenv:

require('dotenv').config();

module.exports = {
  plugins: [
    new webpack.EnvironmentPlugin(Object.keys(process.env))
  ]
};

dotenv загружает переменные из .env, а EnvironmentPlugin внедряет их в сборку.


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

Webpack способен удалять мёртвый код благодаря подстановке значений:

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

После сборки production:

if ("production" === "development") {
  debug();
}

Условие всегда ложно, и код может быть удалён Terser’ом.


Практические ограничения архитектуры

Использование EnvironmentPlugin влияет на архитектуру приложения:

  • конфигурация становится частью сборки
  • требуется пересборка при изменении переменных
  • отсутствует возможность hot-switch окружения без redeploy

Отличие от runtime-конфигурации

В отличие от загрузки конфигурации через HTTP или JSON:

  • значения фиксируются на этапе билда
  • отсутствует задержка на загрузку
  • повышается предсказуемость поведения приложения

Поведение при SSR (Node.js)

В серверных приложениях на Node.js:

  • process.env остаётся реальным объектом окружения
  • Webpack заменяет только клиентскую часть
  • серверный код может использовать переменные напрямую без подстановки

Это создаёт необходимость различать server-side и client-side конфигурации.


Ошибки конфигурации

Типичные проблемы:

  • забытые переменные → undefined
  • несоответствие типов (строка vs boolean)
  • различие окружений между локальной и CI-сборкой
  • утечка чувствительных данных в бандл

Поведение при минификации

После работы Webpack и Terser:

  • выражения упрощаются
  • условные блоки удаляются
  • конечный бандл содержит только литералы

Это усиливает роль EnvironmentPlugin как инструмента compile-time конфигурации.