Webpack upgrade from 3 to 4

Sujaan Singh
5 min readMar 31, 2019

What is webpack?

Before starting with an upgrade to v4, let’s discuss the high-level definition of webpack. I promise this won’t take much time.

Webpack is a tool. Helps in creating browser readable bundle.js* file.

For React component lovers, a simple analogical explanation is given in the diagram.

  1. Webpack(component) takes a webpack.config.js file as a props
  2. Builds a dependency-graph(D-Graph) and keeps in its state
  3. Uses inbuilt templates similar to JSX, to generate the bundled file(s).

Above definition is not enough. We have to dive deep into webpack before starting the upgrade.

Webpack from inside out.

From inside webpack do lots of things but in a simple way.

Above diagram is worth a thousand words. I want to skip writing about obvious things. I will explain a few important parts and flows in this diagram, to help in upgrading.

// Webpack ignition 
webpack --config webpack.config.js

This will start the Webpack engine. The engine will start with few creations and processes.

  1. Create compiler:
// file path: node_modules/webpack/lib/webpack.jscompiler = new Compiler(options.context);

2. Create compilation:

// FIle path: node_modules/webpack/lib/Compiler.jscreateCompilation() {return new Compilation(this);}

3. Create resolver

//File path: node_modules/webpack/lib/Compiler.jsthis.resolverFactory = new ResolverFactory();

Now go to these code path and see above diagram make sense to you or not.

I am sure you have noticed an anonymous element named “Tapable”

class Compiler extends Tapable

What is it? Read here

Now check constructors of “Compiler”, “Compilation” and “ResolverFactory”. You will find “this.hooks” property. This is the new format for “Plugin” in webpack 4.

This is all prerequisite for the webpack 4 upgrades.

Now start with Upgrade

Following types of changes that need to be done.

  • Mode
  • Plugins
  • The remaining upgrade will be done by “run and check

Let’s start from the beginning

npm i webpack webpack-cli --save-dev
or
yarn add webpack webpack-cli --dev

Now stop. Check what are the warnings in the console. Warnings are important. The warning will be saying about peer dependencies. Fix them.

npm WARN webpack-dev-server@2.11.3 requires a peer of webpack@^2.2.0 || ^3.0.0 but none is installed. You must install peer dependencies yourself.

Above warning, need webpack-dev-server to upgrade, not to install its peer dependencies.

Check webpack version in package.json. If it still not the latest try above commands with @latest. Use @latest for plugins which are not getting updated.

Now, all the peer dependencies have been resolved and status is webpack@v4.*.* and webpack-cli@3.*.*

Next, add mode in config: Webpack 4 has introduced two modes. - “development” and “production”.

Two ways to set.

//package.json"build": "webpack --config config/webpack.dev.config.js --mode development"

or

//webpack.config.dev.js
{
entry: {},
mode: "development"
}

Similar for production mode (in webpack.config.prod.js)

Plugins: Following plugins have been removed from webpack 4. There can be more.

  • NoEmitOnErrorsPlugin
  • ModuleConcatenationPlugin
  • NamedModulesPlugin
  • CommonsChunkPlugin
  • WebpackMd5Hash
  • UglifyJsPlugin

But how to use them now? Now onward, configurations of these plugins should go inside the key optimization in webpack.config

{
...
plugins:[
// new webpack.NamedModulesPlugin()
//new webpack.optimize.CommonsChunkPlugin({
// name: 'vendor',
// minChunks: Infinity,
//}),
//new WebpackMd5Hash(),
]
optimization: {
namedModules: true, //NamedModulesPlugin()
splitChunks: { // CommonsChunkPlugin()
name: 'vendor',
minChunks: 2
},
}
...
}

Note: I am also going to cover custom plugins upgrade to V4.

Run and Check:

Add following line to your webpack.config.js file.

process.traceDeprecation = true;

Execute “npm run start” which will run “webpack — config webpack.config.js”

Now onward strategy will be read console warnings and errors, solve them, and move ahead. My project upgrade was not smooth. I faced many warnings and errors. Few errors were straight forward. I had to read them and the solution was there. And few demanded my attention.

Error:

Error: webpack.optimize.UglifyJsPlugin has been removed, please use config.optimization.minimize instead.

Warnings:

DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
DeprecationWarning: Tapable.apply is deprecated. Call apply on the plugin directly instead

How to investigate? Check traces. They will tell you witch node_module/[[module]] raised error or warning.

How to solve them? Check for that [[module]] latest version. Updrage it

npm i [[module]]@latest --save

I was still getting errors and warnings.

How did I solve? I don’t remember exactly. But I can give you some pieces of advice

  • If you are using HtmlWebpackPlugin, then add it at the 0th index of plugins.
...
plugins[
new HtmlWebpackPlugin(),
new OtherPlugins(),
...
]
...

why this is? In short, order matters in plugins in Webpack 4.

  • Just use [contenthash] in your output names, and remove webpack_md5_hash plugin, for following warning
DeprecationWarning: Tapable.plugin is deprecated. Use new API on .hooks instead
webpack-md5-hash\plugin\webpack_md5_hash.js:29:14)
  • awesome-typescript-loader upgrade to ^5.*.*.*

How to update my custom webpack plugins?

You have gone through prerequisites, so you know compiler and compilation hooks. Keep in mind, wherever we were using the plugin, we have to translate that to hooks.

People in webpack tried hard to make it easy to upgrade the webpack plugin. So you have to learn their tricks.

[anything].plugin(‘this-is-old-way-plugin’,(anything, [optional_callback]))

Translate above old way to plugin to webpack 4 using the following rules:

  • [anything].plugin(‘this-is-old-way-plugin’ …to……[anything].hooks.thisIsOldWayPlugin
  • Now check for optional_callback. If it is there then use “tap” otherwise asyncTap
  • Still not working? Use the almighty console.log
console.log(`Expose your APIs ${JSON.stringify([anything].hooks)}`)

Example:

//Old
compiler.plugin('compilation', (compilation) => {
compilation.plugin('html-webpack-plugin-before-html-generation', (htmlPluginData, callback) => {
.....
}
//new way
compiler.hooks.compilation.tap('compilation', (compilation)=>{
compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration.tapAsync("any_name", (htmlPluginData, callback) => {
.....
})
}

Make your plugin webpack 3 compatible so that you don’t have to change the major version of your plugin

if(compiler.hooks) {
compiler.hooks.afterEmit.tapAsync('name', afterEmit);
} else {
compiler.plugin('after-emit', afterEmit);
}

If your plugin is using plugins of compiler or compilation, just remember now onward everything is “tapable”. Console log their APIs and use them. Following line consumed my time.

//Old
const
mainTemplate = compilation.mainTemplate;
mainTemplate.plugin("require-ensure", callback)
...
mainTemplate.applyPluginsWaterfall("asset-path", ...)
...
//New
mainTemplate.hooks.requireEnsure.tap("requireEnsurePlugin", callback)
....
compilation.mainTemplate.hooks.assetPath.call(...)
...

I hope you got a good idea about how to approach the problems.

Upgrade to babel 7

If you want to upgrade babel as well, follow https://github.com/babel/babel-upgrade

It was simple and smooth for me, hope it will be for you as well. Now my .babelrc has been changed to

//old
{"presets":["react","es2015","stage-0"],"plugins":["syntax-dynamic-import"]}
//new
{"presets": ["@babel/react", "@babel/preset-env"],"plugins": [ "@babel/plugin-syntax-dynamic-import"]}

Finally

You must have found this useful, “Press clap”.

--

--