Building Modular JavaScript with Webpack — Librato Blog

Building Modular JavaScript with Webpack


As front-end development increases in complexity, powerful build-step tools have gone from nice-to-have to a necessity. Many teams are now compiling and transpiling JS/CSS, maintaining complex test suites, treating logic as components, linting and minifying code. Good tools integrate these efforts, increasing velocity and developer satisfaction. There is no shortage of these build tools, and none of them are a perfect fit for every situation. But, as usual, some rise above the pack and become something really great.

For a long time at Librato, we maintained one of our substantial JavaScript codebases with quite the smattering of tools; some were installed as Ruby gems, some as NPM modules, and it was all stitched together using an epic Makefile. It came from the days when there were no front-end-centric build tools, just make or rake or whatever you had lying around. It’s what we did out of necessity.

It worked, but any time someone needed to add a class, or include a new utility function, they would have to go through the exercise of:

  1. Adding the new file to the Makefile.

  2. Adding the new test to the Karma config.

  3. Making sure all of the new file’s dependencies were loaded before it.

  4. If they weren’t, rearrange and reinsure that no existing dependency links were broken.

Sounds like a pretty typical day in JavaScript land. We get used to these things, but the accumulated time spent over years of this is pretty staggering.

JavaScript in the browser really needed the same modularity and portability that other ecosystems had. Thankfully, our wonderful, ragtag community has a way of making do, and doing well. There began springing up a host of tools: JSPM, Browserify, Webpack. This becomes an extremely powerful setup when combined with a legitimate package manager like NPM. It’s no secret that having the ability to modularize chunks of your code is truly a fundamental part of writing a maintainable, portable, and clean codebase.

A more advanced build tool also allows other concepts and possibilities to be “discovered”. For instance, it’s fascinating to me that The End of Global CSS is the actual title of a recently written article. Having easily approachable yet extremely capable compilation tools means that we can all be writing ES6 (or ES7) if we want, and just have it transpile to valid ES5. You can write whatever flavor of CSS that you want, and have vendor prefixes automatically inserted by Autoprefixer. It’s as if we’re finally having our cake and eating it, too!

After playing with both Browserify and Webpack, we settled on using Webpack at Librato. It seems to be more capable out-of-the-box, but lots of people use and prefer Browserify’s approach. I’d recommend checking them both out to see which fits your project better.

Thanks, Webpack!

Here’s a short list of things that Webpack made significantly easier for us:

  • No more long “manifest” files that include all of our modules in a delicate and fragile order. 
    (The same applies to our karma.config.js because of karma-webpack.)
  • It’s no longer difficult to determine exactly what dependencies any of our internal modules have, because they’re required at the beginning of the file.
  • It forces modularity and gives us a nice set of conventions for how to actually build things. Webpack has good documentation and it allows us to remove homegrown solutions which varied project to project.

Some Challenges

Implementing Webpack ended up being relatively easy, but here’s a quick summary of some of the pain points:

  • It’s tedious to go through and require dependencies and add module.exports = foo to all of the files that were previously just concatenated together.

  • Shimming jQuery into modules that depend on it being in global scope is a little tricky and requires some extra work.

  • The config options seem endless. It helps to start with an already set-up example or the getting started guide.

One of the big issues we faced with using NPM for managing dependencies was ensuring that everyone on our team had the exact same versions of every package installed. The simplest approach we’ve found is to use an npm-shrinkwrap.json and specify save-exact = true in our .npmrc. We’ve also started migrating all of our projects to NPM 3 for this reason (from the release):

Your dependencies will now be installed maximally flat. Insofar as is possible, all of your dependencies, and their dependencies, and THEIR dependencies will be installed in your project's node_modules folder with no nesting. You'll only see modules nested underneath one another when two (or more) modules have conflicting dependencies.

So, to recap:

  • We install and version external dependencies installed with NPM.

  • Internal and external modules (and tests) are compiled/transpiled/bundled/shimmed together with Webpack.

  • We ensure that we are all consistently building with the same versions of dependencies by using NPM shrinkwrap.

Since migrating this project to Webpack, I’m finding that I can leave the mental overhead of keeping track of our large dependency tree behind. It feels so great to automate away those dull pains. Thanks to Tobias Koppers for creating Webpack, and to Henrik Joreteg for the introduction to Webpack and for an awesome example of a Webpack config.

We're building an awesome application on a cutting-edge stack. If that sounds interesting to you, let's talk.