On this page

The sections below walk through the major changes between webpack 1 and webpack 2.

[!TIP] Far fewer things changed between webpack 2 and 3, so that upgrade should be relatively painless. If you do run into trouble, consult the changelog for the details.

These three options were consolidated into a single resolve.modules option. See resolving for usage.

  resolve: {
-   root: path.join(__dirname, "src")
+   modules: [
+     path.join(__dirname, "src"),
+     "node_modules"
+   ]
  }

You no longer need to pass an empty string in this option. That behavior moved to resolve.enforceExtension. See resolving for usage.

Several APIs changed here. They are not documented in detail because they are rarely used. See resolving for the specifics.

The old loader configuration has been superseded by a more capable rules system that can configure loaders and much more. For backward compatibility the old module.loaders syntax remains valid and the old property names are still parsed, but the new naming conventions are clearer and worth adopting by switching to module.rules.

  module: {
-   loaders: [
+   rules: [
      {
        test: /\.css$/,
-       loaders: [
-         "style-loader",
-         "css-loader?modules=true"
+       use: [
+         {
+           loader: "style-loader"
+         },
+         {
+           loader: "css-loader",
+           options: {
+             modules: true
+           }
+         }
        ]
      },
      {
        test: /\.jsx$/,
        loader: "babel-loader", // Do not use "use" here
        options: {
          // ...
        }
      }
    ]
  }

As in webpack 1, loaders can be chained so that the output of one is passed to the next. With the rule.use option, use accepts an array of loaders. In webpack 1, loaders were typically chained with !; that style is now only supported through the legacy module.loaders option.

  module: {
-   loaders: [{
+   rules: [{
      test: /\.less$/,
-     loader: "style-loader!css-loader!less-loader"
+     use: [
+       "style-loader",
+       "css-loader",
+       "less-loader"
+     ]
    }]
  }

You can no longer omit the -loader suffix when referencing loaders:

  module: {
    rules: [
      {
        use: [
-         "style",
+         "style-loader",
-         "css",
+         "css-loader",
-         "less",
+         "less-loader",
        ]
      }
    ]
  }

You can still opt back in to the old behavior with the resolveLoader.moduleExtensions option, though it is not recommended.

+ resolveLoader: {
+   moduleExtensions: ["-loader"]
+ }

See #2986 for the reasoning behind this change.

When no loader is configured for a JSON file, webpack automatically loads it with json-loader.

  module: {
    rules: [
-     {
-       test: /\.json/,
-       loader: "json-loader"
-     }
    ]
  }

This change was made to smooth out the differences in environment behavior between webpack, Node.js, and Browserify.

In webpack 1, configured loaders resolved relative to the matched file. In webpack 2, they resolve relative to the context option instead.

This fixes duplicate-module problems caused by loaders when using npm link or referencing modules outside the context. As a result, you can drop any workarounds you added for that:

  module: {
    rules: [
      {
        // ...
-       loader: require.resolve("my-loader")
+       loader: "my-loader"
      }
    ]
  },
  resolveLoader: {
-   root: path.resolve(__dirname, "node_modules")
  }
  module: {
-   preLoaders: [
+   rules: [
      {
        test: /\.js$/,
+       enforce: "pre",
        loader: "eslint-loader"
      }
    ]
  }

The sourceMap option of UglifyJsPlugin now defaults to false rather than true. If you want source maps for minified code, or correct line numbers in uglifyjs warnings, set sourceMap: true explicitly.

  devtool: "source-map",
  plugins: [
    new UglifyJsPlugin({
+     sourceMap: true
    })
  ]

The compress.warnings option of UglifyJsPlugin now defaults to false rather than true. To see uglifyjs warnings, set compress.warnings to true.

  devtool: "source-map",
  plugins: [
    new UglifyJsPlugin({
+     compress: {
+       warnings: true
+     }
    })
  ]

UglifyJsPlugin no longer switches loaders into minimize mode. In the long term, minimize: true must be passed through loader options instead, so check each loader's documentation for the relevant settings.

The minimize mode for loaders will be removed in webpack 3 or later.

For compatibility with older loaders, you can still toggle minimize mode through a plugin:

  plugins: [
+   new webpack.LoaderOptionsPlugin({
+     minimize: true
+   })
  ]

webpack.optimize.DedupePlugin is no longer needed. Remove it from your configuration.

BannerPlugin no longer accepts two arguments; it takes a single options object.

  plugins: [
-    new webpack.BannerPlugin('Banner', {raw: true, entryOnly: true});
+    new webpack.BannerPlugin({banner: 'Banner', raw: true, entryOnly: true});
  ]

OccurrenceOrderPlugin is now enabled by default and has been renamed (it was OccurenceOrderPlugin in webpack 1). Remove it from your configuration:

  plugins: [
    // webpack 1
-   new webpack.optimize.OccurenceOrderPlugin()
    // webpack 2
-   new webpack.optimize.OccurrenceOrderPlugin()
  ]

ExtractTextPlugin requires version 2 to work with webpack 2.

The configuration changes for this plugin are mostly syntactical.

module: {
  rules: [
    {
      test: /.css$/,
-      loader: ExtractTextPlugin.extract("style-loader", "css-loader", { publicPath: "/dist" })
+      use: ExtractTextPlugin.extract({
+        fallback: "style-loader",
+        use: "css-loader",
+        publicPath: "/dist"
+      })
    }
  ]
}
new ExtractTextPlugin({options}): void
plugins: [
-  new ExtractTextPlugin("bundle.css", { allChunks: true, disable: false })
+  new ExtractTextPlugin({
+    filename: "bundle.css",
+    disable: false,
+    allChunks: true
+  })
]

A dependency consisting solely of an expression (for example require(expr)) now creates an empty context rather than the context of the entire directory.

Code like this should be refactored, since it will not work with ES2015 modules. If refactoring is not possible, you can use ContextReplacementPlugin to hint the compiler toward the correct resolution.

[!NOTE] Link to an article about dynamic dependencies.

If you previously misused the CLI to pass custom arguments into your configuration like this:

you will find that it is no longer permitted, since the CLI is now stricter. There is instead a dedicated interface for passing arguments into the configuration, which you should use; future tooling may depend on it.

See CLI.

These functions are now always asynchronous, rather than invoking their callback synchronously when the chunk is already loaded.

[!WARNING] require.ensure now depends on native Promises. If you use require.ensure in an environment that lacks them, you will need a polyfill.

You can no longer configure a loader with a custom property in webpack.config.js; it must be done through options. The following configuration, which uses a top-level ts property, is no longer valid in webpack 2:

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
      },
    ],
  },
  // does not work with webpack 2
  ts: { transpileOnly: false },
};

Good question. Strictly speaking, options refers to two possible things, both ways of configuring a webpack loader. Historically options was called query, and was a string appended to the loader name, much like a query string but with greater capabilities:

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: `ts-loader?${JSON.stringify({ transpileOnly: false })}`,
      },
    ],
  },
};

