This will make you more efficient at debugging Webpack unspecified build errors
In this article we'll build a very primitive plugin for Webpack and then I'll show you how to find out if this plugin triggers error by debugging Webpack build.

This will make you more efficient at debugging Webpack unspecified build errors
In this article we'll build a very primitive plugin for Webpack and then I'll show you how to find out if this plugin triggers error by debugging Webpack build.


Recently I’ve been hooking up numerous plugins to our Webpack based build system. As it currently stands most of the plugins lack good documentation which inevitably leads to misconfiguration and consequently build time errors. Of course this is part of the learning process and shouldn’t be a big problem by itself. However, the big obstacle is that at the moment, Webpack doesn’t exactly tell you where the error occurs. And so I often ended up with the following unspecified errors:


Webpack does its job and reports the error, but doesn’t give you any clue as to where exactly this error comes from. Debugging this error can literally take days if you don’t know where to look. You’d have to remove plugins one by one to identify the plugin that causes the error. That’s very time consuming and inefficient.
This article will show a simple way to very quickly identify the culprit for the error. It’s a very useful technique that potentially can save you days of the effort. It worked extremely well for me when I was trying to configure ngtools/webpack AOT plugin to be used outside of angular-cli
. I believe it will help you as well.
HelloWorldCheckerPluginLink to this section
Before I show you how to debug the error, let’s first see how these unspecified errors might occur. For that we will write a simple plugin ourselves that takes a path to a file as an option and checks whether this file contains a Hello World!
string. It then simply logs the result of the check to the console.
<>Copyplugins: [ new HelloWorldCheckerPlugin({path: 'toinspect.txt'}) ]
The plugin seems to be very easy to configure. But, suppose the author of the plugin forgot to mention that the plugin expects the absolute path to the file and not the relative path. It’s very common to first write a plugin for one’s own usage and afterwards make it public on Github. And as a result the documentation, if any, may not contain something that the author knows and assumes is obvious.
The plugin seems to be very easy to configure but suppose the author of the plugin forgot to mention that the plugin expects the absolute path to the file, not relative. It’s very common to first write a plugin for one’s own usage and afterwards make it public on github. And as a result the documentation, if any, may not contain something that the author knows and assumes is obvious.
So here is how our plugin implementation:
<>Copyconst fs = require('fs'); const path = require('path'); class HelloWorldCheckerPlugin { constructor(options) { this.options = options; } apply(compiler) { compiler.plugin('make', (compilation, cb) => this._make(compilation, cb)); } _make(compilation, cb) { try { const file = fs.readFileSync(path.resolve('/', this.options.path), 'utf8'); if (file.includes('Hello World!')) { console.log(`The file ${this.options.path} contains 'Hello World!' string`); } else { console.log(`The file ${this.options.path} doesn't contain 'Hello World!' string`) } cb(); } catch (e) { compilation.errors.push(e); cb(); } } } exports.HelloWorldCheckerPlugin = HelloWorldCheckerPlugin;
Here we simply hook into make
stage of compilation process and try to perform the check in the _make
method. And this line shows that current implementation expects an absolute pat to the file:
<>Copyfs.readFileSync(path.resolve('/', this.options.path), 'utf8');
Now suppose you downloaded this plugin and configure it in the following way specifying the relative path:
<>Copyconst HelloWorldCheckerPlugin = require('./plugin').HelloWorldCheckerPlugin; const path = require('path'); module.exports = { entry: "./main", output: { path: __dirname + "/dist", filename: "bundle.js" }, plugins: [ new HelloWorldCheckerPlugin({path: 'toinspect.txt'}) ] };
And when you run the webpack and you get the unspecified error:


It might be easy to realize that the error is in the HelloWorldCheckerPlugin
. However, imagine:
- the less descriptive error like the one we’ve seen in the introduction
- you have 10+ plugins configured and some of them use
toinspect.txt
file - the file
toinspect.txt
is not only used in the plugin but is also required by a bunch of JS files in the bundle
Now suddenly you’re stuck. It’s not even clear where to start the debugging. It’d be nice if webpack provided at least the file name where the error occurred. But as we will see soon it’s not really possible to do that for webpack in its current implementation and so as I understand it relies on an author of a plugin to provide all the required error details to troubleshoot the problem.
Compilation errorsLink to this section
It’s important to understand that webpack stores all errors related to the compilation process in the Compilation.errors
array:
<>Copyclass Compilation extends Tapable { constructor(compiler) { super(); ... this.errors = []; } }
And every plugin is expected to populate that array if some error occurs. And our awesome HelloWorldCheckerPlugin
certainly follows that requirements and adds the error to the array:
<>Copy_make(compilation, cb) { try { ... } catch (e) { compilation.errors.push(e); cb(); } }
So to understand where the unspecified comes from we would simply need to intercept the call to push
and then inspect the callstack. There we will see the place where the error is being pushed into the array. Perfect, let’s do exactly that.
Intercepting `push` to errors arrayLink to this section
I’m using Chrome to debug Node scripts now. The approach I‘ll show can be used with any Node debugger as well. I use Chrome because it works much faster than built-in Webstorm’s node debugger. See this article for more information on how to use Chrome with Node scripts.
As explained in the article to debug Node with Chrome we simply need to run node with the --inspect
option. I’m using its variation here --inspect-brk
to stop at the first statement since I need a pause to put relevant breakpoints in other files. So to run webpack in a debugging mode we run the following commands:
<>Copynode --inspect-brk node_modules/webpack/bin/webpack.js
I also usually alias this to something, for example dlwpc
(stands for debug local webpack), to be able to debug Webpack from any folder where it’s installed:
<>Copyalias dlwpc="node --inspect-brk node_modules/webpack/bin/webpack.js"
Now, to intercept the push
we’re going to replace:
<>Copythis.errors = []
with
<>Copythis.errors.push = ()=> { Array.prototype.push.call(this, arguments); debugger }
In this way whenever there’s a call to push
we’ll get the execution stopped and we will be able to explore where the call is made from. There’s no need to make that change permanent in the webpack sources. We can utilize the console for that and make substitution there. So, let’s first run the webpack in the debug mode:
<>Copy$ dlwpc
And as described in the article navigate to
<>Copychrome://inspect
and click on inspect
in the Remote Target section:


