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.