Передача зависящих от среды переменных в веб-пакет

306

Я пытаюсь преобразовать угловое приложение из gulp в webpack. в gulp я использую gulp-preprocess для замены некоторых переменных на html-странице (например, имя базы данных) в зависимости от NODE_ENV. Каков наилучший способ достижения аналогичного результата с помощью веб-пакета?

КПГ
источник
1
Псевдоним работал на вас?
Юхо Вепсяляйнен
1
@bebraw: прежде чем я смог разобраться с псевдонимами, я реализовал другое решение, которое вы предложили, на основе DefinePlugin (). Теперь я понимаю, что псевдоним будет лучшим решением и, вероятно, когда-нибудь будет рефакторинг - спасибо. Если вы хотите включить два ответа в ответ, я с радостью приму это.
kpg
2
Был направлен сюда через консольное сообщение. Как это исправить в Browserify?
GN.
2
Этот вопрос пытается настроить SPA во время сборки или загрузки? Я отмечаю два типа конфигурации для SPA: 1) режим разработки или производства и 2) среда развертывания, например, разработка, подготовка, производство. Я думаю, что NODE_ENV можно использовать для настройки (1) во время сборки, но как мы настраиваем для (2) при развертывании, например, настройку производственного режима для различных сред развертывания. Я надеюсь, что это относится к этому вопросу.
Эшли Айткен
1
@AshleyAitken Отличный вопрос, на который я не смог найти ответ в этой теме (возможно, я пропустил его), но разместил эту новую тему: stackoverflow.com/questions/44464504/…
David

Ответы:

427

Есть два основных способа добиться этого.

DefinePlugin

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

Обратите внимание, что это просто заменит совпадения "как есть". Вот почему строка в том формате, в котором она есть. У вас может быть более сложная структура, например, объект там, но вы поймете идею.

EnvironmentPlugin

new webpack.EnvironmentPlugin(['NODE_ENV'])

EnvironmentPluginиспользует DefinePluginвнутренне и сопоставляет значения среды для кодирования через него. Терсер синтаксис.

кличка

В качестве альтернативы вы можете использовать конфигурацию через модуль с псевдонимами . Со стороны потребителя это будет выглядеть так:

var config = require('config');

Сама конфигурация может выглядеть так:

resolve: {
    alias: {
        config: path.join(__dirname, 'config', process.env.NODE_ENV)
    }
}

Скажем Давайте process.env.NODE_ENVесть development. Это будет отображаться в ./config/development.jsто время. Модуль, к которому он привязан, может экспортировать конфигурацию следующим образом:

module.exports = {
    testing: 'something',
    ...
};
Юхо Вепсяляйнен
источник
3
Спасибо за указание на то, что он заменяет матчи "как есть". Некоторое время я пытался понять, почему мой код JSON.stringify()
выдавал
4
Если вы используете ES2015, вы также можете использовать интерполяцию строк -'process.env.NODE_ENV': `"${process.env.NODE_ENV || 'development'}"`
user2688473
1
@ tybro0103, JSON.stringify('development')как это может быть не очень полезно. Вместо этого JSON.stringify(someVariable)вполне может быть!
Superjos
1
Вы должны NODE_ENVсделать это. Как установить это зависит от вашей платформы.
Юхо Вепсяляйнен
1
@AnyulRivas Да. Реакт использует process.env.NODE_ENVшаблон, и это работает.
Юхо Вепсяляйнен
109

Просто еще один вариант, если вы хотите использовать только интерфейс definecli , просто используйте параметр webpack. Я добавляю следующий скрипт в мой package.json:

"build-production": "webpack -p --define process.env.NODE_ENV='\"production\"' --progress --colors"

Так что мне просто нужно бежать npm run build-production.

