Pasar variables dependientes del entorno en webpack

306

Estoy tratando de convertir una aplicación angular de gulp a webpack. en gulp utilizo gulp-preprocess para reemplazar algunas variables en la página html (por ejemplo, nombre de la base de datos) dependiendo del NODE_ENV. ¿Cuál es la mejor manera de lograr un resultado similar con webpack?

kpg
fuente
1
¿Alias ​​te funcionó?
Juho Vepsäläinen
1
@bebraw: antes de que pudiera entender los alias, implementé la otra solución que sugirió basada en DefinePlugin (). Ahora veo que el alias sería una mejor solución y probablemente refactorizará en algún momento, gracias. Si desea incluir sus dos soluciones en una respuesta, lo aceptaré con mucho gusto.
kpg
2
Fue dirigido aquí a través del mensaje de la consola. ¿Cómo arreglar esto en Browserify?
GN.
2
¿Esta pregunta intenta configurar el SPA en el momento de la compilación o en el momento de la carga? Observo dos tipos de configuración para SPA: 1) modo de desarrollo o producción, y 2) entorno de implementación, por ejemplo, desarrollo, puesta en escena, producción. Creo que NODE_ENV puede usarse para configurar para (1) en el momento de la compilación, pero ¿cómo configuramos para (2) en la implementación, por ejemplo, configurar un modo de producción para diferentes entornos de implementación? Espero que esto sea relevante para esta pregunta.
Ashley Aitken
1
@AshleyAitken Gran pregunta de la cual no pude encontrar una respuesta en este hilo (tal vez me la perdí), pero publiqué este nuevo hilo: stackoverflow.com/questions/44464504/…
David Tesar

Respuestas:

427

Hay dos formas básicas de lograr esto.

DefinePlugin

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

Tenga en cuenta que esto solo reemplazará las coincidencias "tal cual". Es por eso que la cadena tiene el formato que tiene. Podría tener una estructura más compleja, como un objeto allí, pero se entiende la idea.

Medio ambiente

new webpack.EnvironmentPlugin(['NODE_ENV'])

EnvironmentPlugin usos DefinePlugin internamente y asigna los valores del entorno para codificar a través de él. Sintaxis Terser.

Alias

Alternativamente, podría consumir la configuración a través de un módulo con alias . Desde el lado del consumidor se vería así:

var config = require('config');

La configuración en sí misma podría verse así:

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

Digamos que process.env.NODE_ENVes development. ./config/development.jsEntonces se mapearía en . El módulo al que se asigna puede exportar configuraciones como esta:

module.exports = {
    testing: 'something',
    ...
};
Juho Vepsäläinen
fuente
3
Gracias por señalar el hecho de que reemplaza las coincidencias "tal cual". Estuve luchando por un tiempo para descubrir por qué mi código arrojaba un error y fue porque no estaba envolviendo el valor en unJSON.stringify()
pbojinov
44
Si está usando ES2015, también puede usar la interpolación de cadenas -'process.env.NODE_ENV': `"${process.env.NODE_ENV || 'development'}"`
user2688473
1
@ tybro0103 JSON.stringify('development')como está podría no ser realmente útil. En cambio JSON.stringify(someVariable)puede ser bastante!
superjos
1
Deberías ponerte NODE_ENVa hacer eso. Cómo configurar eso depende de su plataforma.
Juho Vepsäläinen
1
@AnyulRivas Sí. React utiliza process.env.NODE_ENVpatrón y funciona.
Juho Vepsäläinen
109

Solo otra opción, si desea usar solo una interfaz cli, simplemente use la defineopción de webpack. Agrego el siguiente script en mi package.json:

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

Entonces solo tengo que correr npm run build-production.

zer0chain
fuente
2
¿Hay documentación para esto? No puedo Google --define :(
Richard
55
Para webpack @ 2, "-p" ya es un acceso directo para --optimize-minimiza --define process.env.NODE_ENV = "production"
okm
@okm Docs mencionan -p Igual a --optimize-minimiza --optimize-occurence-order, por lo que no se menciona --define process.env.NODE_ENV = "production". ¿Es algo que se eliminó?
Nader Ghanbari
1
@NaderHadjiGhanbari Está en la versión 2 del paquete web webpack.js.org/api/cli/#shortcuts
okm
73

Investigué un par de opciones sobre cómo establecer variables específicas del entorno y terminé con esto:

Tengo 2 configuraciones de paquete web actualmente:

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')
  }
}),

