Reading a Webpack config and pulling out your hair trying to figure out WTF it does? You’re not alone. That’s pretty much everyone that has ever read a Webpack config.

The most complicated part is the loaders section. Part of the reason it is so complicated: there are many many different ways to write the same config.

Webpack 2 was just released. It’s a great product with tons of awesome features - but unfortunately it introduced even more ways to configure loaders.

But there’s hope - once you understand that many options are the same as others, you’ll realize there is a lot less to learn than you thought. So let’s dive in. 💦



Two important terms before we get started:

rule: A rule specifies how files that meet certain criteria should be treated. For instance you might have a rule that says ”.js files should be run through the babel-loader”. You can read more about rules in the official docs.

loader: A loader is basically a webpack plugin. A loader processes files. Babel-loader is the most famous loader - it converts ES6+ into ES5. You can read more about loaders in the official docs.

Every rule has one or more loaders.

Four Key Changes to Loader Config

Before getting into all the different ways that loader configs can be written - you need to know about four key ways in which loader configuration has changed in Webpack 2:

1. module.rules and module.loaders are interchangeable

rules is the new & recommended keyword but loaders will still be accepted. Using it will NOT generate a warning unfortunately.

These are equivalent:

// rules way 
module: {
  rules: [
// loaders way 
module: {
  loaders: [

2. use and loaders are interchangeable

Within a rule, use or loaders can specify the list of loaders. use is the new keyword but loaders can still be used and will not generate a warning.

These are equivalent:

{ test: /\.js$/, use: ['babel-loader'] }
{ test: /\.js$/, loaders: ['babel-loader'] }

3. New object syntax for writing loaders

This is very helpful for configuring loaders. In Webpack 1 CSS modules were configured like this:


But now they can be configured like this:

  loader: 'css-loader',
  options: {
    modules: true,
    importLoaders: 1,
    localIdentName: '[name]_[local]_[hash:base64:5]',

4. -loader is now required

All loader names must use the full package name.

In Webpack 1 babel-loader could be written as babel. Webpack 2 will fail and show an error you try to use a loader without writing its full name. This is awesome, but it’s the only place where Webpack 2 actually made an old style of config obsolete.

BTW - to see more about what changed in Webpack 2, see the official migration guide. It is very good.

Loader Config Variations

Now that we’ve covered some Webpack 2 updates, let’s dig in to a bunch of equivalent configurations. Each of these sections discusses several ways of writing the same list of loaders for a rule.

1. Rule with a single loader - no options

There are at least 7 equivalent ways to specify a single loader without a configuration. I’ve placed emoji next to each one to let you know how they make me feel. 🤓

1.1 "loader" string 👍

loader: 'babel-loader'

Very succinct. Same syntax as Webpack 1.

1.2 "use" array of strings 👍

use: ['babel-loader']

New array syntax, still pretty readable and succinct.

1.3 "use" array of objects 🙃

use: [{ loader: 'babel-loader' }]

This is starting to get a little verbose. This object syntax is new in Webpack 2. It is most useful when you want to add options to your loader (see below).

1.4 "use" string 👎

use: 'babel-loader'

The docs specifically say NOT to do this one. I was surprised that it works!

1.5 "loader" object 🙃

loader: { loader: 'babel-loader' }

This might be a good way to go if you anticipate adding some options to that object.

1.6 "loader" array of strings 👎😡

loaders: ['babel-loader']

Remember that loaders is a Webpack 1 property and is no longer recommended by the maintainers.

1.7 "loader" array of objects 👎👹

loaders: [{ loader: 'babel-loader' }]

Again, loaders is deprecated so please do not use.

2. Rule with a single Loader - with options

There are at least 3 equivalent ways to specify a single loader with a configuration.

2.1 Object syntax 👍

loader: {
  loader: 'babel-loader',
  options: { presets: ['es2016'] },

This is the format that the documentation recommends, so I use it despite the confusing bit where there are two properties named loader. The options property is new in Webpack 2 and lets us write our loader options in a much more readable way then the next ones:

2.2 String syntax 👎

loader: 'babel-loader?presets[]=es2015'

Leftover Webpack 1 syntax. I can’t think of any reason to use this old format.

2.3 Query syntax 👎

loader: 'babel-loader',
query: {
  presets: ['es2016']

This is another leftover Webpack 1 syntax. It’s not great because if you add additional loaders it is not clear which loader the query options apply to.

There are many more ways because any of the above could be changed into the array format with either loaders or use.

3. Rule with multiple loaders - no options

There are at least 3 ways to specify multiple loaders when none of them require configuration.

3.1 “use” strings 👍

use: [

This is the most succinct way.

3.2 “use” objects 👍

use: [
  { loader: 'style-loader' },
  { loader: 'css-loader' },

You can mix and match this one with the strings approach above.

3.3 “loader” string chain 👎 😡

loader: 'style-loader!css-loader'

This is the Webpack 1 way, and it is still supported. I find it less readable than either of the above ways.

Again, there are more variations where the deprecated loaders property is written in place of use.

4. Rule with multiple loaders - with options

Here are two ways to configure rules with multiple loaders, in which some of the loaders need to be configured.

4.1 Object configuration 👍

use: [
    loader: 'css-loader',
    options: {
      modules: true,
      importLoaders: 1,
      localIdentName: '[name]_[local]_[hash:base64:5]',

The options property is new in Webpack 2 and allows you to specify loader options in a more readable way.

4.2 String configuration 👎😡

use: [

This is another Webpack 1-style config. It’s harder to read than the object version.

For more variations you could imagine that style-loader might be replaced with the object format, or use could be replaced with loaders. Note that some loader options might contain functions, in which case the string configuration is not an option.


Webpack 2 is an awesome product with a great feature set. But I believe there are far too many ways to write the config.

The worst part is that Webpack 2 doesn't show any errors/warnings when you use deprecated properties and config formats. I'm sure this will be added eventually, but it can be a pain in the meantime.

When you read configs written by others, you can expect to find any of the above odd/weird formats. Try to use the recommended properties yourself, but be aware that others may not do so. Keep this in mind when you read configs in starter projects, open source projects, etc.

Hopefully once Webpack 2 has been out for a while, some of the older config options will no longer be supported.