Here at Tumblr, we use a JS bundler to compile our client-side code. As time went by, we started to
Here at Tumblr, we use a JS bundler to compile our client-side code. As time went by, we started to feel the very real effects of bit rot in the form increasingly slow build times and the realization that were were 9 major versions behind on Browserify with no straightforward way to upgrade.We decided to take a fresh look at our process and give Webpack a try. We laughed, we cried, we saved a bunch of time on our builds.About two years ago, Tumblr embarked on a journey to create and apply cohesive clientside architecture to the website. Our Product Engineers had lived without things like JS bundling, npm libraries, and CSS preprocessors, and a lingua franca for application-level functionalities to share between Product teams was a considerable step forward for our codebase.One of the things that came out of this initiative was our use of Browserify to create JavaScript bundles that used npm modules as well as our own libraries. Suddenly, we had a straightforward way to share classes and utilities without polluting the global namespace! We decided on Browserify for building and Gulp as our taskrunner.We decided early on that we wouldn’t just start from scratch and rewrite the entire site, but rather we would pull things over piecemeal from the old way to the new way. We needed a way of splitting code between various pages on the site during this transition. Thus, “contexts” were born.A context bundle is essentially a mini Single Page App. For example, the dashboard is a context. The search page is a different context. The help docs are a different context. Each distinct context meant a different set of JS and CSS build artifacts, which meant a Browserify build for each.These contexts were still sharing plenty of code between them, particularly vendor libraries, which necessitated another bundle to avoid code duplication (and downloading identical code). We used a vendor bundle to address this. Another Browserify build! We manually maintained a list of modules that would be kept in the vendor bundle so the context bundle builds would know not to include them.The Browserify build processFast forward a year or so and we had added the header bundle, which loaded above the fold, and standalone bundles, which are entirely self-contained and don’t rely on the full vendor bundle. Our builds had turned into something like this (and this isn’t even including CSS):Browserify ├─ Header * ├─ Vendor * ├─ Context │ ├─ Default * │ ├─ Dashboard * │ ├─ Search * │ ├─ Explore * │ └─ ... └─ Standalone ├─ Blog Network * ├─ Mobile Web * ├─ Share Button * ├─ Embed-A-Post * └─ ...Each starred thing up there was a separate Browserify build, and it got slower every time there was a new context or standalone bundle. Furthermore, the version of Browserify we were using was falling further out of date, because newer versions were even slower in our case. One engineer created a system to parallelize gulp tasks using the cluster, which sped things up and had the added benefit of turning our boxes into loud, fan-spinning space heaters.Rethinking the buildLuckily, we did have an idea why things were so slow. Many modules were shared across contexts, but not in the vendor bundle, and our builds parsed them repeatedly. Browserify couldn’t share cached information about these modules across build processes. We could have fixed that by passing multiple entry points into Browserify, but that required rewriting our JS build scripts entirely, which was way too scary.In the meantime while we were furrowing our brows at the situation we were in, Webpack was emerging as a popular new solution to bundling. It had some neat features that weren’t in Browserify, or not as easy to configure. We were particularly interested in automatic bundle splitting, async loading, and hot reloading. One engineer had looked into it early on, but some of the magic in our Browserify configuration didn’t translate over easily. We shelved it.From Browserify to WebpackAt this point, our backs were against the wall. Our build process was so fucked up that we really had nothing to lose by trying something completely different except time.“Okay, fine. Where do I sign up?”Baby stepsThe first step was trying to get something building in Webpack. Since we were still committed to using Gulp, we opted for webpack-stream. I tossed together a basic Webpack configuration and tried it.We encountered problems immediately. Each of our context bundles used a “bootloader” to kick off the bundled JS with some bootstrapped data generated by the server. Being able to do require('context') and pass in the bootstrap data in an inline