webpack-config-assign

What it is

A utility to merge separate webpack configs (e.g. webpack.config.prod.js, webpack.config.development.js, etc.) in a smarter way than a simple merge or assign.

What it does

It looks at each configuration key passed in (e.g. plugins, module, entry, etc.) and if it can merge it intelligently, the utility attempts to do so. For instance, consider the following scenarios:

This plugin is able to intelligently merge each of the above, in essence, in concatenates plugins, and intelligently merges loaders when the test matches.

How to use

The utility can be seamlessly integrated into an existing workflow, particularly one that has already been segmented out into environment configs. However, if using a monolithic webpack.config.js with a bunch of environment checks, then some work will need to be done to separate these out into their respective environments/phases.

Consider the following "base" config (configuration that will be shared between dev, test, and/or production). I like to name it webpack.config.base.js, but it can just as easily be inlined into webpack.config.js:

const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const path = require('path');
const pkg = require(path.resolve('./package.json'));

module.exports = {
  devtool: 'source-map',
  entry: {
    [pkg.name]: [
      './src/index'
    ],
    vendor: Object.keys(pkg.dependencies)
      .filter((dep) => {
        return ['normalize.css'].indexOf(dep) === -1;
      })
  },
  output: {
    filename: '[name].js',
    path: path.join(__dirname, './dist/')
  },
  module: {
    rules: [
      {
        test: /\.js(x)?$/,
        include: [path.join(__dirname, 'src')],
        use: [
          'react-hot-loader/webpack',
          'babel-loader'
        ]
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              modules: true
            }
          },
          'postcss-loader'
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: path.join(__dirname, 'dist', 'index.html'),
      inject: 'body',
      template: path.join(__dirname, 'src', 'index.html')
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: Infinity
    }),
    new webpack.LoaderOptionsPlugin({
      options: {
        context: __dirname,
        postcss: [
          require('postcss-nested'),
          require('postcss-cssnext')({
            browsers: ['ie >= 9', 'last 2 versions']
          })
        ]
      }
    }),
    new webpack.NoErrorsPlugin(),
    new webpack.NamedModulesPlugin(),
    new webpack.DefinePlugin({
      'process.env': { 
         NODE_ENV: JSON.stringify(process.env.NODE_ENV) 
       }
    })
  ]
};

the above is a fairly typical config, and this could be dropped into webpack.config.js quite easily. However, what if we want super speedy reload times, webpack-dev-server, and so on and so forth in development. Well, let's create a webpack.config.development.js, like so:

const DashboardPlugin = require('webpack-dashboard/plugin');

module.exports = {
  devtool: 'eval',
  entry: {
    [pkg.name]: [
      'webpack-dev-server/client?http://localhost:3000',
      'webpack/hot/only-dev-server',
      'react-hot-loader/patch',
      './src/index'
    ]
  },
  plugins: [
    new DashboardPlugin()
  ]
};

now we're cooking. We are using the excellent webpack-dashboard plugin, and we're also doing some nice webpack hot reloading with the webpack dev server. Now, let's get to merging those configurations

const webpackConfigAssign = require('webpack-config-assign');

const devConfig = process.env.NODE_ENV === 'development' ? require('./webpack.config.development') : {};

module.exports = webpackConfigAssign(require('./webpack.config.base'), devConfig);

and like that, we're done. Easy as pie! Let's wrap up what specifically happened: