Organising webpack configuration for different environments

A common question is how best to organise webpack config files, especially when it comes to different environments. In this post I'll share a pattern that works well and allows scaling to as many environments as needed.

A file per environment

The simplest way to start is with a webpack directory that contains a base configuration and then an additional configuration for each environment:

webpack
├── base.config.js
├── dev.config.js
└── prod.config.js

The base

As you could expect, this file will contain settings that are common across all environments in your application. Good candidates are entry files, plugins and loaders.

base.config.js

const webpack = require('webpack');

module.exports = {
  entry: {
    app: './src/app',
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },
    ],
  },

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

Extending for different for environments

From here we can make use of webpack-merge to extend the base configuration.

Common settings in development could be source maps, a dev server or different settings for file loaders.

dev.config.js

const merge = require('webpack-merge');
const baseConfig = require('./base.config.js');

module.exports = merge(baseConfig, {
  devtool: 'eval-source-map',

  devServer: {
    inline: true,
    contentBase: 'src',
    port: '3001',
  },

  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader?importLoaders=1',
        ],
      },
    ],
  },
});

In production it could include modification of assets, extracting CSS into a separate file and outputting the chunks to a build directory.

prod.config.js

const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const merge = require('webpack-merge');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const baseConfig = require('./base.config.js');

module.exports = merge(baseConfig, {
  output: {
    path: 'build',
    filename: '[name].bundle.[chunkhash].js',
  },

  module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          use: [
            'css-loader',
          ],
        }),
      },
    ],
  },

  plugins: [
    // Extract imported CSS into own file
    new ExtractTextPlugin('[name].bundle.[chunkhash].css'),
    // Minify JS
    new UglifyJsPlugin({
      sourceMap: false,
      compress: true,
    }),
    // Minify CSS
    new webpack.LoaderOptionsPlugin({
      minimize: true,
    }),
  ],
});

Running webpack in different environments

With webpack and webpack-dev-server installed as dependencies in your project it is a simple case of adding two npm scripts to package.json:

"scripts": {
  "build": "NODE_ENV=production webpack --config webpack/prod.config.js",
  "start": "NODE_ENV=development webpack-dev-server --config webpack/dev.config.js",
  "deploy": "npm run build && <some build script>"
}
npm run build
npm run start

A working example

I used this setup on github-user-search to run the site locally and deploy it to github pages.

Further reading can be found in the webpack documentation.