Technical, webpack

webpack 4 – How to have separate build outputs for production and development

A pretty common use case is wanting to have different configs for development and production for front end builds. For example, when running in development mode, I want sourcemaps generated, and I don’t want any of my JavaScript or CSS to be minified to help with my debugging.

Webpack 4 introduced a new build flag that is supposed to make this easier – “mode”. Here’s how it’s supposed to be used:

webpack --mode production

This actually won’t be much help if you want to get different build outputs. Whilst this flag will be picked up by Webpack’s internals and will produced minified JavaScript, it won’t help if you want to do something like conditionally include a plugin.

For example, just say I have a plugin to build my SCSS, if I want to minify and bundle my generated CSS into a single file, the best way to do it is to use a plugin – OptimizeCssAssetsPlugin. It would be great if I could detect this mode flag at build time, and conditionally include this plugin if I’m building in production mode. The goal is that in production mode, my generated CSS gets bundled and minified, and in development mode, it doesn’t.

In your webpack.config.js file, it’s not possible to detect this mode flag, and to then conditionally add the plugin based on this flag. This is because the mode flag can only be used in the DefinePlugin phase, where it is mapped to the NODE_ENV variable. The configuration below will make a global variable “ENV” available to my JavaScript code, but not to any of my Webpack configuration code:

module.exports = {
  ...
  plugins: [
    new webpack.DefinePlugin({
      ENV: JSON.stringify(process.env.NODE_ENV),
      VERSION: JSON.stringify('5fa3b9')
    })
  ]
}

Tying to access process.env.NODE_ENV outside of the DefinePlugin phase will return undefined, so we can’t use it. In my application JavaScript, I can use the “ENV” and the “VERSION” global variables, but not in my webpack config files themselves.

The Solution

The best solution even with webpack 4, is to split your config files. I now have 3:

  • webpack.common.js – contains common webpack configs for all environments
  • webpack.dev.js – contains only the dev config settings (e.g. source map gens)
  • webpack.prod.js – contains only prod config, like the OptimizeCssAssetsPlugin

The configs are merged together using the webpack-merge package. For example, here’s my webpack.prod.js file:

const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = merge(common, {
  plugins: [
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.css$/g,
      cssProcessor: require('cssnano'),
      cssProcessorPluginOptions: {
        preset: ['default', { discardComments: { removeAll: true } }],
      },
      canPrint: true
    })
  ]
});

You can then specify which config file to use when you call webpack. You can do something like this in your package.json file:

{
  ...
  "scripts": {
    "build": "webpack --mode development --config webpack.dev.js",
    "build-dist": "webpack --mode production --config webpack.prod.js"
  }
}

Some further information on using the config splitting approach can be found on the production page in the webpack documentation.

Leave a Reply

Your email address will not be published. Required fields are marked *