Why does your bundle grow? 
Tags: javascript, performance, webpack
Published: 2019-09-13 20:38:06

---
title: Why does your bundle grow? 
published: true
description: See how to compare the JavaScript bundle between builds and prevent the unnecessary increase of its size with a new tool: webpack-stats-explorer. 
tags: javascript, performance, webpack
---

# Growth is an inherent property of development

One may say. Every time you're adding some feature to your application, the size of the codebase and the resulting bundle increases (the opposite thing is what they call "refactoring"). It's nothing wrong with growth until you keep track of these changes and you know exactly why files shipped to the browser are bigger than before. The task is quite difficult, as web applications nowadays are incredibly complex - it's no longer just your code, most of it (in terms of size) are external libraries. The build system matters as well - all the transpilers and bundlers completely changes how the app code looks for the end-user. When any of those parts changes, the bundle may change and very often it's unpredictable and unexpected. Bumping some small package by the minor version or changing a single option in the tooling configuration is enough to get extra kilobytes that you may not even need. How do you know if anything unnecessary was added to your bundle, and what exactly was it? 

# Understanding is the key

There are many great tools for visualization of the bundle structure, like [Webpack's Analyse](http://webpack.github.io/analyse/) and [webpack-bundle-analzer](https://www.npmjs.com/package/webpack-bundle-analyzer). They help to understand what the bundle consists of but still don't solve the problem of tracking changes between subsequent builds. Unless you're having fun with "spot the difference" game.

![Two webpack-bundle-analyzer reports placed next to each other for comparison](https://thepracticaldev.s3.amazonaws.com/i/28c91huqt0v4nxmfakhw.png)

For the source code `git diff` is a perfect tool, but you what about built artifacts? Keeping them in the repository to manually review compiled code doesn't sound really exciting. There is a tool that may help with this miserable situation: [webpack-stats-explorer](https://erykpiast.github.io/webpack-stats-explorer/).

![webpack-stats-explorer overview](https://thepracticaldev.s3.amazonaws.com/i/nzwvbc74ppded3mxtqnf.png)

# Example

The common project setup these days includes Babel, which transpiles usually succinct ES6+ code to quite verbose ES5 counterpart. Let's look how relatively small changes to source code may affect the final bundle significantly. 

Consider a very simple module, literally 57 characters long. It's actually pure ES5 code, so the output is perfectly predictable.

```javascript
export default function (a, b, c) {
  return [a, b, c];
}
```

What if we'd add some ES6 feature? A simple one, like a default value for the function argument. It's just four characters including spaces!


```javascript
export default function (a, b = 1, c) {
  return [a, b, c];
}
```

The ES5 output will be significantly bigger. And I mean it - 137 characters. 

```javascript
function _default(a) {
  var b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : b;
  var c = arguments.length > 2 ? arguments[2] : undefined;
  return [a, b, c];
}
```

Of course, before shipping to production, this code will be minified, but it'd be still 92 characters - 23 times bigger than you may expect. That's how it's displayed in [webpack-stats-explorer](https://erykpiast.github.io/webpack-stats-explorer/).

![The difference made by adding default function argument visualized in webpack-stats-explorer](https://thepracticaldev.s3.amazonaws.com/i/y2rlwbdr67s1mb8o26uf.png)

You may say, that 100 bytes don't make a difference. Multiply it a few hundred times and you will get tens of kilobytes.

But there are more creatures that may scare you. Let's look at this little snippet - asynchronous function.

```javascript
export default async function (a, b = 1, c) {
  return [a, b, await Promise.all(c)];
}
```

[webpack-stats-explorer](https://erykpiast.github.io/webpack-stats-explorer/) shows increase of 1,42 kB. It's just 25 characters in the source! That's huge.

![The difference made by making the function async visualized in webpack-stats-explorer](https://thepracticaldev.s3.amazonaws.com/i/38mq8x6f5l7vrm6jsoeh.png)

And you know what? This code doesn't even work. You need to add the whole regenerator runtime library, which costs the next 20 kilobytes. So it's 22 000 bytes for 25. Nice ratio, isn't it?  

![The difference made by adding regenerator runtime library visualized in webpack-stats-explorer](https://thepracticaldev.s3.amazonaws.com/i/hfx0prapd8q7lc7ihdyg.png)

In case it wasn't convincing enough, think of a completely different situation: upgrading dependencies. It's quite an easy and straightforward task when it's a patch or minor bump and the library you use follows semver convention. So you just launch [`ncu -u`](https://www.npmjs.com/package/npm-check-updates) and `npm install`, you run unit tests and if they pass, `git commit -m "Upgrade dependencies" && git push`. Voilà, the maintenance part is done, let's go back to doing fun things.

But sometimes things go worse. If you used [`react-apollo`](https://github.com/apollographql/react-apollo) for a while, you may remember [this issue](https://github.com/apollographql/react-apollo/issues/2918). With a little patch, you could get almost 10 kB of code (after minification) just for free. The issue was fixed after a while, so if you knew, you could, well, react. Like wait or help to resolve it.

![webpack-stats-explorer showing the difference after upgrading dependency by the patch version](https://thepracticaldev.s3.amazonaws.com/i/fp8ntobxg9eszjv58zxc.png)

Those are only two simple examples, but the problem surface is much greater. Think about all these times your bundle exceeded the limit and you've just increased it with heavy hearth sighing: "sure, I've added some code and bumped dependencies, development has a cost". Are you sure you had not missed anything obvious?

# Conclusion

You should be aware of what is going on in your code - not only the source but built one as well. With that knowledge, you can decide, if you really need all these extra pieces you've got for different reasons. If users downloading all this JavaScript with every page reload need it. You at least have a chance to do something about it: tweak build system configuration, skip upgrading a library or stick to `.then` this time. 

If you have Webpack in your project, give [webpack-stats-explorer](https://erykpiast.github.io/webpack-stats-explorer/) a chance. Next time you are going to merge a feature branch to `master`, review not only the source code but production bundle as well.

### Appendix

[webpack-stats-explorer](https://erykpiast.github.io/webpack-stats-explorer/) is an open-source, non-profit side project created with React and [ReasonML](https://reasonml.github.io/). It may be a good opportunity to try bleeding-edge technology in a real-world scenario. There is [a backlog](https://erykpiast.github.io/webpack-stats-explorer/issues) full of ideas, but also with space for bug reports and suggestions. Any help will be appreciated!