Passando variáveis ​​dependentes do ambiente no webpack

306

Estou tentando converter um aplicativo angular do gulp para o webpack. no gulp, uso o gulp-preprocess para substituir algumas variáveis ​​na página html (por exemplo, nome do banco de dados), dependendo do NODE_ENV. Qual é a melhor maneira de obter um resultado semelhante com o webpack?

kpg
fonte
1
O apelido funcionou para você?
Juho Vepsäläinen
1
@bebraw: antes que eu pudesse entender meus apelidos, eu implementei a outra solução que você sugeriu com base no DefinePlugin (). Agora vejo que o alias seria uma solução melhor e provavelmente será refatorada em algum momento - obrigado. Se você gostaria de incluir suas duas soluções em uma resposta, aceitarei com prazer.
kpg
2
Foi direcionado aqui via mensagem do console. Como corrigir isso no Browserify?
GN.
2
Esta pergunta está tentando configurar o SPA no tempo de compilação ou carregamento? Observo dois tipos de configuração para SPAs: 1) modo de desenvolvimento ou produção e 2) ambiente de implementação, por exemplo, desenvolvimento, preparação, produção. Eu acho que o NODE_ENV pode ser usado para configurar para (1) no momento da compilação, mas como configuramos para (2) na implantação, por exemplo, configurando um modo de produção para diferentes ambientes de implantação. Espero que isso seja relevante para esta questão.
Ashley Aitken
1
@AshleyAitken Grande pergunta de que eu não poderia encontrar uma resposta sobre este tópico (talvez eu perdi), mas postou neste novo segmento: stackoverflow.com/questions/44464504/...
David Tesar

Respostas:

427

Existem duas maneiras básicas de conseguir isso.

DefinePlugin

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

Observe que isso substituirá apenas as correspondências "como estão". É por isso que a string está no formato que está. Você poderia ter uma estrutura mais complexa, como um objeto, mas você entendeu a ideia.

EnvironmentPlugin

new webpack.EnvironmentPlugin(['NODE_ENV'])

EnvironmentPluginusa DefinePlugininternamente e mapeia os valores do ambiente para codificá-lo. Sintaxe Terser.

Alias

Como alternativa, você pode consumir a configuração por meio de um módulo com alias . Do lado do consumidor, ficaria assim:

var config = require('config');

A configuração em si poderia ter esta aparência:

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

Digamos que process.env.NODE_ENVé development. Seria mapeado para ./config/development.jsentão. O módulo para o qual ele mapeia pode exportar configurações como esta:

module.exports = {
    testing: 'something',
    ...
};
Juho Vepsäläinen
fonte
3
Obrigado por apontar o fato de que ele substitui as correspondências "como estão". Eu estava lutando por um tempo para descobrir por que meu código estava jogando um erro e que era porque eu não estava enrolando o valor em umaJSON.stringify()
pbojinov
4
Se você estiver usando ES2015, você também pode usar interpolação string -'process.env.NODE_ENV': `"${process.env.NODE_ENV || 'development'}"`
user2688473
1
@ tybro0103 JSON.stringify('development')como está pode não ser realmente útil. Em vez disso JSON.stringify(someVariable)pode ser bastante!
superjos 25/02
1
Você deve NODE_ENVfazer isso. Como definir isso depende da sua plataforma.
Juho Vepsäläinen
1
@AnyulRivas Yeah. React usa process.env.NODE_ENVpadrão e funciona.
Juho Vepsäläinen 5/05
109

Apenas outra opção, se você deseja usar apenas uma interface defineCLI , basta usar a opção do webpack. Eu adiciono o seguinte script no meu package.json:

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

Então eu só tenho que correr npm run build-production.

zer0chain
fonte
2
Existe documentação para isso? Não consigo usar o Google --define :(
Richard
5
Para o webpack @ 2, "-p" já é um atalho para --optimize-minimize --define process.env.NODE_ENV = "production"
okm
Os documentos do @okm mencionam -p Igual a - otimizar-minimizar - otimizar a ordem da ocorrência, portanto, não há menção de --define process.env.NODE_ENV = "produção". É algo que foi removido?
Nader Ghanbari
1
@NaderHadjiGhanbari Está na versão 2 do webpack webpack.js.org/api/cli/#shortcuts
okm
73

Eu investiguei algumas opções sobre como definir variáveis ​​específicas do ambiente e acabei com isso:

Atualmente, tenho 2 configurações do 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')
  }
}),

