Composing Webpack Configs

Today's our last day with our friend Webpack, and anyone who's ever worked with it on anything non-trivial will tell you how quickly the config file blows up to hundreds of lines. But it doesn't have to be this way! Webpack config files are just Node.js modules that export a configuration object. That means you have all the expressive power of a full programming language to compose your files into manageable pieces.

There are two main ways to divide up your config: by environment and by concern. These strategies are not mutually exclusive, though the former is much more common that the latter.

Composing by Environment

Let's look at an example of breaking up your config file by environment. This method is taken pretty much directly from the docs. In this case, you can think of the elements of your configuration in three groups. There are the elements that are specific to your development environment, those that are specific to production, and those that are common to both.

You'll start by creating the file that holds common config options:

/**
  * webpack.common.js
  * This file holds things that don't change between environments
  */
module.exports = {
  entry: /*...*/,
  output: /*...*/
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin(),
  ]
}

You can use a tool called webpack-merge, to combine the common config with environment-specific configs:

// webpack.prod.js
const { merge } = require('webpack-merge')
const common =  require('./webpack.common.js')

// the defauts in production mode are pretty good. We'll just use those for now,
// and merge the mode parameter with the common config
module.exports = merge(common, {
  mode: 'production',
})

And likewise we can create a dev config:

// webpack.dev.js
const { merge } = require('webpack-merge')
const common =  require('./webpack.common.js')

module.exports = merge(common, {
  mode: 'development',
  devtool: 'eval-source-map',
  devServer: {
    contentBase: './dist'
  }
})

When you have multiple config files, you can pass them to webpack using the -c/--configuration option, like so:

webpack -c webpack.prod.js

Composing by Concern

The example above works well, but the common config still has a tendency to get pretty bloated. You can break that file up further by extracting pieces that are logically connected into their own configs, and merging them into the common config. For instance, you could pull the plugins and loader rules related to CSS into their own file, the configuration for your dev server into its own file, etc. You don't even need to extract them to their own files if you don't want. In the example below, I've grouped configuration options into logically related objects, then merged them at the end. It's up to you whether you want to move those configs to their own files or keep them together in the common file.

// webpack.common.js
const { merge } = require('webpack-merge')

const baseConfig = {
  entry: {
    index: path.resolve(__dirname, 'src/index.jsx'),
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: "[name].[contenthash].js"
  },
  plugins: [
    new CleanWebpackPlugin(),
  ],
}

const cssConfig = {
  module: {
    rules: [
      {
        test: /\.css$/,
        exclude: /[\\/]static[\\/]/,
        use: [ MiniCssExtractPlugin.loader, 'css-loader' ]
      },
    ]
  },
  plugins: [
    new MiniCssExtractPlugin()
  ]
}

const staticConfig = {
  module: {
    rules: [
      {
        test: /\.html$/,
        use: ['html-loader']
      },
      {
        test: /[\\/]static[\\/]/,
        type: 'asset/resource'
      },
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: "index.html",
      template: "src/index.html",
      chunks: ["index"],
    }),
  ],

}

const optConfig = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 1,
      name: 'chunk',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendor'
        }
      }
    }
  }
}


module.exports = merge(
  baseConfig,
  staticConfig,
  cssConfig,
  optConfig,
)

Next Up:
I'm not good about reading documentation

Previously:
Making Sense of Your Bundle with Webpack Sourcemaps


Want to impress your boss?

Useful articles delivered to your inbox. Learn to to think about software development like a professional.

Icon