1 Demystifying Webpack

1.1 Some Background

I've played with a lot of the Javascript build tools in the ecosystem. Today, Webpack is my go to. I like it because it adds static analysis to my file paths for virtually everything. It also has fairly clear ways in which it transforms files. As with any tool, there's trade-offs to it. I can't get map files working for my unit tests, and the initial builds can take a really long time (upwards of a minute). Sure I can turn some features off to speed it up, but then I'll miss certain errors. For example, Angular's dependency injection system can lean on just the function parameter names, which break when any kind of minification is done. If I'm not minifying, I'm potentially missing those errors.

Maybe someone can help me with the test stuff though. That would be just dandy. Until then, I consider it something lost in the transition to Webpack.

Grunt is a tool introduced to me very early on in my budding Javascript career. I quickly found it nightmarish to work with for non-trivial projects. In fairness, Grunt really isn't a build tool - just look at their site. It's a task runner. People shape it into a build tool much like when schools shaped my grandmother into being a right hander by slapping her wrist with a ruler anytime she used her left hand. Grunt tasks don't really chain into each other very well, so when you have multiple steps in your file transformations they usually look like this:

.tmp/foo.css             # Convert from less to css
.tmp-tmp/foo.css         # Add browser prefixes
.tmp-tmp-tmp/foo.min.css # Minify css
dist/foo.min.css         # Actually write the final result

And when you make a change the file has to flow through all of that again. If for some reason you need to make a change to the build system, like throw something in the middle, it's not exactly clear that you updated all of the right things in the right places. Each task needs to know where the prior task had left its files, so it could pick them up and continue a transformation. It wasn't long before I loathed the idea of making tweaks to my Grunt config. This introduced resistance to change. "It works now - don't touch it". I made the jump to Gulp. It helped with the above setup! But if I needed to do any non-one-way transformations, I ran into issues with Gulp's piping mechanism.

1.2 Enter Webpack

At time of writing, Webpack is at version 3.1.0. My examples are written with Webpack v2.x in mind, but so far is working against Webpack v3.x. This is what Webpack's file transformation looks like:

???

Uh, wait. Where's my files? I fired up the webpack dev server like a good engineer and my reward is no files? Well, that's correct. Webpack keeps everything in memory for as long as it can. In the case of a dev server where it hosts everything, all of the files are held in memory.

Webpack might seem like magic on the surface, but nothing could be further from the truth. In the realm of programming, "magic" is usually something we describe as obeying some convention and because we did, things Just Work(tm). See Rails. Like all of it. Webpack demands of you an entry point. The void main() of your application. In Javascript land, this just means a Javascript file. This is how an entry point file might look:

require('./base.css')
var utils = require('./utils.js')
var color = require('color') // I don't know that the color lib does this, so
                             // just pretend.

utils.banner(color.rainbow('ohai world'))

You might recognize require Node.js. Browsers don't have a built in require, but Webpack provides one when it builds your runtime. require takes a path which can be relative or a "module" (from out of your nodemodules). Webpack does something extra though. It performs static analysis on these files. You might have noticed we're requiring a css file at the start of our entry point. We're not doing anything with it directly, but we're telling Webpack we want this file, and it should be included in our bundle somehow. Webpack will look for a base.css in the same directory our entry point is in, since we provided a relative path. Fun fact: IDEs and editors can be configured to auto-complete file names when they think you are providing a path. That css file might in turn have images it refers to, or even fonts. These paths will also be analyzed. Webpack will often munge these paths somehow, but it keeps them consistent internally. So we don't have to worry ourselves on how the file paths look in the final build. We just have to make sure at develop-time that everything is in the right places as defined by our require and equivalent statements.

How does Webpack do all of this? Magic??

1.3 Loaders

Well, not exactly. Webpack has these sort-of plugins called loaders. A loader is simply a means of transforming handling kinds of files. For css, I typically will use css-loader. There's also a babel-loader from going from ES6 to ES5 conversions, and loaders for html templates or even the index.html itself. These loaders give Webpack the context it needs to walk every file you need for your build. Javascript files use require and import. Html can use the src attribute from image tags (and a lot more). Css files can use fonts and url(). These are all provided via the various loaders that you will configure for your project. At build time, Webpack finds all of these files and makes sure that the urls will remain consistent throughout the bundling process. If it can't find a file at a particular path, the bundle will fail. If a file is not included directly or indirectly from your entry-point, the file will not be included in the bundle. This allows Webpack to make very small builds because it only includes what's needed.

Loaders can also be chained together. You can have a sass-loader which pipes into the postcss-loader which in turn goes to the css-loader. It's important to remember that these chain in reverse order. I like to think it goes from "outer" to "inner". Think of it like this:

foo(bar(baz()))

In this setup, baz gets called first, then the results are passed to bar, and then those results are passed to foo. Compare that to a chain of loaders:

{
  test: /\.css$/,
  use: [
    'css-loader',
    'csso-loader',
    'postcss-loader',
  ],
}

Here, postcss-loader goes first, then csso-loader, and finally css-loader. Pick whatever mnemonic works best for you.

1.4 Plugins

Once you have all of these loaders arranged, there's a few plugins I want to call out that make it all hum. extract-text-webpack-plugin is great for extracting your CSS into its own file. The alternative is to stuff it all in a style tag. My preference is an external file. The next is html-webpack-plugin coupled with html-loader. These let you take your index.html and it dynamically adds the appropriate script and link tags to your assets. It also insures that the html file is provided with the rest of your bundle.

I've provided a lightweight project that can help you get setup: webpack-skeleton

1.5 Conclusion

Sometimes this Webpack stuff can be confusing. Since Webpack 2 a lot of the documentation has improved, but there's still gaps, and it's easy to conflate the Webpack 1 docs and other resources with Webpack 2. Don't feel bad if it seems like it's taking you a while to get started with Webpack. For you bloggers and other people posting resources out there, be sure to include at least a major version number and a date on your material. The Internet is getting old, and we need to be mindful that there's a lot of dated information floating around out there. For those of us reading materials, be sure to check dates and version numbers when they are provided, and use skepticism when neither can be found. Open source has a lot of benefits, but can be problematic because documentation is perhaps the least sexy thing about it. It also is hard to produce docs when you're simply sharing an itch you scratched for yourself. Keep your expectations healthy! And pull requests for documentation are typically very welcome. If you ever want to get your name on a big project to claim some internet fame, I'd do it through documentation for sure.

Hopefully this sheds some light on how Webpack works. Not everything will be simple and easy dealing with it, but here you have sane start with something very simple and bare bones.