zer0chain
источник
2
Есть ли документация для этого? Я не могу Google --define :(
Ричард
5
Для веб-пакета @ 2 «-p» уже является сокращением для --optimize-minim --define process.env.NODE_ENV = "production"
okm
@okm В документах упоминается -p Равно --optimize-минимизировать --optimize-occurence-order, поэтому не упоминайте --define process.env.NODE_ENV = "production". Это то, что удалено?
Надер Ганбари
1
@NaderHadjiGhanbari Он находится в версии 2 веб-
пакета.
73

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

У меня есть 2 конфига webpack в настоящее время:

webpack.production.config.js

new webpack.DefinePlugin({
  'process.env':{
    'NODE_ENV': JSON.stringify('production'),
    'API_URL': JSON.stringify('http://localhost:8080/bands')
  }
}),

webpack.config.js

new webpack.DefinePlugin({
  'process.env':{
    'NODE_ENV': JSON.stringify('development'),
    'API_URL': JSON.stringify('http://10.10.10.10:8080/bands')
  }
}),

В моем коде я получаю значение API_URL таким (кратким) способом:

const apiUrl = process.env.API_URL;

РЕДАКТИРОВАТЬ 3 ноября 2016

В документации по Webpack есть пример: https://webpack.js.org/plugins/define-plugin/#usage

new webpack.DefinePlugin({
    PRODUCTION: JSON.stringify(true),
    VERSION: JSON.stringify("5fa3b9"),
    BROWSER_SUPPORTS_HTML5: true,
    TWO: "1+1",
    "typeof window": JSON.stringify("object")
})

С ESLint вам нужно специально разрешить неопределенные переменные в коде, если у вас есть no-undefправило. http://eslint.org/docs/rules/no-undef вот так:

/*global TWO*/
console.log('Running App version ' + TWO);

РЕДАКТИРОВАТЬ 7 сентября 2017 г. (специфично для Create-React-App)

Если вам не нужно слишком много настраивать, проверьте Create-React-App: Create-React-App - Добавление переменных пользовательской среды . Под капотом CRA все равно использует Webpack.

thevangelist
источник
2
Вы обнаружили, что это предотвратило передачу переменных среды во время выполнения? Если вы замените целое, process.envто, process.env.PORTнапример, не разрешится, например, undefinedво время сборки веб-пакета, что означает, что вы больше не сможете переопределить порт из среды?
djskinner
Спасибо. Наконец-то ответ на этот вопрос, который понятен!
Дейв Саг
что такое процесс? откуда это? если это объект узла, как он попадает в браузер?
Даниэль Бировский Попески
Это ужасное решение, у вас есть два почти одинаковых файла webpack.configs, за исключением установки NODE_ENV и API_URL
Брайан Огден
1
@BrianOgden Да, действительно, вы должны использовать что-то вроде webpack-merge для этого: npmjs.com/package/webpack-merge - Это немного выходит за рамки этого вопроса IMO.
евангелист
24

Вы можете передать любой аргумент командной строки без дополнительных плагинов , используя --envтак WebPack 2:

webpack --config webpack.config.js --env.foo=bar

Используя переменную в webpack.config.js:

module.exports = function(env) {
    if (env.foo === 'bar') {
        // do something
    }
}

Источник

andruso
источник
22

Вы можете напрямую использовать EnvironmentPluginдоступный webpackдоступ, чтобы иметь доступ к любой переменной среды во время транспортировки.

Вам просто нужно объявить плагин в вашем webpack.config.jsфайле:

var webpack = require('webpack');

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

Обратите внимание, что вы должны явно объявить имя переменных среды, которые вы хотите использовать.

Kuhess
источник
4
В документации по Webpack есть пример с этим примером использования. github.com/webpack/docs/wiki/list-of-plugins#environmentplugin
Technetium,
1
Если вы хотите поместить переменные окружения в файл .env, вы можете использовать пакет dotenv и инициализировать его в файле webpack.config.js. npmjs.com/package/dotenv
Джастин Маккэндлесс,
13

Чтобы добавить к куче ответов лично, я предпочитаю следующее:

const webpack = require('webpack');
const prod = process.argv.indexOf('-p') !== -1;

module.exports = {
  ...
  plugins: [
    new webpack.DefinePlugin({
      process: {
        env: {
          NODE_ENV: prod? `"production"`: '"development"'
        }
      }
    }),
    ...
  ]
};

Используя это, вы не столкнетесь с проблемой фан-переменных env или кроссплатформенности (с env vars). Все, что вы делаете, это запускаете обычный webpackили webpack -pдля dev или production соответственно.

Ссылка: выпуск Github

Goblinlord
источник
При определении значений для процесса предпочитайте 'process.env.NODE_ENV': JSON.stringify('production')более process: { env: { NODE_ENV: JSON.stringify('production') } }. Использование последнего перезапишет объект процесса, что может нарушить совместимость с некоторыми модулями, которые ожидают определения других значений в объекте процесса.
Слоренцо
13

Так как мой Редактировать вышеупомянутый пост не был одобрен thevangelist , опубликовать дополнительную информацию.

Если вы хотите выбрать значение из package.json как определенный номер версии и получить к нему доступ через DefinePlugin внутри Javascript.

{"version": "0.0.1"}

Затем импортируйте package.json внутри соответствующего webpack.config , получите доступ к атрибуту с помощью переменной import, затем используйте атрибут в DefinePlugin .

const PACKAGE = require('../package.json');
const _version = PACKAGE.version;//Picks the version number from package.json

Например, определенная конфигурация в webpack.config использует METADATA для DefinePlugin:

const METADATA = webpackMerge(commonConfig({env: ENV}).metadata, {
  host: HOST,
  port: PORT,
  ENV: ENV,
  HMR: HMR,
  RELEASE_VERSION:_version//Version attribute retrieved from package.json
});

new DefinePlugin({
        'ENV': JSON.stringify(METADATA.ENV),
        'HMR': METADATA.HMR,
        'process.env': {
          'ENV': JSON.stringify(METADATA.ENV),
          'NODE_ENV': JSON.stringify(METADATA.ENV),
          'HMR': METADATA.HMR,
          'VERSION': JSON.stringify(METADATA.RELEASE_VERSION)//Setting it for the Scripts usage.
        }
      }),

Получите доступ к этому внутри любого машинописного файла:

this.versionNumber = process.env.VERSION;

Самый умный способ будет выглядеть так:

// webpack.config.js
plugins: [
    new webpack.DefinePlugin({
      VERSION: JSON.stringify(require("./package.json").version)
    })
  ]

Спасибо Россу Аллену

Abhijeet
источник
11

Просто еще один ответ, который похож на ответ @ zer0chain. Однако с одним отличием.

Настройка webpack -pдостаточно.

Это так же, как:

--define process.env.NODE_ENV="production"

И это так же, как

// webpack.config.js
const webpack = require('webpack');

module.exports = {
  //...

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

Так что вам может понадобиться что-то подобное в package.jsonфайле Node:

{
  "name": "projectname",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "debug": "webpack -d",
    "production": "webpack -p"
  },
  "author": "prosti",
  "license": "ISC",
  "dependencies": {    
    "webpack": "^2.2.1",
    ...
  }
}

Несколько советов от DefinePlugin :

DefinePlugin позволяет вам создавать глобальные константы, которые можно настроить во время компиляции. Это может быть полезно для разрешения различного поведения между сборками разработки и сборками выпуска. Например, вы можете использовать глобальную константу, чтобы определить, происходит ли запись в журнал; возможно, вы выполняете вход в свою сборку разработки, но не в сборке выпуска. Именно такой сценарий облегчает использование DefinePlugin.


Что это так, чтобы вы могли проверить, если вы печатаете webpack --help

Config options:
  --config  Path to the config file
                         [string] [default: webpack.config.js or webpackfile.js]
  --env     Enviroment passed to the config, when it is a function

Basic options:
  --context    The root directory for resolving entry point and stats
                                       [string] [default: The current directory]
  --entry      The entry point                                          [string]
  --watch, -w  Watch the filesystem for changes                        [boolean]
  --debug      Switch loaders to debug mode                            [boolean]
  --devtool    Enable devtool for better debugging experience (Example:
               --devtool eval-cheap-module-source-map)                  [string]
  -d           shortcut for --debug --devtool eval-cheap-module-source-map
               --output-pathinfo                                       [boolean]
  -p           shortcut for --optimize-minimize --define
               process.env.NODE_ENV="production" 

                      [boolean]
  --progress   Print compilation progress in percentage                [boolean]
прости
источник
3

Чтобы добавить к куче ответов:

Используйте ExtendedDefinePlugin вместо DefinePlugin

npm install extended-define-webpack-plugin --save-dev.

ExtendedDefinePlugin намного проще в использовании и задокументирована :-) ссылка

