Zillow Tech Hub

Avoid webpack bloat: Optimize your dependencies

Package managers like NPM are a great way to find and share code; however, you have to be careful whenever you take a dependency. Each dependency you take comes with a cost; it is another potential source of bugs and breakage, and it requires more effort to update than your own source. If it’s not clear how dependencies can be dangerous, let me remind you how a 17 line javascript module basically took down the internet.

left-pad is an extreme example, but even well-maintained dependencies are victim to feature bloat. Consider the popular date handling library Moment.js; when it was published at version 1.0.0, the library was only 2.2kb in size — currently, the library sits over 20kb gzipped (even larger if you bring in locales or timezones). Granted a lot of features have been added since the 1.0.0 release, but maybe you only need a small subset. You might want to re-evaluate your need for this dependency, or consider using a more modular library like date-fns to only bring in the features you require.

Track your application size

A good way to catch dependency bloat is to track the size of your application. At Zillow, every time we push code, a build is triggered that calculates our webpack bundle size. The sizes are tracked in Splunk where we can visualize the trend and investigate any curious changes. Additionally, we set an upper limit on the size of every application that breaks the build if it crosses our set threshold. Last December, the following happened:

We saw a huge jump in our application sizes indicating a common dependency had ballooned. After investigating, we found that a newly factored out module included a mismatch in the versions of React. Consequently, both major versions of React were bundled with our applications resulting in a major jump in size. After we updated the new module to the latest version of React, the issue was resolved.

Prevent duplicate dependency versions

The December build break made me wonder if there were any other modules with duplicate versions in our bundles. When you have a lot of dependencies (which in turn have their own dependencies), it becomes increasingly likely that two modules share a dependency. In most cases, the dependencies versions are compatible and won’t increase the size of your bundle; however, if the versions are conflicting (like in the React example above), your bundle will include both. I wanted an automated way to help find and prevent the latter from happening.

Introducing webpack-stats-duplicates, a command line tool that will analyze that output of your webpack --json > stats.json and find dependencies with multiple versions:

With the addition of a gulp plugin, we now have the ability to fail any builds that have duplicate dependency versions. When a new duplicate is found, that dependency can either be fixed, or whitelisted if it falls outside your realm of control.

Audit your application’s dependencies

From time to time, you should go back and audit your dependencies to make sure they are still providing meaningful value. A useful tool for this is Chris Bateman’s webpack-visualizer. Give this web application your webpack --json > stats.json output, and it will create a graph with all your dependencies:

In the above example, I found that moment-timezone was accounting for 10% of the size of one of our applications. That seemed exorbitant. I investigated and found that we were only using the module in one place, and I was able to remove the dependency altogether in favor of a smaller helper function.

Be proactive and critical of your dependencies

You can liken bundle size to entropy — without intervention, you can expect it to always increase with time. The tools above can help identify when bloat happens, but they won’t prevent it on their own. You have to be proactive in managing your dependencies; you have to be critical of them, both new and old.

On the Mortgages team at Zillow, we have two requirements that every dependency must fulfill:

  1. A dependency must give you something good (non-trivial, and not provided by the standard library)
  2. A dependency must be responsibly maintained (no backwards-incompatible changes, with an owner that is responsive to requests)

In retrospect, would left-pad have met our requirements for taking a new dependency? It is arguable about whether or not the 17 lines of code are complex enough to warrant a dependency, but we would most likely say no; the code could be added directly to our source and we would have one less dependency to worry about. Was the module well maintained? Considering it was unpublished and broke thousands of dependents, I would definitely say no. Any time you take on a dependency, no matter what the size, you are now at the mercy of the maintainer.

Exit mobile version