Dev Diaries: Speeding up Bottlebeagle by breaking it into happy little chunks

Bottlebeagle is a Vue app. It's my first foray into frontend JavaScript in three years, and I have actually (surprisingly!) really enjoyed learning and creating in Vue.

However, in fixing a tiny little bug that required me watching the build logs carefully, I realized I am shipping a whopper of code – most of which will never be used by the average user.

When I run npm run build, I see that the app I built is around 5x the size they recommend:

Whoops! How on earth did that happen? Well, quite easily, it turns out.

  1. Webpack, the thing responsible for bundling the app together into something deployable, simply puts every dependency it sees together into one big chunk. It's not very clever about when you might use them – you have to tell it what can be defered.
  2. I have some enormous dependencies, including Mapbox (not surprising, given it does mapping and looks nice) and a library for parsing phone numbers (surprising, given that it's just phone numbers).

The solution isn't straightforward, either. But I found this article helpful:

Reduce the size of Vue.js application | QwertoBlog
Qwertovsky blog

It introduced me to a command that I could run with the Vue CLI: npx vue-cli-service build --report. Neat!

(For whatever reason, I needed to run that with npx, or else it wouldn't even see the --report flag, and no report.html would be generated.)

With this command, I could see a visualization of the app's dependencies, and begin to figure out which to tackle first. Here's what Bottlebeagle's looks like, after splitting:

The article continues by offering some ways to minimize the app size, but I was caught up by something funny: in JavaScript, it looks like there's no convenient way to tell your build tool to simply defer loading an external library. You kinda just have to load the whole thing, or rely on the library's creators to have packaged it up into chunks. You can't really lazily load it.

To get around that in Vue, you just lazily load whichever components rely on that library. If a piece of code formerly looked like this:

import Location from '@/components/Location.vue';

export default {
  name: 'Home',
  components: {
    Location,
  },
  …
}

Then now you'll change Location to actually return a sneaky little function to load the component later:

const Location = () => import('@/components/Location.vue');

// everything else stays the same:
export default {
  name: 'Home',
  components: {
    Location,
  },
  …
}

Wild! By doing that a few times, I managed to get SO close to the recommended size of the initally-loaded bundle:

Ow! 1kb over the size limit. I hope no one notices.

And you can even see how: instead of those four files having to contain literally everything, it's spread between a lot more chunk- files:

Performance work continues until we hit 244kb

Thanks for joining me in this latest foray. In other performance news, I finally got a CDN working so that I no longer have to bundle marketing-related images into the JavaScript app. Overall, the app has become a lot more responsive, and I hope to keep it that way.