Поскольку DefinePlugin не хватает хорошей документации, я хочу помочь, сказав, что на самом деле он работает как #DEFINE в c # .

#if (DEBUG)
        Console.WriteLine("Debugging is enabled.");
#endif

Таким образом, если вы хотите понять, как работает DefinePlugin, прочтите документацию c # #define. ссылка на сайт

Ханнес Нойкерманс
источник
2

Я предпочитаю использовать файл .env для другой среды.

  1. Используйте webpack.dev.config для копирования env.devв .env в корневую папку
  2. Используйте webpack.prod.config для копирования env.prodв .env

и в коде

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

require('dotenv').config(); const API = process.env.API ## which will store the value from .env file

Шива Кандарадж
источник
2

Я обнаружил, что следующее решение проще всего настроить переменную среды для Webpack 2:

Например, у нас есть настройки веб-пакета:

var webpack = require('webpack')

let webpackConfig = (env) => { // Passing envirmonment through
                                // function is important here
    return {
        entry: {
        // entries
        },

        output: {
        // outputs
        },

        plugins: [
        // plugins
        ],

        module: {
        // modules
        },

        resolve: {
        // resolves
        }

    }
};

module.exports = webpackConfig;

Добавить переменную среды в Webpack:

plugins: [
    new webpack.EnvironmentPlugin({
       NODE_ENV: 'development',
       }),
]

