Why We Switched to webpack

This is the first of a two-part series on why and how we switched our JavaScript bundling system from an ad hoc system of Grunt tasks and PHP, to a declarative webpack configuration. This post will cover the “why” we did it — Stay tuned for a post that covers the “how”!

Hi! I’m Spencer, a Computer Science student at the University of Toronto. This was an initiative that I kicked off during my internship while working with the event app team at EventMobi.

Until recently, EventMobi’s primary event app codebase had used an ad hoc system of Grunt tasks and PHP to bundle JavaScript for the browser:

  • Scripts and stylesheets were collected in a dependencies-first order and piped through some preprocessors.
  • For production builds, another task would concatenate and minify scripts and stylesheets into one JS and one CSS artifact, but in development, we had PHP render each individual script and stylesheet into a new `<script>` / `<link>` on the page so that individual files can be found in the debugger.
  • Like the angular-seed starter kit, application code was separated into modules using `angular.module`: each file had one `angular.module()` definition with a unique string name to identify the module. We relied on AngularJS’s module names and dependency injection to resolve dependencies between modules.

This build system had served us well enough, but we eventually ran up against some pain points which could be addressed by using a module bundler like webpack:

1. Resolving dependencies was tedious

Scripts had to be collected in a specific order, otherwise dependencies would be out-of-order: first load jQuery, then load AngularJS, then load AngularJS plugins, etc.

Managing dependencies with <script> tags

With a module bundler, dependencies are imported using CommonJS `require` statements in the file where they are used, and combined into one JS bundle. So dependencies are guaranteed to be available: no need to render many `<script>` tags in a specific order.

Managing dependencies with CommonJS modules

2. Hard to use npm packages

Importing packages from npm required us to manually create a UMD bundle if one wasn’t available. Want to use ng-redux? Create a UMD bundle and add it the the list of vendor scripts. Want to use left-pad? Create a UMD bundle.

In Node.js it’s common, and arguably just good design to publish and consume tiny modules from npm like left-pad. But the overhead of creating and managing UMD bundles meant it was often easier to ‘reinvent the wheel’ by re-implementing common functionality than use an npm package.
Oh, and what about when two npm packages share a common dependency? There’s no simple way to share that common dependency between the two UMD bundles; that dependency is now duplicated in each bundle, wasting valuable bytes.

With a module bundler, npm packages can be used with a simple

npm install <name>

and

require('<name>');

No need to create UMD bundles. And if two npm packages share a common dependency, both will share the same code without duplication.

3. Poor dev-prod parity

Scripts loaded during development were sometimes different from scripts loaded in production, since the PHP `<script>`-rendering behaviour for development was separate from the Grunt script concatenation behaviour for production. This meant that sometimes our development build would work, but the production build would fail due to a missing script. In other words, poor dev-prod parity.

With a module bundler, in both development and production, a single JS bundle and a single CSS bundle are created, so there is no disparity between the two environments in how many `<script>`s are rendered.


The above presented us many challenges to solve, and as we started to look into potential solutions we had two major goals in mind:

Goal #1: Make the codebase easier to read and understand

Since we relied on AngularJS’s module names and dependency injection to resolve dependencies between modules, there were no explicit links between modules; locating the source of a function was often an arduous process of performing global searches on the codebase.

With a module bundler, it’s much easier to trace the origin of any piece of code when it’s imported via a `require()` call. The module path in the `require()` call explicitly tells us which file the dependency comes from.

Goal #2: Make the codebase ready for a future where it’s simple to start using:

  • Babel: to use the latest ECMAScript while still supporting old browsers, and to support JSX syntax for…
  • React: some parts of the app were very slow, like rendering long lists. Using a faster view layer like React for specific views would help solve this problem.
  • Redux: we recently started using Redux via a UMD bundle of ng-redux to help reason about the app’s complex state. This would be easier to manage if we didn’t have to maintain a UMD bundle for each individual redux addon and middleware we use.

With a module bundler, enabling ECMA2015+ language features and JSX is as simple as enabling Babel, e.g. via babel-loader, and using React, Redux, and its ecosystem of packages is simplified to `npm install` and `require()`.


This isn’t an exhaustive list of all of the reasons to use a module bundler like webpack, but these were the main reasons why we opted to switch.

Next week, we will cover how we switched to webpack, using an incremental approach to the migration.

[If you liked this article, click the little ❤️ on the left so other folks know. If you want more posts like this, follow EventMobi publication with the (Follow) button on the right!

Finally if solving problems like this seems up your alley, we’re hiring! Checkout our current open positions at http://www.eventmobi.com/careers/]