Как транспилировать ES модули с помощью Webpack

месяц назад·10 мин. на чтение

В экосистеме JavaScript webpack выделяется как основной инструмент для объединения нашего кода. Но прежде чем мы углубимся в то, как webpack это делает, разберемся, что такое ES-модули.

Что такое ES-модули?

ES модули (ESM) являются рекомендуемым способом написания кода как для Node.js, так и для браузера. В настоящее время модули ES поддерживаются всеми основными браузерами и представляют собой официальный стандартный формат для упаковки и повторного использования кода JavaScript в Интернете. Мы можем определять и использовать пакеты в модульной системе с ключевыми словами export и import.

Импорт и экспорт функций

Ниже приведен пример импорта и экспорта функции с синтаксисом ES модулей:
// smallestNumber.js
export function smallestNumber(arr) {
    let smallestNumber = arr[0]
    let smallestIndex = 0
    for(let i = 1; i < arr.length; i++) {
       if(arr[i] < smallestNumber) {
         smallestNumber = arr[i]
         smallestIndex = i 
       }
    }
     return smallestNumber;
}
Обратите внимание, ключевое слово export полезно, поскольку оно позволяет нам сделать нашу функцию smallestNumber доступной для других модулей, которым необходимо ее вызвать. Помимо обычных функций, мы можем экспортировать константы, классы и даже просто переменные. Также обратите внимание, что у нас может быть экспорт по умолчанию с ESM.
// result.js
import { smallestNumber } from './smallestNumber.js';

console.log(smallestNumber([3,2,5,6,0,-1]))

// возвращает -1

Транспилирование кода

В конце концов, мы сосредоточимся на механике процесса транспиляции кода в следующем разделе, но перед этим давайте в первую очередь разберемся в важности этого процесса. Как мы уже упоминали ранее, старые версии JavaScript, такие как ES5, должны иметь возможность запускать наш новый код, а также понимать его синтаксис. Это означает, что должен быть способ полной обратной совместимости языка. Обратная совместимость является одним из наиболее важных аспектов, которые следует учитывать и расставлять приоритеты при добавлении новых функций в язык программирования. Для ESM среда выполнения Node.js может определить систему/формат модуля, который она должна использовать по умолчанию, на основе поля type, если оно установлено в module в package.json, который находится в корне проекта.
// package.json 
{
  "type": "module"
}
Приведенная выше настройка позволяет создавать все файлы, которые находятся на том же уровне структуры папок, что и package.json по умолчанию ESM. В качестве альтернативы мы можем установить тип commonjs, и среда выполнения Node заставит все файлы соответствовать системе Common JS Module по умолчанию. Хотя обычно это поведение по умолчанию, если поле type не указано. По сути, когда модули помечены как модули ECMAScript, среда выполнения Node использует другой шаблон или метод для разрешения импорта файлов. Например, импорт теперь более строгий, что означает, что мы должны добавить полное имя файла с их расширениями для относительных путей или запросов, в зависимости от обстоятельств.

Что такое транспиляция кода?

С введением новой версии JavaScript и изменениями в синтаксисе (также известным как ES2015), введением TypeScript, надмножества JavaScript и других достижений, таких как, например, CoffeeScript, написание JavaScript, который работает везде, уже не так просто, как раньше. Как мы, возможно, уже знаем, разные браузеры имеют разные движки JavaScript, и их различные уровни принятия и поддержки этих новых функций JS могут различаться, потому что они не соответствовали спецификации языка в одинаковое время. Это привело к тому, что код может работать в одном браузере и не работать в другом. Таким образом, суть транспиляции заключается в том, чтобы иметь возможность конвертировать новый синтаксис JS ES2015 в старый синтаксис ES5, чтобы код мог работать в старых браузерах. Например, шаблонные литералы или, оператор слияения null (??) и другие функции ES2015 или ES2015+ по-прежнему не имеют полной поддержки браузеров и серверной среды выполнения, поэтому нам может потребоваться транспилировать наш код для поддержки этих версий. В основном, инструменты, называемые загрузчиками (loaders), такие как Babel, Traceur и т. д., используются в сочетании с webpack для транспиляции кода. На высоком уровне транспиляторы работают на разных языках программирования, считывая исходный код строка за строкой и выдавая эквивалентный результат. Например, мы можем захотеть транспилировать кодовую базу TypeScript в старый добрый JavaScript. В целом, транспиляторы позволяют нам уверенно использовать новые, нестандартизированные возможности JavaScript. В настоящее время лучшим подходом к работе с модулями ES как в среде Node.js, так и в среде браузера является их транспилирование в формат модуля CommonJS с помощью Babel.