Определите переменную плагина и добавьте ее в plugins:

    new webpack.DefinePlugin({
        'NODE_ENV': JSON.stringify(env.NODE_ENV || 'development')
    }),

Теперь при запуске команды webpack передайте в env.NODE_ENVкачестве аргумента:

webpack --env.NODE_ENV=development

// OR

webpack --env.NODE_ENV development

Теперь вы можете получить доступ к NODE_ENVпеременной в любом месте вашего кода.

ruddra
источник
1

Начиная с Webpack v4, простая настройка modeв вашей конфигурации Webpack установит NODE_ENVдля вас (через DefinePlugin). Документы здесь.

ericsoco
источник
1

Вот способ, который сработал для меня и позволил мне сохранить мои переменные среды DRY, повторно используя файл json.

const webpack = require('webpack');
let config = require('./settings.json');
if (__PROD__) {
    config = require('./settings-prod.json');
}

const envVars = {};
Object.keys(config).forEach((key) => {
    envVars[key] = JSON.stringify(config[key]);
});

new webpack.DefinePlugin({
    'process.env': envVars
}),
cbaigorri
источник
0

Я не большой поклонник ...

new webpack.DefinePlugin({
  'process.env': envVars
}),

... так как он не обеспечивает никакой безопасности. вместо этого вы заканчиваете тем, что усиливаете свои секретные вещи, если только вы не добавите веб-пакет в gitignore - есть лучшее решение.

