Passing environment-dependent variables in webpack

I'm trying to convert an angular app from gulp to webpack. in gulp I use gulp-preprocess to replace some variables in the html page (e.g. database name) depending on the NODE_ENV. What is the best way of achieving a similar result with webpack?

Answers:

Answer

There are two basic ways to achieve this.

DefinePlugin

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

Note that this will just replace the matches "as is". That's why the string is in the format it is. You could have a more complex structure, such as an object there but you get the idea.

EnvironmentPlugin

new webpack.EnvironmentPlugin(['NODE_ENV'])

EnvironmentPlugin uses DefinePlugin internally and maps the environment values to code through it. Terser syntax.

Alias

Alternatively you could consume configuration through an aliased module. From consumer side it would look like this:

var config = require('config');

Configuration itself could look like this:

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

Let's say process.env.NODE_ENV is development. It would map into ./config/development.js then. The module it maps to can export configuration like this:

module.exports = {
    testing: 'something',
    ...
};
Answer

Just another option, if you want to use only a cli interface, just use the define option of webpack. I add the following script in my package.json :

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

So I just have to run npm run build-production.

Answer

I investigated a couple of options on how to set environment-specific variables and ended up with this:

I have 2 webpack configs currently:

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

In my code I get the value of API_URL in this (brief) way:

const apiUrl = process.env.API_URL;

EDIT 3rd of Nov, 2016

Webpack docs has an example: 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")
})

With ESLint you need to specifically allow undefined variables in code, if you have no-undef rule on. http://eslint.org/docs/rules/no-undef like this:

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

EDIT 7th of Sep, 2017 (Create-React-App specific)

If you're not into configuring too much, check out Create-React-App: Create-React-App - Adding Custom Environment Variables. Under the hood CRA uses Webpack anyway.

Answer

You can pass any command-line argument without additional plugins using --env since webpack 2:

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

Using the variable in webpack.config.js:

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

Source

Answer

You can directly use the EnvironmentPlugin available in webpack to have access to any environment variable during the transpilation.

You just have to declare the plugin in your webpack.config.js file:

var webpack = require('webpack');

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

Note that you must declare explicitly the name of the environment variables you want to use.

Answer

To add to the bunch of answers personally I prefer the following:

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

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

Using this there is no funky env variable or cross-platform problems (with env vars). All you do is run the normal webpack or webpack -p for dev or production respectively.

Reference: Github issue

Answer

Since my Edit on the above post by thevangelist wasn't approved, posting additional information.

If you want to pick value from package.json like a defined version number and access it through DefinePlugin inside Javascript.

{"version": "0.0.1"}

Then, Import package.json inside respective webpack.config, access the attribute using the import variable, then use the attribute in the DefinePlugin.

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

For example certain configuration on webpack.config is using 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.
        }
      }),

Access this inside any typescript file:

this.versionNumber = process.env.VERSION;

The smartest way would be like this:

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

Thanks to Ross Allen

Answer

Just another answer that is similar to @zer0chain's answer. However, with one distinction.

Setting webpack -p is sufficient.

It is the same as:

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

And this is the same as

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

module.exports = {
  //...

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

So you may only need something like this in package.json Node file:

{
  "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",
    ...
  }
}

Just a few tips from the DefinePlugin:

The DefinePlugin allows you to create global constants which can be configured at compile time. This can be useful for allowing different behavior between development builds and release builds. For example, you might use a global constant to determine whether logging takes place; perhaps you perform logging in your development build but not in the release build. That's the sort of scenario the DefinePlugin facilitates.


That this is so you can check if you type 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]
Answer

To add to the bunch of answers:

Use ExtendedDefinePlugin instead of DefinePlugin

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

ExtendedDefinePlugin is much simpler to use and is documented :-) link

Because DefinePlugin lacks good documentation, I want to help out, by saying that it actually works like #DEFINE in c#.

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

Thus, if you want to understand how DefinePlugin works, read the c# #define doucmentation. link

Answer

I prefer using .env file for different environment.

  1. Use webpack.dev.config to copy env.dev to .env into root folder
  2. Use webpack.prod.config to copy env.prod to .env

and in code

use

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

Answer

I found the following solution to be easiest to setup environment variable for Webpack 2:

For example we have a webpack settings:

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;

Add Environment Variable in Webpack:

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

Define Plugin Variable and add it to plugins:

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

Now when running webpack command, pass env.NODE_ENV as argument:

webpack --env.NODE_ENV=development

// OR

webpack --env.NODE_ENV development

Now you can access NODE_ENV variable anywhere in your code.

Answer

Since Webpack v4, simply setting mode in your Webpack config will set the NODE_ENV for you (via DefinePlugin). Docs here.

Answer

Here is a way that has worked for me and has allowed me keep my environment variables DRY by reusing a json file.

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

I'm not a huge fan of...

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

...as it does not provides any type of security. instead, you end up boosting your secret stuff, unless you add a webpack to gitignore ??????? there is a better solution.

Basically with this config once you compile your code all the process env variables will be removed from the entire code, there is not going to be a single process.env.VAR up thanks to the babel plugin transform-inline-environment-variables PS if you do not want to end up with a whole bunch of undefines, make sure you call the env.js before webpack calls babel-loader, that's why it is the first thing webpack calls. the array of vars in babel.config.js file must match the object on env.js. now there is only one mow thing to do. add a .env file put all your env variables there, the file must be at the root of the project or feel free to add it where ever u want, just make sure to set the same location on the env.js file and also add it to gitignore

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

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

If you want to see the whole babel + webpack + ts get it from heaw https://github.com/EnetoJara/Node-typescript-babel-webpack.git

and same logic applies to react and all the other ????

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 file with no 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"],
    };
};
Answer

I don't know why but nobody really mentioning the simplest solution. This works for me for nodejs and grunt. As for many people the webpack can be confusing you can simply use the below line:

process.env.NODE_ENV = 'production';

With the above solution you don't really need to use envify or webpack. Sometimes the simple hardcoded solution may work for some people.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.