No meu código, obtenho o valor de API_URL desta maneira (breve):

const apiUrl = process.env.API_URL;

EDIT 3 de novembro de 2016

Os documentos do Webpack têm um exemplo: 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")
})

Com o ESLint, você precisa permitir especificamente variáveis ​​indefinidas no código, se você tiver uma no-undefregra. http://eslint.org/docs/rules/no-undef assim:

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

EDIT 7 de setembro de 2017 (específico para Create-React-App)

Se você não gosta muito de configurar, confira Create-React-App: Create-React-App - Adicionando variáveis ​​de ambiente personalizadas . Sob o capô, o CRA usa o Webpack de qualquer maneira.

thevangelist
fonte
2
Você achou que isso impedia a passagem de variáveis ​​de ambiente em tempo de execução? Se você substituir o conjunto inteiro process.env, process.env.PORTpor exemplo, não resolve undefineddurante a compilação do webpack, o que significa que você não pode mais substituir a porta do ambiente?
Djskinner 02/08/19
Muito obrigado. Finalmente, uma resposta sobre esta questão que é compreensível!
Dave Sag
o que é processo? De onde isto está vindo? se é um objeto de nó, como ele entra no navegador?
Daniel Birowsky Popeski
Esta é uma solução terrível, você tem duas webpack.configs quase inteiramente idênticos, exceto para a criação NODE_ENV e API_URL
Brian Ogden
1
@BrianOgden Sim, é verdade, você deve usar algo como o webpack-merge para isso: npmjs.com/package/webpack-merge - Está um pouco fora do escopo desta questão IMO.
thevangelist
24

Você pode passar qualquer argumento da linha de comando sem plugins adicionais usando --envdesde o webpack 2:

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

Usando a variável em webpack.config.js:

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

Fonte

andruso
fonte
22

Você pode usar diretamente o EnvironmentPlugindisponível emwebpack para ter acesso a qualquer variável de ambiente durante a transpilação.

Você apenas tem que declarar o plugin no seu webpack.config.jsarquivo:

var webpack = require('webpack');

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

Observe que você deve declarar explicitamente o nome das variáveis ​​de ambiente que deseja usar.

Kuhess
fonte
4
Há um exemplo nos documentos do webpack com esse mesmo caso de uso. github.com/webpack/docs/wiki/list-of-plugins#environmentplugin
Technetium
1
Se você quiser colocar suas variáveis ​​de ambiente em um arquivo .env, poderá usar o pacote dotenv e inicializá-lo em webpack.config.js. npmjs.com/package/dotenv
Justin McCandless
13

Para adicionar pessoalmente ao grupo de respostas, prefiro o seguinte:

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

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

Usando isso, não há problemas na variável de ambiente ou entre plataformas (com env vars). Tudo o que você faz é executar o normal webpackou o desenvolvimento webpack -pou a produção, respectivamente.

Referência: Problema no Github

Goblinlord
fonte
Ao definir valores para o processo, prefira 'process.env.NODE_ENV': JSON.stringify('production')sobre process: { env: { NODE_ENV: JSON.stringify('production') } }. O uso deste último substituirá o objeto do processo, o que pode quebrar a compatibilidade com alguns módulos que esperam que outros valores no objeto do processo sejam definidos.
slorenzo
13

Como minha Edição na postagem acima, pela thevangelist, não foi aprovada , postando informações adicionais.

Se você deseja escolher o valor do package.json como um número de versão definido e acessá-lo através do DefinePlugin dentro do Javascript.

{"version": "0.0.1"}

Em seguida, importe package.json dentro do respectivo webpack.config , acesse o atributo usando a variável import e use o atributo no DefinePlugin .

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

Por exemplo, determinada configuração no webpack.config está usando o METADATA for 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.
        }
      }),

Acesse isso dentro de qualquer arquivo datilografado:

this.versionNumber = process.env.VERSION;

A maneira mais inteligente seria assim:

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

Agradecimentos a Ross Allen

Abhijeet
fonte
11

Apenas outra resposta semelhante à resposta do @ zer0chain. No entanto, com uma distinção.

A configuração webpack -pé suficiente.

É o mesmo que:

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

E isso é o mesmo que

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

module.exports = {
  //...

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

Portanto, você pode precisar apenas de algo assim no package.jsonarquivo 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",
    ...
  }
}

Apenas algumas dicas do DefinePlugin :