В основном с этим конфигом, как только вы скомпилируете свой код, все переменные процесса env будут удалены из всего кода, не будет ни одного process.env.VAR благодаря плагину babel transform-inline-environment-variables PS, если вы не хотите заканчивать работу с целой кучей неопределенностей, убедитесь, что вы вызываете env.js до того, как webpack вызывает babel-loader, поэтому это первое, что вызывает webpack. массив vars в файле babel.config.js должен соответствовать объекту в env.js. теперь есть только одна косилка. добавьте .envфайл, поместите туда все свои переменные env, файл должен находиться в корне проекта или вы можете свободно добавлять его куда угодно, просто убедитесь, что в файле env.js указано то же место, а также добавьте его в gitignore

const dotFiles = ['.env'].filter(Boolean);

if (existsSync(dotFiles)) {
    require("dotenv-expand")(require("dotenv").config((dotFiles)));
}

Если вы хотите увидеть весь babel + webpack + ts, возьмите его с ног https://github.com/EnetoJara/Node-typescript-babel-webpack.git

и та же логика применяется, чтобы реагировать и все остальные 💩

config
---webpack.js
---env.js
src
---source code world
.env
bunch of dotFiles

env.js

"use strict";
/***
I took the main idea from CRA, but mine is more cooler xD
*/
const {realpathSync, existsSync} = require('fs');
const {resolve, isAbsolute, delimiter} = require('path');

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

const appDirectory = realpathSync(process.cwd());

if (typeof NODE_ENV !== "string") {
    throw new Error("falle and stuff");
}

const dotFiles = ['.env'].filter(Boolean);

if (existsSync(dotFiles)) {
    require("dotenv-expand")(require("dotenv").config((dotFiles)));
}

process.env.NODE_PATH = (process.env.NODE_PATH || "")
    .split(delimiter)
    .filter(folder => folder && isAbsolute(folder))
    .map(folder => resolve(appDirectory, folder))
    .join(delimiter);

const ENETO_APP = /^ENETO_APP_/i;

module.exports = (function () {
    const raw = Object.keys ( process.env )
        .filter ( key => ENETO_APP.test ( key ) )
        .reduce ( ( env, key ) => {
                env[ key ] = process.env[ key ];
                return env;
            },
            {
                BABEL_ENV: process.env.ENETO_APP_BABEL_ENV,
                ENETO_APP_DB_NAME: process.env.ENETO_APP_DB_NAME,
                ENETO_APP_DB_PASSWORD: process.env.ENETO_APP_DB_PASSWORD,
                ENETO_APP_DB_USER: process.env.ENETO_APP_DB_USER,
                GENERATE_SOURCEMAP: process.env.ENETO_APP_GENERATE_SOURCEMAP,
                NODE_ENV: process.env.ENETO_APP_NODE_ENV,
                PORT: process.env.ENETO_APP_PORT,
                PUBLIC_URL: "/"
            } );

    const stringyField = {
        "process.env": Object.keys(raw).reduce((env, key)=> {
            env[key]=JSON.stringify(raw[key]);
            return env;
        },{}),

    };

    return {
        raw, stringyField
    }
})();

файл webpack без плагинов тролль

"use strict";

require("core-js");
require("./env.js");

const path = require("path");
const nodeExternals = require("webpack-node-externals");

module.exports = env => {
    return {
        devtool: "source-map",
        entry: path.join(__dirname, '../src/dev.ts'),
        externals: [nodeExternals()],
        module: {
            rules: [
                {
                    exclude: /node_modules/,
                    test: /\.ts$/,
                    use: [
                        {
                            loader: "babel-loader",
                        },
                        {
                            loader: "ts-loader"
                        }
                    ],
                },
                {
                    test: /\.(png|jpg|gif)$/,
                    use: [
                        {
                            loader: "file-loader",
                        },
                    ],
                },
            ],
        },
        node: {
            __dirname: false,
            __filename: false,
        },
        optimization: {
            splitChunks: {
                automaticNameDelimiter: "_",
                cacheGroups: {
                    vendor: {
                        chunks: "initial",
                        minChunks: 2,
                        name: "vendor",
                        test: /[\\/]node_modules[\\/]/,
                    },
                },
            },
        },
        output: {
            chunkFilename: "main.chunk.js",
            filename: "name-bundle.js",
            libraryTarget: "commonjs2",
        },
        plugins: [],
        resolve: {
            extensions: ['.ts', '.js']
        }   ,
        target: "node"
    };
};