Что такое Webpack ?

Webpack — это инструмент сборки, который помогает объединить наш код и его зависимости в один файл JavaScript. Можно также сказать, что webpack — это своего рода сборщик статических модулей для JavaScript-приложений. Это связано с тем, что он применяет такие методы, как встряхивание дерева и компиляция (которая состоит из этапов транспиляции и минификации) к нашему исходному коду. Упаковщики, такие как webpack, работают рука об руку с транспиляторами. Это означает, что это совершенно разные, но дополняющие друг друга наборы инструментов. Поэтому нам нужно настроить webpack для работы с транспилятором — скажем, Babel. Как мы упоминали ранее, транспиляторы либо выполняют работу по компиляции одного языка в другой, либо делают язык обратно совместимым. Webpack довольно хорошо работает с Babel, а также легко настраивается. Например, мы можем настроить Babel для работы с webpack, создав конфигурационный файл webpack (webpack.config.js) использование плагина Babel — по сути, экосистема плагинов webpack — это то, что делает webpack тем, что есть. С другой стороны, Babel может быть сконфигурирован с помощью babel.config.js файл или .babelrc файл.

Почему webpack?

Как вы, возможно, знаете, webpack поддерживает несколько типов модулей из коробки, включая модули CommonJS и ES. Webpack также работает как на клиентском, так и на серверном JavaScript, поэтому с помощью webpack мы также можем легко работать с ресурсами, такими как изображения, шрифты, таблицы стилей и так далее. Он остается действительно мощным инструментом, поскольку автоматически строит и выводит граф зависимостей на основе импорта и экспорта файлов (поскольку, по сути, каждый файл является модулем). Сочетание этого с загрузчиками и плагинами делает webpack отличным инструментом в нашем арсенале. Более подробно о том, как это работает под капотом, можно прочитать в документации. Что касается плагинов, webpack также имеет богатую экосистему плагинов. Плагины поддерживают webpack в выполнении некоторой грязной работы, такой как оптимизация пакетов, управление ресурсами и так далее. Подводя итог, можно сказать, что комплектация с такими инструментами, как webpack, является самым быстрым способом работы или обеспечения обратной совместимости с модулями в наши дни, поскольку ESM постепенно набирает обороты в качестве официального стандарта для повторного использования кода в экосистеме. Webpack также поддерживает загрузчики, которые помогают ему решать, как обрабатывать, объединять и обрабатывать несобственные модули или файлы. Важно отметить, как webpack относится к загрузчикам. Загрузчики оцениваются и выполняются снизу вверх. Таким образом, последний загрузчик выполняется первым, и так далее, и тому подобное, именно в таком порядке. В этой статье мы сосредоточимся на том, как webpack транспилирует или обрабатывает модули ECMAScript.

Использование загрузчиков для транспиляции

Загрузчики преобразуют файлы из одного языка программирования в другой. Например, ts-loader может преобразовывать или транспилировать TypeScript в JavaScript. Обычно мы используем загрузчики в качестве зависимостей разработки. Например, давайте посмотрим, как мы можем использовать ts-loader. Для установки мы можем выполнить следующее:
npm install --save-dev ts-loader
Затем мы можем использовать этот загрузчик, чтобы указать webpack правильно обрабатывать все файлы TypeScript в нашем исходном коде. Ознакомьтесь с примером webpack.config.js файл ниже.
module.exports = {
  module: {
    rules: [
      { test: /.ts$/, use: 'ts-loader' },
    ],
  },
};
Здесь, как мы уже упоминали, мы говорим webpack обрабатывать все пути к файлам, оканчивающиеся на .ts и транспилировать их в синтаксис JavaScript, понятный браузеру и среде выполнения Node. Это означает, что загрузчики также могут работать в среде Node.js и, следовательно, также следовать стандарту разрешения модулей.

Соглашения об именовании загрузчиков

Общий способ именования загрузчиков является последовательным, так как загрузчики именуются своим именем и дефисом — обычно как xxx-loader. Например, babel-loader, ts-loader и так далее. Для нашего примера использования нас особенно интересует ESNext или babel-loader, загрузчик, созданный и поддерживаемый сообществом. Более подробную информацию о загрузчиках можно найти в документации webpack.

Некоторые основные понятия webpack

