NODE_ENV и его влияние на сборку

NODE_ENV — одна из ключевых переменных окружения в экосистеме JavaScript и Node.js. В контексте Webpack она используется для переключения поведения сборки между различными режимами работы приложения.

Наиболее распространённые значения:

NODE_ENV=development
NODE_ENV=production
NODE_ENV=test

Webpack, плагины, загрузчики, Babel, React, Vue и множество других инструментов ориентируются на значение этой переменной для изменения логики работы.


Что такое переменные окружения

Переменные окружения представляют собой внешние параметры процесса, передаваемые операционной системой.

Пример:

NODE_ENV=production webpack

Во время запуска процесса Node.js значение становится доступным через объект:

process.env.NODE_ENV

Пример:

console.log(process.env.NODE_ENV);

Результат:

production

Основное назначение NODE_ENV

Главная задача NODE_ENV — разделение среды выполнения приложения.

Обычно используются два режима:

Режим Назначение
development разработка
production production-сборка
test тестирование

Разные режимы требуют различного поведения:

Development

Для разработки важны:

  • высокая скорость сборки;
  • source maps;
  • подробные ошибки;
  • hot reload;
  • отсутствие минификации;
  • удобство отладки.

Production

Для production-сборки важны:

  • минимальный размер файлов;
  • tree shaking;
  • минификация;
  • удаление debug-кода;
  • оптимизация производительности;
  • кэширование.

Связь NODE_ENV и mode в Webpack

Webpack имеет собственную настройку:

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

или:

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

Важно понимать различие:

Параметр Назначение
mode режим работы Webpack
NODE_ENV глобальная переменная окружения

Что делает mode

Webpack автоматически включает набор внутренних оптимизаций.

mode: development

Автоматически активируются:

  • удобные имена модулей;
  • быстрые сборки;
  • отключённая минификация;
  • улучшенные source maps.

mode: production

Автоматически включаются:

  • Terser;
  • tree shaking;
  • scope hoisting;
  • минификация;
  • оптимизация runtime;
  • удаление dead code.

Автоматическая установка process.env.NODE_ENV

Webpack автоматически подставляет значение process.env.NODE_ENV, если используется mode.

Пример:

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

Webpack фактически выполняет замену:

process.env.NODE_ENV

на:

'production'

Как работает подмена значений

Webpack не использует настоящие переменные окружения внутри браузерного JavaScript.

Во время сборки происходит статическая замена кода.

Исходный код:

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

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

if ('production' === 'development') {
    console.log('debug');
}

Минификатор видит невозможное условие и удаляет код полностью.


Удаление debug-кода

Одна из важнейших причин использования NODE_ENV — исключение отладочной логики из production.

Пример:

if (process.env.NODE_ENV !== 'production') {
    console.log('Application started');
}

В production:

if ('production' !== 'production') {
    console.log('Application started');
}

После минификации:

// код удалён

Влияние на размер bundle

Использование NODE_ENV=production значительно уменьшает размер итоговой сборки.

Причины:

  • удаление неиспользуемого кода;
  • tree shaking;
  • минификация;
  • удаление development-проверок;
  • исключение warning-сообщений.

Особенно сильно это влияет на React.


NODE_ENV и React

React содержит две версии внутренней логики:

  • development;
  • production.

Пример:

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

В development React включает:

  • проверки prop types;
  • предупреждения;
  • диагностические сообщения;
  • дополнительные проверки хуков.

В production всё это удаляется.

Разница в размере может достигать сотен килобайт.


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

Для передачи NODE_ENV используется DefinePlugin.

Пример:

const webpack = require('webpack');

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

Почему нужен JSON.stringify

Неправильный вариант:

'process.env.NODE_ENV': 'production'

Webpack подставит:

production

без кавычек.

Это приведёт к ошибке:

if (production === 'development')

Правильный вариант:

'process.env.NODE_ENV': JSON.stringify('production')

Результат:

if ('production' === 'development')

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

Webpack предоставляет сокращённую форму:

const webpack = require('webpack');

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

Отличие DefinePlugin и EnvironmentPlugin

DefinePlugin

Полностью вручную определяет значения:

new webpack.DefinePlugin({
    VERSION: JSON.stringify('1.0')
})

EnvironmentPlugin

Берёт значения из реального окружения Node.js:

NODE_ENV=production webpack

и автоматически передаёт их в сборку.


Передача NODE_ENV через npm scripts

Пример:

{
    "scripts": {
        "dev": "NODE_ENV=development webpack serve",
        "build": "NODE_ENV=production webpack"
    }
}

Проблема Windows

На Windows такой синтаксис не работает:

NODE_ENV=production webpack

Используется пакет cross-env.

Установка:

npm install cross-env --save-dev

Пример:

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

Использование NODE_ENV внутри webpack.config.js

Переменная доступна во время выполнения конфигурации.

Пример:

const isProd = process.env.NODE_ENV === 'production';

module.exports = {
    mode: isProd ? 'production' : 'development'
};

Условная настройка конфигурации

Source maps

module.exports = {
    devtool: isProd
        ? false
        : 'source-map'
};

Минификация

optimization: {
    minimize: isProd
}

Имена файлов

output: {
    filename: isProd
        ? '[name].[contenthash].js'
        : '[name].js'
}

Плагины

plugins: [
    !isProd && new webpack.HotModuleReplacementPlugin()
].filter(Boolean)

Создание единой конфигурации

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

const path = require('path');

const isProd = process.env.NODE_ENV === 'production';

