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!
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
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.
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.
Node.js follows CommonJS conventions and includes
require as a built-in function.
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.
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.
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
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.
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.
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!