babel.config.js

module.exports = api => {

    api.cache(() => process.env.NODE_ENV);

    return {

        plugins: [
            ["@babel/plugin-proposal-decorators", { legacy: true }],
            ["@babel/plugin-transform-classes", {loose: true}],
            ["@babel/plugin-external-helpers"],
            ["@babel/plugin-transform-runtime"],
            ["@babel/plugin-transform-modules-commonjs"],
            ["transform-member-expression-literals"],
            ["transform-property-literals"],
            ["@babel/plugin-transform-reserved-words"],
            ["@babel/plugin-transform-property-mutators"],
            ["@babel/plugin-transform-arrow-functions"],
            ["@babel/plugin-transform-block-scoped-functions"],
            [
                "@babel/plugin-transform-async-to-generator",
                {
                    method: "coroutine",
                    module: "bluebird",
                },
            ],
            ["@babel/plugin-proposal-async-generator-functions"],
            ["@babel/plugin-transform-block-scoping"],
            ["@babel/plugin-transform-computed-properties"],
            ["@babel/plugin-transform-destructuring"],
            ["@babel/plugin-transform-duplicate-keys"],
            ["@babel/plugin-transform-for-of"],
            ["@babel/plugin-transform-function-name"],
            ["@babel/plugin-transform-literals"],
            ["@babel/plugin-transform-object-super"],
            ["@babel/plugin-transform-shorthand-properties"],
            ["@babel/plugin-transform-spread"],
            ["@babel/plugin-transform-template-literals"],
            ["@babel/plugin-transform-exponentiation-operator"],
            ["@babel/plugin-proposal-object-rest-spread"],
            ["@babel/plugin-proposal-do-expressions"],
            ["@babel/plugin-proposal-export-default-from"],
            ["@babel/plugin-proposal-export-namespace-from"],
            ["@babel/plugin-proposal-logical-assignment-operators"],
            ["@babel/plugin-proposal-throw-expressions"],
            [
                "transform-inline-environment-variables",
                {
                    include: [
                        "ENETO_APP_PORT",
                        "ENETO_APP_NODE_ENV",
                        "ENETO_APP_BABEL_ENV",
                        "ENETO_APP_DB_NAME",
                        "ENETO_APP_DB_USER",
                        "ENETO_APP_DB_PASSWORD",
                    ],
                },
            ],
        ],
        presets: [["@babel/preset-env",{
            targets: {
                node: "current",
                esmodules: true
            },
            useBuiltIns: 'entry',
            corejs: 2,
            modules: "cjs"
        }],"@babel/preset-typescript"],
    };
};
Эрнесто
источник
«В конечном итоге вы усиливаете свои секретные вещи, если не добавите веб-пакет в gitignore» @ Эрнесто, ты можешь рассказать об этом вообще?
Кэти Байерс
В основном ваш пакет заканчивается без process.env.BLAHBLAH и устанавливает фактическое значение. Например, вместо «process.env.NODE_ENV» вы получите «производство», я имею в виду, что это не лучший пример, но представьте секретный ключ. Ваша связка будет иметь фактическое значение, и кто знает, что означает эта проводная строка?
Эрнесто
Хммм - да, эти значения будут интерполированы в встроенной версии, но, вероятно, вы не продвигаете это в GitHub ...
Кэти Байерс,
-4

Я не знаю почему, но никто не упоминает самое простое решение. Это работает для меня для nodejs и grunt. Поскольку для многих людей веб-пакет может сбить с толку, вы можете просто использовать следующую строку:

process.env.NODE_ENV = 'production';

С вышеупомянутым решением вам не нужно использовать envify или webpack. Иногда простое жестко закодированное решение может работать для некоторых людей.

Джон Скумбурдис
источник