module.exports = {
    mode: isProd ? 'production' : 'development',

    entry: './src/index.js',

    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: isProd
            ? '[name].[contenthash].js'
            : '[name].js'
    },

    devtool: isProd
        ? false
        : 'eval-source-map'
};

Разделение конфигураций

Другой популярный подход:

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

webpack.dev.js

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

webpack.prod.js

module.exports = {
    mode: 'production',
    optimization: {
        minimize: true
    }
};

NODE_ENV и tree shaking

Tree shaking работает значительно эффективнее в production-режиме.

Webpack анализирует:

  • ES Modules;
  • статические импорты;
  • условные ветки.

Пример:

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

В production код становится недостижимым и удаляется.


Dead Code Elimination

Dead code elimination — удаление кода, который никогда не выполнится.

Пример:

const DEV = process.env.NODE_ENV !== 'production';

if (DEV) {
    console.log('debug');
}

После замены:

const DEV = false;

if (false) {
    console.log('debug');
}

Terser удаляет этот блок.


Оптимизация библиотек

Многие библиотеки имеют условную логику:

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

Без правильного NODE_ENV:

  • bundle становится больше;
  • остаются проверки;
  • снижается производительность.

Опасность неправильной конфигурации

Распространённая ошибка:

mode: 'production'

но:

process.env.NODE_ENV === 'development'

или наоборот.

Это создаёт рассинхронизацию между:

  • Webpack;
  • Babel;
  • React;
  • сторонними библиотеками.

Правильная синхронизация

Рекомендуется использовать единое значение:

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

module.exports = {
    mode: NODE_ENV
};

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

Пакет:

npm install dotenv --save-dev

Позволяет загружать переменные из .env.


Файл .env

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

Подключение

require('dotenv').config();

Особенность браузерного JavaScript

В браузере объекта process.env не существует.

Webpack имитирует его через подстановку значений.

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

console.log(process.env);

не выводит реальные переменные окружения ОС.


Проверка режима во время сборки

Пример:

console.log(process.env.NODE_ENV);

в webpack.config.js показывает реальное значение окружения Node.js.

Но внутри клиентского кода:

console.log(process.env.NODE_ENV);

используется уже подставленное значение Webpack.


Production-оптимизации Webpack

При:

mode: 'production'

Webpack автоматически включает:

Оптимизация Назначение
TerserPlugin минификация
ModuleConcatenationPlugin scope hoisting
tree shaking удаление unused exports
deterministic ids стабильный cache
sideEffects analysis удаление лишнего кода

Влияние на производительность

Production-сборка:

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

Development-сборка:

  • легче отлаживается;
  • содержит подробную информацию;
  • генерирует source maps;
  • сохраняет читаемый код.

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

Пример:

console.log(process.env.NODE_ENV);

Использование собственных флагов

Помимо NODE_ENV можно определять собственные переменные:

new webpack.DefinePlugin({
    __DEV__: JSON.stringify(true),
    __API__: JSON.stringify('https://api.site.com')
})

Антипаттерн: использование runtime-проверок

Плохо:

if (window.location.hostname === 'localhost') {
    enableDebug();
}

Лучше:

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

Во втором случае debug-код может быть полностью удалён из production bundle.


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

Babel также ориентируется на NODE_ENV.

Пример:

presets: [
    ['@babel/preset-env']
]

Некоторые плагины автоматически отключаются в production.


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

Пример:

module.exports = {
    plugins: [
        process.env.NODE_ENV === 'production'
            ? require('cssnano')
            : false
    ].filter(Boolean)
};

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

Vue также разделяет development и production-режимы.

Development включает:

  • предупреждения;
  • проверки;
  • devtools hooks.

Production удаляет эти части.


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

Angular CLI использует environment-конфигурации:

ng build --configuration production

Внутри также применяется production-режим и оптимизация сборки.


SSR и NODE_ENV

На сервере:

process.env.NODE_ENV

является реальной переменной окружения Node.js.

На клиенте — результатом подстановки Webpack.

Эти механизмы необходимо различать.


Типичная production-конфигурация

const webpack = require('webpack');

module.exports = {
    mode: 'production',

    plugins: [
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify('production')
        })
    ],

    optimization: {
        minimize: true
    }
};

Типичная development-конфигурация

const webpack = require('webpack');

module.exports = {
    mode: 'development',

    devtool: 'eval-source-map',

    plugins: [
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify('development')
        })
    ]
};

Главные эффекты NODE_ENV

Эффект Результат
Удаление debug-кода меньше bundle
Включение production-режима библиотек выше производительность
Tree shaking удаление unused modules
Минификация уменьшение размера
Отключение warning ускорение runtime
Оптимизация React/Vue меньше памяти и CPU

Наиболее распространённые ошибки

Отсутствие production-режима

mode: 'development'

в production-сборке.


Ручная подмена без JSON.stringify

new DefinePlugin({
    NODE_ENV: 'production'
})

Несоответствие mode и NODE_ENV

mode: 'production'

но:

NODE_ENV=development

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

В браузере:

console.log(process.env.NODE_ENV);

может привести к ошибкам, если подстановка не настроена.


Рекомендуемая схема

{
    "scripts": {
        "dev": "cross-env NODE_ENV=development webpack serve",
        "build": "cross-env NODE_ENV=production webpack"
    }
}
const isProd = process.env.NODE_ENV === 'production';

module.exports = {
    mode: isProd ? 'production' : 'development'
};