Sometimes it takes a few seconds for the entry to appear, so give it a little time. One you click on it, this will open a standalone Chrome debugging tools and stop on the first statement:


Now we need to access Compilation
class in the node_modules/webpack/lib/Compilation.js
file but it won’t be available since Chrome haven’t yet loaded it. You have three options:
- Put a
debugger
statement in theCompilation
class constructor in the sources. That's the least convenient option because you'll need to clean these statements but I still sometimes use this approach when I have source code open and want to get to this point without extra hassle. - Upload Webpack files manually from the file system. That's probably the most convenient option, but it doesn't always solve the problem. When you know you debug just one package, it's easy to find it and upload it. But often multiple packages are used so you don't know which packages to upload and uploading entire
node_modules
folder isn't feasible. Besides, the file could easily be outside thenode_modules
. - Run Webpack till the end one time and then the inspector will load and keep all the files used during previous execution. Then you’ll be able to open the the required file and put a breakpoint there. I use this approach quite often too.
Let me show the second and the third approach.
Running the webpack till the endLink to this section
To run Webpack first time, I put a breakpoint at the end of the webpack.js
main file:


And then resume script execution with Resume script execution (F8)
and wait until webpack has reached the breakpoint. At this moment all the files used during execution have been loaded so I can simply open the file with Compilation
class using Ctrl+P
on Windows:


and put a breakpoint after the this.errors=[]
have been defined:


Since Webpack have processed this file already let’s restart webpack again and after we resume the execution the debugger should stop at this breakpoint. I’ve sometimes experienced Chrome not remembering breakpoints from the previous session, so you might need to re-run Webpack twice or use the debugger
statement approach.
Uploading webpack filesLink to this section
To upload Webpack files, go to Sources
tab and Filesystem
section. Click on the Add folder to workspace
and select webpack
module in the file system:


Once you've done that, the Compilation
class is available, you can open it


and put a breakpoint there:


Overriding errors
arrayLink to this section
Now, once you’ve got execution paused at the breakpoint we need to override this.errors
with our custom object. As I said earlier we will use the console for that:
I open console by pressing escape button Esc
on Windows. If it doesn’t open a console for your check the documentation. It’s important to replace this.errors
with the custom object before any code wrote anything to it.


Now once we’ve done that let’s just simply resume the execution and see what happens. The breakpoint is stopped and I can clearly see where the error comes from using the callstack:


And if I click on the plugin.js:22
in the callstack I can see our plugin adding the error:


Voila! We’ve just pinpointed the error. Now we know where the error comes from and we can experiment with the configuration or read the plugin sources and understand what we did incorrectly.
Just to show you that our plugin is powerful and correctly implemented let’s provide an absolute path:
<>Copyplugins: [ new HelloWorldCheckerPlugin({path: path.resolve(__dirname, 'toinspect.txt')}) ]
And here is what we have:


And that’s it.
GitHubLink to this section
If you want to play with the setup I’ve created the github repository here. Once you've download the repository, change the path supplied for the HelloWorldCheckerPlugin
in the webpack.config.js
to generate an error:
<>Copyplugins: [ new HelloWorldCheckerPlugin({path: 'toinspect.txt'}) ]
Comments (0)
Be the first to leave a comment
About the author

Principal Engineer at kawa.ai.. Founder indepth.dev. Big fan of software engineering, Web Platform & JavaScript. Man of Science & Philosophy.

About the author
Max Koretskyi
Principal Engineer at kawa.ai.. Founder indepth.dev. Big fan of software engineering, Web Platform & JavaScript. Man of Science & Philosophy.
About the author

Principal Engineer at kawa.ai.. Founder indepth.dev. Big fan of software engineering, Web Platform & JavaScript. Man of Science & Philosophy.