En mi código obtengo el valor de API_URL de esta manera (breve):

const apiUrl = process.env.API_URL;

EDITAR 3 de noviembre de 2016

Webpack docs tiene un ejemplo: 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")
})

Con ESLint , debe permitir específicamente variables indefinidas en el código, si tieneno-undef regla. http://eslint.org/docs/rules/no-undef así:

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

EDITAR 7 de septiembre de 2017 (Crear-Reaccionar-Aplicación específica)

Si no está interesado en configurar demasiado, consulte Crear-Reaccionar-Aplicación: Crear-Reaccionar-Aplicación - Agregar variables de entorno personalizadas . Debajo del capó, CRA usa Webpack de todos modos.

thevangelist
fuente
2
¿Descubrió que esto impedía que se pasaran variables de entorno en tiempo de ejecución? Si reemplaza todo process.env, ¿no se process.env.PORTresuelve, por ejemplo, undefineddurante la compilación del paquete web, lo que significa que ya no puede anular el puerto del entorno?
djskinner
Muchas gracias. ¡Finalmente una respuesta sobre este tema que es comprensible!
Dave Sag
¿Qué es el proceso? ¿De dónde viene? si es un objeto de nodo, ¿cómo ingresa al navegador?
Daniel Birowsky Popeski el
Esta es una solución terrible, tiene dos webpack.configs casi completamente idénticos, excepto para configurar NODE_ENV y API_URL
Brian Ogden
1
@BrianOgden Sí, de hecho, debería usar algo como webpack-merge para esto: npmjs.com/package/webpack-merge - Está un poco fuera de alcance para esta pregunta de la OMI.
thevangelist
24

Puede pasar cualquier argumento de línea de comandos sin complementos adicionales usando --envdesde webpack 2:

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

Usando la variable en webpack.config.js:

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

Fuente

andruso
fuente
22

Puede usar directamente el EnvironmentPlugindisponible en webpackpara tener acceso a cualquier variable de entorno durante la transpilación.

Solo tiene que declarar el complemento en su webpack.config.jsarchivo:

var webpack = require('webpack');

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

Tenga en cuenta que debe declarar explícitamente el nombre de las variables de entorno que desea utilizar.

Kuhess
fuente
44
Hay un ejemplo en los documentos del paquete web con este mismo caso de uso. github.com/webpack/docs/wiki/list-of-plugins#environmentplugin
tecnecio
1
Si desea colocar sus variables de entorno en un archivo .env, puede usar el paquete dotenv e inicializarlo en webpack.config.js. npmjs.com/package/dotenv
Justin McCandless
13

Para agregar al conjunto de respuestas personalmente, prefiero lo siguiente:

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

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

Al usar esto, no hay variables funky env o problemas multiplataforma (con env vars). Todo lo que haces es ejecutar el normal webpacko webpack -ppara desarrollo o producción respectivamente.

Referencia: problema de Github

Duendecillo
fuente
Al definir valores para el proceso, prefiera 'process.env.NODE_ENV': JSON.stringify('production')sobre process: { env: { NODE_ENV: JSON.stringify('production') } }. El uso de este último sobrescribirá el objeto del proceso, lo que puede romper la compatibilidad con algunos módulos que esperan que se definan otros valores en el objeto del proceso.
slorenzo
13

Desde mi edición en la publicación anterior por thevangelist no fue aprobada , información adicional.

Si desea elegir el valor de package.json como un número de versión definido y acceder a él a través de DefinePlugin dentro de Javascript.

{"version": "0.0.1"}

Luego, importe package.json dentro del respectivo webpack.config , acceda al atributo usando la variable de importación, luego use el atributo en DefinePlugin .

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

Por ejemplo, cierta configuración en webpack.config está utilizando METADATA para 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.
        }
      }),

Acceda a esto dentro de cualquier archivo mecanografiado:

this.versionNumber = process.env.VERSION;

La forma más inteligente sería así:

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

Gracias a ross allen

Abhijeet
fuente
11

Solo otra respuesta que es similar a la respuesta de @ zer0chain. Sin embargo, con una distinción.

El ajuste webpack -pes suficiente.

Es lo mismo que:

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

Y esto es lo mismo que

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

module.exports = {
  //...

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

Por lo tanto, es posible que solo necesite algo como esto en el package.jsonarchivo 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",
    ...
  }
}

Solo algunos consejos de DefinePlugin :