Чтобы понять, как работает webpack, в этом разделе рассматриваются некоторые высокоуровневые концепции, о которых должны знать читатели. Как мы упоминали ранее, webpack использует граф зависимостей, что означает, что он рекурсивно строит связь, включающую каждый модуль, который нужен приложению или от которого зависит, а затем объединяет все эти модули в выходной файл, готовый к использованию. Это означает, что webpack должен иметь точку входа — и это действительно так. При настройке конфигурации webpack точкой входа является начало проверок webpack пути к файлу, прежде чем он начнет строить внутренний граф зависимостей для нашего приложения. Обратите внимание, что webpack также поддерживает несколько точек входа для нашего приложения.
module.exports = {
  entry: ['./path/to/my/entry1/file1.js', './path/to/my/entry2/file2.js']
};
Чтобы добавить несколько точек входа, мы можем использовать массив. В webpack v5 теперь у нас может быть пустой объект входа. После того, как webpack построит граф зависимостей внутри и завершит процесс объединения, он должен вывести пакет в другой путь к файлу, который нам затем нужно обработать. Это то, что делает output. Он сообщает webpack, какой путь выдавать созданные пакеты и как называются файлы.
const path = require('path');

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js',
  },
};
Начиная с версии 4.0.0, webpack не требует конфигурационного файла для объединения проекта, хотя и предполагает, что вы определили точку входа в приложение с папкой и путем к файлу как src/index.js, и что вывод упакован в dist/main.js путь к папке, минимизированный, оптимизированный и готовый к продакшену.

Настройка webpack и Babel

В webpack была реализована полная поддержка использования встроенного синтаксиса модуля ES2015. Это означает, что мы можем использовать операторы import и export, не полагаясь на внешние инструменты транспиляции или зависимости, такие как Babel. Тем не менее, все же рекомендуется настроить Babel, на случай, если есть другие, более новые функции ES2015+, которые webpack еще не принял во внимание. В зависимости от форматов модулей webpack проверяет ближайший package.json и обеспечивает соблюдение соответствующих рекомендаций. При использовании webpack для объединения нашего кода обычно рекомендуется придерживаться синтаксиса одного модуля, чтобы позволить webpack правильно обрабатывать собранный вывод и, следовательно, предотвращать нежелательные ошибки. Чтобы начать, нам нужно убедиться, что на наших компьютерах установлен интерфейс командной строки webpack. Затем мы можем использовать команду CLI init, чтобы быстро запустить конфигурацию webpack в соответствии с требованиями нашего проекта. Мы можем сделать это, просто выполнив npx webpack-cli init и соответствующим образом отвечая на запросы. Теперь нам нужно скомпилировать наш код ES2015 в ES5, чтобы мы могли использовать его в различных средах браузера или средах выполнения. Для этого нам нужно установить Babel и все его зависимости, необходимые для webpack. Давайте установим следующее:
  • Babel Core
  • Babel Loader, webpack загрузчик, который взаимодействует с Babel Core
Для установки мы можем выполнить:
npm i webpack webpack-cli webpack-dev-server @babel/core @babel/preset-env babel-loader rimraf  -D
По окончанию установки наш package.json должно выглядеть следующим образом:
{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "A demo of webpack with babel",
  "main": "dist/bundle.js",
  "scripts": {
    "build": "node_modules/.bin/webpack --config webpack.config.js --mode=production",
    "watch": "node_modules/.bin/webpack --config webpack.config.js --mode=development -w",
    "prebuild:dev": "rimraf dist"
  },
  "author": "Name",
  "license": "MIT",
  "dependencies": {},
  "devDependencies": {
    "@babel/core": "^7.16.0",
    "@babel/preset-env": "^7.16.4",
    "babel-loader": "^8.2.3",
    "rimraf": "^3.0.2",
    "webpack": "^5.64.3",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.5.0"
  },
  "type": "module"
}
Обратите внимание, что наши зависимости закреплены, чтобы обеспечить согласованность при запуске нашего приложения в будущем. Пакет @babel/preset-env преобразует весь код ES2015–ES2020 в ES5 или практически в любую целевую среду, которую мы укажем в целевых параметрах. Обычно он нам нужен, когда мы намереваемся установить конкретную цель, которая затем позволяет Babel нацелиться на эту конкретную среду. Этот пакет в основном проверяет указанное целевое окружение по своему внутреннему отображению, а затем составляет список плагинов, которые он передает в Babel для транспиляции кода. Это приводит к уменьшению количества пакетов JavaScript. Поэтому, если мы не укажем target, размер выводимого кода будет больше, потому что по умолчанию плагины Babel группируют синтаксические функции ECMAScript в коллекцию связанных функций. Более того, с опцией меньшего размера пакета и большего прироста производительности, мы можем использовать babel/preset-modules, которые в конечном итоге будут объединены в ядро @babel/preset-env.