Alternatively, it can be a separate object supplied alongside the loader:

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        options: { transpileOnly: false },
      },
    ],
  },
};

Some loaders need contextual information and read it from the configuration. In the long term, this should be passed through loader options, so check each loader's documentation for the relevant settings.

For compatibility with older loaders, the information can still be passed through a plugin:

  plugins: [
+   new webpack.LoaderOptionsPlugin({
+     options: {
+       context: __dirname
+     }
+   })
  ]

In webpack 1, the debug option switched loaders into debug mode. In the long term, this should be passed through loader options, so check each loader's documentation for the relevant settings.

The debug mode for loaders will be removed in webpack 3 or later.

For compatibility with older loaders, you can still switch them into debug mode through a plugin:

- debug: true,
  plugins: [
+   new webpack.LoaderOptionsPlugin({
+     debug: true
+   })
  ]

In webpack 1, you could use require.ensure() to lazily load chunks of your application:

require.ensure([], require => {
  const foo = require('./module');
});

The ES2015 loader spec defines import() as the way to load ES2015 modules dynamically at runtime. webpack treats import() as a split point and places the requested module in a separate chunk. import() takes the module name as its argument and returns a Promise.

function onClick() {
  import('./module')
    .then(module => module.default)
    .catch(err => {
      console.log('Chunk loading failed');
    });
}

A welcome benefit: because loading is now Promise-based, you can handle the failure to load a chunk.

You can pass a partial expression to import(). This works much like expressions in CommonJS: webpack creates a context containing all possible files, and import() produces a separate chunk for each possible module.

function route(path, query) {
  return import(`./routes/${path}/route`).then(route => new route.Route(query));
}
// This creates a separate chunk for each possible route

As with AMD and CommonJS, you can freely mix all three module types, even within a single file. webpack behaves much like Babel and node-eps here:

// CommonJS consuming ES2015 Module
const book = require('./book');

book.currentPage;
book.readPage();
book.default === 'This is a book';

Note that you will want to tell Babel not to parse these module symbols, so that webpack can use them. Do this by adding the following to your .babelrc or babel-loader options.

{
  "presets": [["es2015", { "modules": false }]]
}

Nothing here is required, but each is an opportunity to improve your setup.

webpack now supports template strings in expressions, so you can use them in webpack constructs:

- require("./templates/" + name);
+ require(`./templates/${name}`);

webpack now supports returning a Promise from the configuration file, which allows asynchronous processing during configuration.

module.exports = function () {
  return fetchLangs().then(lang => ({
    entry: '...',
    // ...
    plugins: [new DefinePlugin({ LANGUAGE: lang })],
  }));
};

webpack now supports more ways to match loaders.

module.exports = {
  // ...
  module: {
    rules: [
      {
        resource: /filename/, // matches "/path/filename.js"
        resourceQuery: /^\?querystring$/, // matches "?querystring"
        issuer: /filename/, // matches "/path/something.js" if requested from "/path/filename.js"
      },
    ],
  },
};

Several new CLI options are available:

  • --define process.env.NODE_ENV="production" — see DefinePlugin.
  • --display-depth — displays the distance from the entry point for each module.
  • --display-used-exports — displays which exports are used in a module.
  • --display-max-modules — sets how many modules appear in the output (defaults to 15).
  • -p — now also defines process.env.NODE_ENV as "production".

The following changes are relevant only to loader authors.

Loaders are now cacheable by default. A loader must opt out if it is not cacheable.

  // Cacheable loader
  module.exports = function(source) {
-   this.cacheable();
    return source;
  }

webpack 1 supports only JSON.stringify-able options for loaders, whereas webpack 2 supports any JavaScript object as loader options.

Before webpack 2.2.1 (that is, from 2.0.0 through 2.2.0), complex options required an ident on the options object so that it could be referenced from other loaders. This was removed in 2.2.1, so current migrations do not need the ident key.

{
  test: /\.ext/
  use: {
    loader: '...',
    options: {
-     ident: 'id',
      fn: () => require('./foo.js')
    }
  }
}