DefinePlugin le permite crear constantes globales que se pueden configurar en tiempo de compilación. Esto puede ser útil para permitir un comportamiento diferente entre las compilaciones de desarrollo y las de lanzamiento. Por ejemplo, puede usar una constante global para determinar si se realiza el registro; quizás realice el inicio de sesión en su compilación de desarrollo pero no en la compilación de lanzamiento. Ese es el tipo de escenario que facilita DefinePlugin.


Que esto es para que puedas comprobar si escribes 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
fuente
3

Para agregar al conjunto de respuestas:

Use ExtendedDefinePlugin en lugar de DefinePlugin

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

ExtendedDefinePlugin es mucho más simple de usar y está documentado :-) enlace

Debido a que DefinePlugin carece de buena documentación, quiero ayudar, diciendo que en realidad funciona como #DEFINE en c # .

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

Por lo tanto, si desea comprender cómo funciona DefinePlugin, lea la documentación de c # #define. enlace

Hannes Neukermans
fuente
2

Prefiero usar el archivo .env para diferentes entornos.

  1. Use webpack.dev.config para copiar env.deva .env en la carpeta raíz
  2. Use webpack.prod.config para copiar env.proda .env

y en código

utilizar

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

Siva Kandaraj
fuente
2

Encontré que la siguiente solución es la más fácil de configurar variable de entorno para Webpack 2:

Por ejemplo, tenemos una configuración de paquete web:

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;

Agregar variable de entorno en Webpack:

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

Defina la variable del complemento y agréguela a plugins:

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

Ahora, cuando ejecute el comando webpack, pase env.NODE_ENVcomo argumento:

webpack --env.NODE_ENV=development

// OR

webpack --env.NODE_ENV development

Ahora puede acceder a la NODE_ENVvariable en cualquier parte de su código.

Ruddra
fuente
1

Desde Webpack v4, simplemente configurando modesu configuración de Webpack establecerá el NODE_ENVpara usted (a través de DefinePlugin). Documentos aquí.

ericsoco
fuente
1

Aquí hay una manera que me ha funcionado y me ha permitido mantener SECAS mis variables de entorno reutilizando un archivo 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
fuente
0

No soy un gran fan de ...

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

... ya que no proporciona ningún tipo de seguridad. en cambio, terminas aumentando tus cosas secretas, a menos que agregues un paquete web a gitignore 🤷‍♀️ hay una mejor solución.

Básicamente, con esta configuración, una vez que compila su código, todas las variables env del proceso se eliminarán de todo el código, no habrá un solo proceso.env.VAR gracias al complemento de babel transform-inline-environment-variables PS si no desea terminar con un montón de indefinidos, asegúrese de llamar a env.js antes de que webpack llame a babel-loader, por eso es lo primero que llama webpack. la matriz de vars en el archivo babel.config.js debe coincidir con el objeto en env.js. ahora solo hay una cosa para cortar. agregue un .envarchivo coloque todas sus variables env allí, el archivo debe estar en la raíz del proyecto o siéntase libre de agregarlo donde lo desee, solo asegúrese de establecer la misma ubicación en el archivo env.js y también agréguelo a gitignore

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

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

Si quieres ver todo el babel + webpack + ts obtenlo de heaw https://github.com/EnetoJara/Node-typescript-babel-webpack.git

y la misma lógica se aplica para reaccionar y todos los demás 💩

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

archivo webpack sin troll de complementos

"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
fuente
"Terminas impulsando tus cosas secretas, a menos que agregues un paquete web a gitignore". @ Ernesto, ¿puedes ampliar eso?
Katie Byers
Básicamente, su paquete termina sin el proceso.env.BLAHBLAH y pone el valor real. Por ejemplo, en lugar de tener process.env.NODE_ENV u terminar con "producción", quiero decir que ese no es el mejor ejemplo, pero imagina una clave secreta. Su paquete tendrá el valor real y quién sabe qué significa esa cadena cableada 🤷‍♀️
Ernesto
Hmmm: sí, esos valores se interpolarán en la versión incorporada , pero presumiblemente no estás empujando eso a GitHub ...
Katie Byers
-4

No sé por qué, pero nadie menciona realmente la solución más simple. Esto funciona para mí para nodejs y gruñido. Como para muchas personas, el paquete web puede ser confuso, simplemente puede usar la siguiente línea:

process.env.NODE_ENV = 'production';

Con la solución anterior, realmente no necesita usar envify o webpack. A veces, la solución simple codificada puede funcionar para algunas personas.

John Skoumbourdis
fuente