Настройка webpack.config.js

Теперь давайте перейдем к настройке нашего файла webpack. Создайте новый webpack.config.js в корне нашего проекта.
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));

export default  {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, './dist'),
        filename: 'bundle.js',
    },
    experiments: {
        outputModule: true,
    },
    plugins: [
       //empty pluggins array
    ],
    module: {
         // https://webpack.js.org/loaders/babel-loader/#root
        rules: [
            {
                test: /.m?js$/,
                loader: 'babel-loader',
                exclude: /node_modules/,
            }
        ],
    },
    devtool: 'source-map'
}
В приведенном выше конфиге мы установили для поля outputModule: true, что необходимо, если мы намерены использовать webpack для компиляции публичной библиотеки, предназначенной для использования другими. babel-loader загружает код ES2015+ и транспилирует его в ES5 с помощью Babel. Как вы также можете видеть в конфигурационном файле, у нас есть свойство module, которое имеет свойство rules, содержащее массив для настройки отдельных загрузчиков, которые могут нам понадобиться для нашей конфигурации webpack. Здесь мы добавили загрузчик webpack и установили необходимые параметры в соответствии с требованиями нашего проекта. Обратите внимание, что test представляет собой регулярное выражение, соответствующее абсолютному пути к каждому файлу и проверяющее наличие расширений файлов. В приведенном выше примере мы проверяем, заканчивается ли наш файл на .mjs или .js расширение.

Использование (и неиспользование) .babelrc файл

Теперь мы можем сконфигурировать Babel, создав .babelrc , также в корне нашего проекта. Содержимое файла показано ниже:
 {
    "presets": [
      [
        "@babel/preset-env",
        {
          "targets": {
            "esmodules": true
          }
        }
      ]
    ]
  }
В случае, если мы не хотим использовать .babelrc, мы также можем добавить пресеты в объект options внутри массива rules, например:
module: {
  rules: [
    {
      test: /.m?js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env', "es2015", "es2016"],
        }
      }
    }
  ]
}

Настройка пресетов и добавление плагинов

Нам нужно установить пресеты так, чтобы функции ES2015 в нашем коде могли быть преобразованы в ES5. В массиве options мы также можем добавить плагины, которые мы хотим добавить в нашу конфигурацию. Например, мы можем добавить эту plugins: ['@babel/plugin-transform-runtime'], которая устанавливает среду выполнения Babel, которая отключает автоматическое внедрение среды выполнения для каждого файла, чтобы предотвратить раздувание. Чтобы включить это в наш код, нам нужно установить их, выполнив:
npm install -D @babel/plugin-transform-runtime
Мы также должны добавить @babel/runtime в качестве зависимости, выполнив npm install @babel/runtime. Также обратите внимание, что в объекте rules мы указываем webpack исключить файлы в папке node_modules со свойством exclude. Это сделано для того, чтобы у нас был более быстрый процесс объединения, так как мы не хотим объединять нашу node_modules папку. Также бывают случаи, когда мы хотим, чтобы webpack обрабатывал модули ES 2015. Чтобы разрешить это, нам нужно использовать плагин ModuleConcatenationPlugin. Этот плагин включает некоторую форму поведения конкатенации в webpack, называемую поднятием области, что является возможной благодаря синтаксису ESM. По умолчанию этот плагин уже включен в рабочем режиме и отключен в противном случае.

Итоги

Чтобы избежать проблем с совместимостью, нам нужно транспилировать наш код из ES2015 в ES5. Здесь на помощь приходит webpack. Webpack объединяет наш код и выводит транспилированную версию в целевой файл, как указано в конфигурационном файле. В нашем конфигурационном файле webpack правила модуля позволяют нам указывать различные загрузчики, что является простым способом отображения загрузчиков. В этом посте мы использовали только загрузчик Babel, но есть много других загрузчиков, которые мы также можем использовать в экосистеме. Что касается улучшений и изменений в последнем выпуске webpack 5, теперь есть встроенная поддержка асинхронных модулей. Как следует из названия, асинхронные модули основаны на Promise и поэтому не разрешаются синхронно. Импорт асинхронных модулей через require() теперь также будет возвращать Promise, который разрешается в их экспорт.