O DefinePlugin permite criar constantes globais que podem ser configuradas em tempo de compilação. Isso pode ser útil para permitir comportamentos diferentes entre compilações de desenvolvimento e compilações de versão. Por exemplo, você pode usar uma constante global para determinar se o log ocorre; talvez você faça logon na sua construção de desenvolvimento, mas não na versão. Esse é o tipo de cenário que o DefinePlugin facilita.


Isso é para que você possa verificar se digita 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]
prosti
fonte
3

Para adicionar ao grupo de respostas:

Use ExtendedDefinePlugin em vez de DefinePlugin

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

O ExtendedDefinePlugin é muito mais simples de usar e está documentado :-) link

Como o DefinePlugin não possui boa documentação, quero ajudar, dizendo que ele realmente funciona como # DEFINE em c # .

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

Portanto, se você quiser entender como o DefinePlugin funciona, leia a c # #define a duplicação. ligação

hannes neukermans
fonte
2

Eu prefiro usar o arquivo .env para ambiente diferente.

  1. Use webpack.dev.config para copiar env.devpara .env na pasta raiz
  2. Use webpack.prod.config para copiar env.prodpara .env

e no código

usar

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

Siva Kandaraj
fonte
2

Eu achei a solução a seguir mais fácil de configurar a variável de ambiente para o Webpack 2:

Por exemplo, temos as configurações de um webpack:

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;

Adicionar variável de ambiente no Webpack:

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

Defina a variável de plug-in e adicione-a a plugins:

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

Agora, ao executar o comando webpack, passe env.NODE_ENVcomo argumento:

webpack --env.NODE_ENV=development

// OR

webpack --env.NODE_ENV development

Agora você pode acessar a NODE_ENVvariável em qualquer lugar do seu código.

Ruddra
fonte
1

Desde o Webpack v4, basta definir modena sua configuração do Webpack o conjunto NODE_ENVpara você (via DefinePlugin). Documentos aqui.

ericsoco
fonte
1

Aqui está uma maneira que funcionou para mim e me permitiu manter minhas variáveis ​​de ambiente secas reutilizando um arquivo 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
fonte
0

Eu não sou um grande fã de ...

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

... como não fornece nenhum tipo de segurança. em vez disso, você acaba aprimorando suas coisas secretas, a menos que você adicione um webpack ao gitignore there️, existe uma solução melhor.

Basicamente, com esta configuração, quando você compilar seu código, todas as variáveis ​​env do processo serão removidas de todo o código, não haverá um único processo.env.VAR, graças ao plugin babel transform-inline-environment-variables PS, se você não quiser acabar com várias definições indefinidas, chame o env.js antes de o webpack chamar babel-loader, é por isso que é a primeira coisa que o webpack chama. a matriz de vars no arquivo babel.config.js deve corresponder ao objeto em env.js. Agora, há apenas uma coisa a ser cortada. adicione um .envarquivo, coloque todas as suas variáveis ​​env lá, o arquivo deve estar na raiz do projeto ou fique à vontade para adicioná-lo onde quiser, apenas certifique-se de definir o mesmo local no arquivo env.js e adicioná-lo a gitignore

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

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

Se você quiser ver todo o babel + webpack + ts obtê-lo do heaw https://github.com/EnetoJara/Node-typescript-babel-webpack.git

e a mesma lógica se aplica para reagir e todos os outros

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
    }
})();

arquivo webpack sem plugins troll

"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"],
    };
};
Ernesto
fonte
"Você acaba aprimorando suas coisas secretas, a menos que adicione um webpack ao gitignore". @ Ernesto, você pode expandir isso?
Katie Byers
Basicamente, seu pacote acaba com o process.env.BLAHBLAH e coloca o valor real. Por exemplo, em vez de process.env.NODE_ENV acabar com "produção", quero dizer que esse não é o melhor exemplo, mas imagine uma chave secreta. Seu pacote terá o valor real e quem sabe o que essa corda com fios significa 🤷♀️
Ernesto
Hmmm - sim, esses valores serão interpolados na construído versão, mas provavelmente você não está empurrando isso para GitHub ...
Katie Byers
-4

Não sei por que, mas ninguém realmente menciona a solução mais simples. Isso funciona para mim para nodejs e grunhido. Quanto a muitas pessoas, o webpack pode ser confuso, basta usar a linha abaixo:

process.env.NODE_ENV = 'production';

Com a solução acima, você realmente não precisa usar o envify ou o webpack. Às vezes, a solução simples codificada pode funcionar para algumas pessoas.

John Skoumbourdis
fonte