Webpack is known as a bit of a bear. Yet, it's used in a large percentage of frontend projects. There is a lot to Webpack, and I won't go into all of it today, but I did want to talk about certain aspects.
I often say that one of the most important pieces of information when debugging your project is knowing what technology is responsible for the error you're seeing. It helps you google more effectively, helps you narrow down what changes might be causing the issue, etc.
Thanks to leaky abstractions, understanding when an issue is Webpack or Node.js is not as obvious as one might think. So let's talk about it!
Node.js
I wrote a post a little over a year ago called The Layers of JavaScript. The reason I bring it up now is that it's important to remember that npm is bundled into Node.
Npm is a package manager. And npm listens to a package.json file to determine what dependencies and versions to install. The result of running npm install
lives in your node_modules
directory.
Insert joke about the size of that directory here.
If you've gotten your package name wrong when listing it in package.json, or tried to reference a version that doesn't exist, npm will yell at you when you try and install dependencies. But as long as those things exist, and npm can install them, it doesn't care.
Webpack
This is where Webpack comes in. Lots of modern tools abstract Webpack configuration away from you. But the goal of Webpack is to bundle resources so a browser can use them.
The result, is that your dependencies exist as static assets that your code can reference. Ever seen code like this before?
const React = require('react')
Well, this is where things get a bit confusing.
Overloading require
Node.js follows CommonJS conventions and includes require
as a built-in function. require
allows you to reference JavaScript in other files.
Webpack supports a number of different specs, including CommonJS. So require
is also valid Webpack syntax. However, Webpack's require
is more powerful than the same function in Node.js. It uses enhanced-resolve
and allows you to reference absolute paths, relative paths and module paths.
Webpack also includes a function called
require.resolve
. This function takes a module name and returns a string that contains the path to the module. The difference between the two is sometimes confusing, so I wanted to include that callout here.
Supporting multiple standards
As mentioned before, Webpack allows for multiple different syntaxes (though it recommends you stay consistent within your project). One of those is ES6. The rough equivalent of require
in ES6 is this.
import React from 'react'
Here is where stuff really gets interesting. ES6 and CommonJS are not the same spec! So even though both are valid in Webpack, they often aren't elsewhere in the ecosystem. And since Webpack is bundling lots of different types of files for you, it can be challenging to keep things straight.
Node.js
At this moment, ES6 import syntax is not valid in Node.js. If you want to support it you can use the experimental package esm.
This means that files that run server-side, taking advantage of Node.js runtime, likely need to use require
.
Babel
Conversely, a lot of JavaScript files run in the browser. These files are often built with Babel. If you're not familiar with Babel my post on the ECMAScript Ecosystem is a good primer.
When Babel compiles your code, it turns all of your imports into Node.js require
statements (not Webpack ones).
It's worth noting that Babel output typically needs to be bundled by Webpack, so a bit of a Twilight Zone moment there.
Debugging
With all of that background it becomes a bit easier to determine where an error like Cannot find module 'react'
is coming from.
It may appear because it's referencing a dependency you don't have installed in your project. Make sure it's installed, and then make sure you're referencing it properly, no typos!
Conversely, you may see that error because Webpack didn't bundle your files where Node expected to find them. Take a look at your file path.
Not an expert
I've spent a fair time debugging these various issues and the thing I've come to recognize is that error messages go a long way. With so many packages and tools bundling Webpack for us, it's important to make sure the debugging information we get is as helpful as it can be!