Как использовать переменные среды в React

2 года назад·5 мин. на чтение

О различных способах доступа к переменным среды в React приложении

Если у вас нет опыта server side программирования, переменные среды могут показаться чем-то магическим. Этот недостаток знаний может поставить вас в тупик, когда вы закончите создавать приложения todo на localhost и попытаетесь создать продакшн сборку в первый раз. Если вы хотите узнать, как использовать переменные среды в ваших собственных инструментах, или глубоко погрузиться в то, как переменные среды работают в React, вы можете продолжить чтение этой статьи. Но если вы ищете быстрое решение и используете Create React App, ознакомьтесь с документацией здесь. Пользователи NextJS, ознакомьтесь с документацией здесь.

Проблема, которую мы решаем

Как объявить различные URL-адресов API для локальной разработки и для продакшн сборки.

Как решить эту проблему

Использовать переменные среды. При работе с React переменные среды — это переменные, доступные через глобальный объект process.env. Этот глобальный объект предоставляется вашей средой через NodeJS. И поскольку у нас нет NodeJS в браузере, нам понадобится webpack. В этой статье рассмотрим два способа установки и использования переменных среды для ваших React проектов с помощью webpack: с помощью скриптов npm и с помощью файла .env.

Способ 1: Использование скриптов npm для установки переменных среды

Во-первых, установите webpack и webpack-cli из npm:
npm install --save-dev webpack webpack-cli
Перейдите в файл package.json, проверьте поле scripts и найдите команды, которые запускают webpack. Вероятно, это будет выглядеть примерно так:
{
  // ...
  scripts: {
    "dev": "webpack --config webpack.config.dev.js",
    "build": "webpack --config webpack.config.build.js"
  }
}
Давайте добавим некоторые переменные окружения с флагом --env в scripts:
{
  // ...
  scripts: {
    "dev": "webpack --env.API_URL=http://localhost:8000 --config webpack.config.dev.js",
    "build": "webpack --env.API_URL=https://www.myapi.com --config webpack.config.build.js"
  }
}
Мы добавили --env.API_URL= часть в обоих скриптах. Теперь запустите команду npm run dev, перейдите к React компоненту и используйте process.env.API_URL:
const App = () => <h1>{process.env.API_URL}</h1>;
И тут проект должен сломаться.
Сломается он потому, что когда мы используем переменные окружения в клиентском коде, они на самом деле просто служат заполнителями, которые будут заменены при компиляции нашего кода. Проблема в том, что мы не сказали webpack скомпилировать эти переменные в реальные значения. Давайте сделаем это в нашем конфигурационном файле webpack с плагином DefinePlugin:
const webpack = require('webpack'); // DefinePlugin это часть webpack, поэтому это require обязателен

// возвращаем функцию из config файла
// переменная `env` будет просто объектом { API_URL: 'http://localhost:8000' }
// в ней будут содержаться все переменные среды, которые мы указали в package.json

module.exports = (env) => {
  // этот объект это сама конфигурация webpack
  return {
    plugins: [
      // добавим плагин в список плагинов
      new webpack.DefinePlugin({ `process.env.API_URL`: JSON.stringify(${env.API_URL}) })
    ]
  };
};
DefinePlugin требует, чтобы вы буквально определили свои «переменные среды». Вы также можете применить .reduce к переменным среды, чтобы получить объект:
module.exports = (env) => {
  // создаем объект из переменных среды
  const envKeys = Object.keys(env).reduce((prev, next) => {
    prev[`process.env.${next}`] = JSON.stringify(env[next]);
    return prev;
  }, {});

  return {
    plugins: [
      new webpack.DefinePlugin(envKeys)
    ]
  };
};
Если вы запустите команду сейчас, все скомпилируется, и ваш process.env.API_URL будет скомпилирован в правильный URL-адрес на основе переменной среды.

Способ 2: Использование файла .env для установки переменных среды

Вся идея здесь состоит в том, чтобы создать файл (называемый просто .env), заполненный переменными среды. Чтобы защитить пароли и другие значения переменных среды, добавьте файл .env в .gitignore. Фронтенд код будет ссылаться на одну и ту же переменную среды (process.env.API_URL) в обеих средах (при локальной разработке и на продакшене), но поскольку вы определили разные значения в своих .env, скомпилированные значения будут отличаться.

