Module bundlers essentially figure out which bits of code depend on other bits of code (in other words, bundlers identify dependencies) and make sure code runs in the order it needs to. Bundlers ultimately create a dependency graph, starting with a root (which has no dependencies) wherein the further down the graph a bundle of code falls, the more dependencies it has. Further-down code waits its turn for its dependencies to load first before it is loaded itself.
Even in simple applications, we write a lot of code in a lot of separate files. We use syntax like
@import and ES6. We use helpers like TypeScript to which allow us to write clearer code and catch errors sooner. Think of applications built with component-based libraries and frameworks like React where we import components or functions that depend on code written somewhere else in our application. The way our code is parsed, packaged, and executed in a way our browsers can understand can seem like nothing short of magic. But it isn't. It's a bundler called Webpack.
What makes Webpack stand out, though, is its ability to gather all dependencies including not just code, but assets such as images, stylesheets (including preprocessors like sass, typescript and more and create the beforementioned dependency graph. And that's the key -- the heart of Webpack is building the dependency graph.
The dependency graph consists of a few key components. Here I'll focus on: Entry, Output, Loaders, and Plugins. One can run
yarn add webpack webpack-dev-server --save-dev and create a
webpack.config.js file to your app's root directory to get started. Don't forget to update your
The first thing Webpack does is establish an entry point. This is going to be the root of the dependency graph. We can do this by creating a variable containing an object which points to the source file. This is typically going to be
index.js. You can create the object with a simple string, but for scalability and the possibility of a need for multiple entry points, let's use an object.
Once Webpack has finished bundling code and creating the dependency tree, it needs to be told where to put the finished product; This is our Output. Remember, you can name the filename parameter whatever you'd like. Typically this will be named something like
Interestingly enough, this is the base information the application needs to bundle code. With this, you can spin up your local server with
yarn start and Webpack will begin doing its thing.
This is where Webpack starts getting so cool.
Here's a screen grab of some imports from the
index.js file of a recent React project of mine called Squad.
It's something we do 100 times a day. But what's really happening behind the scenes? We're telling Webpack about the dependencies
index.js needs. For example, the line
import ./index.css tells Webpack to grab those styles.
webpack.config.js file, we add a module object like the example below (see webpack concepts, loaders).
This object uses a Regular Expression to identify certain file types and then tells Webpack which loader to use before bundling. We're saying, "Webpack compiler when you find a path that ends with
.css in an import, transform/load them using the
style-loader when you bundle."
Important note: "Transforming" means parsing files (other than
.js files), and translating it into something else that Webpack and the browser can understand.
A few examples:
- Have you ever used
@importor syntax like
url('./icon.png')in your CSS files? Webpack's css-loader is the reason why! It parses your
.cssfile and processes it. That is why you can
import Icon from ./icon.png;and later
element.appendChild(Icon)! As stated in the Webpack Documentation, "The loader will recognize this is a local file, and replace the
./icon.pngpath with the final path to the image in your output directory. The html-loader handles
<img src="./icon.png" />in the same manner." Isn't that so cool?
- What about any ES6 syntax like classes,
constvariables, arrow functions, default values, destructuring, etc? babel-loader parses the code you wrote, identifies ES6 syntax, and loads and transpiles it to ES5 the browser understands.
You can learn more about how loaders work under the hood, and even how to write your own loaders here.
- Work on a file level
- Help create the bundle (during or before bundle generation)
- Work on a system level
- Affect the created bundle (bundle or chunk level)
- Focus on optimization (uglifyJS takes the
bundle.jsand minimizes it to decrease file size)
The code implementation looks something like this:
The following graphic may help visualize the difference as well:
Webpack is the culprit behind much of the magical secret sauce that allows us to use syntax and libraries that makes code cleaner, clearer, more scalable. From imports to ES6, developing apps would be difficult without bundlers like Webpack.