Создадим файл .env

Этот файл должен находиться в корневом каталоге проекта и называться .env. Добавим переменную:
API_URL=http://localhost:8000

Обработка файла .env

Теперь нам нужен какой-то способ обработки файлов и их содержимого. Для этого мы собираемся использовать популярный npm пакет под названием dotenv. Dotenv широко используется (create-react-app использует его). Он будет получать переменные из нашего файла .env и добавлять их в глобальный process.env.
$ npm install --save-dev dotenv

Добавление переменных в проект React

Есть одна проблема. Dotenv работает только на стороне сервера. А мы хотим использовать переменные среды на стороне клиента, на фронтенде. В данном случае мы разрабатываем клиентскую часть. И dotenv нужна какая-то среда для фактического хранения переменных. Здесь поможет Webpack. Воспользуемся плагином DefinePlugin в нашей webpack конфигурации:
const webpack = require('webpack');
const dotenv = require('dotenv');

module.exports = () => {
  // dotenv вернет объект с полем parsed 
  const env = dotenv.config().parsed;
  
  // сделаем reduce, чтобы сделать объект
  const envKeys = Object.keys(env).reduce((prev, next) => {
    prev[`process.env.${next}`] = JSON.stringify(env[next]);
    return prev;
  }, {});

  return {
    plugins: [
      new webpack.DefinePlugin(envKeys)
    ]
  };
};
При необходимости проверьте параметры конфигурации dotenv в документации на github. Вызов .config() в dotenv вернет объект со всеми переменными среды, установленными в вашем файле .env через поле parsed. Теперь давайте проверим наш React код:
const App = () => <h1>{process.env.API_URL}</h1>;
И это работает! Он показывает значение переменной среды API_URL, определенной в .env. Осталась только одна проблема: нам все еще нужно определить различные API_URL для локальной разработки и продакшена.

Различные переменные среды для разных сред

Вся идея состоит в том, чтобы создать разные файлы .env для разных сред и позволить webpack выбрать правильный файл .env в зависимости от активной среды. Поэтому создайте два файла в корневом каталоге проекта:
  • .env (содержит все переменные среды для продакшн)
  • .env.development (содержит все переменные среды для локальной разработки)
Чтобы было ясно: мы добавляем к имени файла .env сопоставление имени среды. Общепринятой практикой является использование исходного файла .env для продакшн сборки, поэтому мы не будем добавлять постфикс для продакшн .env .

Настройка активной среды с помощью scripts в package.json

Мы собираемся использовать scripts (как мы это делали в методе 1), чтобы установить текущую среду в нашем package.json:
{
  "scripts": {
    "dev": "webpack --env.ENVIRONMENT=development --config webpack.config.dev.js",
    "build": "webpack --env.ENVIRONMENT=production --config webpack.config.build.js"
  }
}
Так как мы определили нашу среду в нашем package.json, теперь она доступна в нашей конфигурации webpack. Следующим шагом будет переход к webpack конфигурации и дать ему использовать файл .env, принадлежащий активной среде. Как и раньше, мы используем dotenv, но теперь мы указываем пользовательский path в параметрах.
const webpack = require('webpack');
const dotenv = require('dotenv');
const fs = require('fs'); // для проверки существования файла
const path = require('path'); // для получения текущего пути

module.exports = (env) => {
  // получаем корневой путь (предполагаем, что webpack config лежит в корне проекта)
  const currentPath = path.join(__dirname);
  
  // путь по умолчанию (будет использован для продакшена - `.env`)
  const basePath = currentPath + '/.env';

  // склеиваем имя среды с именем файла для получения имени env файла
  const envPath = basePath + '.' + env.ENVIRONMENT;

  // проверяем существует ли env файл, если нет используем имя по умолчанию
  const finalPath = fs.existsSync(envPath) ? envPath : basePath;

  // устанавливаем параметр path в dotenv
  const fileEnv = dotenv.config({ path: finalPath }).parsed;
  
  // сделаем reduce, чтобы получить объект
  const envKeys = Object.keys(fileEnv).reduce((prev, next) => {
    prev[`process.env.${next}`] = JSON.stringify(fileEnv[next]);
    return prev;
  }, {});

  return {
    plugins: [
      new webpack.DefinePlugin(envKeys)
    ]
  };
Эта вся необходимая настройка, но вы можете создать больше .env файлов для большего количества сред (например, .env.staging) по аналогии.