Optimizing third-party libraries

Part 4 of 6 in our Vue.js Performance series.
Written by Filip Rakowski

It’s been a while since we published last part of Vue Performance series. I hope you had a chance to apply everything you already learned in your projects and make them faster! If you have read previous articles you already know perfectly how lazy loading works, how to code-split routes and components, and how to manage asynchronous loading in elegant way but we still haven’t covered the aspect that influences bundle size the most - third party libraries. This article is all about them!

Defining the budget

The first question you should ask yourself before optimizing any frontend application is “Do I really need to do this?”.

It’s easy to get obsessed with performance but being too aggressive with optimizations is never a good idea. It’s very important to understand that performance optimization is about tradeoffs. A faster website is not always a better website. To achieve better performance results we always need to sacrifice something. Sometimes it’s a feature, sometimes it’s our favorite library that we need to change.

Performance optimization is not a goal by itself. To keep a healthy relationship between speed and common sense we need to define the measureable goals we want to achieve. Reaching them should mean that our application is fast enough and we don’t need to optimize it further. Those goals are commonly called a performance budget.

So what is a perfect performance budget? The answer to this question highly correlates to our target audience. The performance budget for applications targeting countryside residents or countries with slow internet connection will be much lower than one targeting major Europe or US cities. There is a commonly accepted rule of thumb though to never exceed 170 kB for critical assets. Critical assets are the ones that are downloaded by the user on initial visit and not loaded lazily. It’s typically a shared bundle with common dependencies (including stylesheets) and the current route.

Performance impact of third-party libraries

The code that you write by yourself is just a small piece of your production bundle. Most of its content is filled with third-party dependencies. A simple library, can in fact, contain more code than your whole application!

We usually do a lot of different optimizations in our own code hoping that they will make our apps faster when in reality it’s not our code that makes them slow - it’s others code! This is why choosing your third-party libraries wisely is a key to build well-performing web applications.

Learning about the performance impact of a certain third-party library turns out to be very easy thanks to bundlephobia! It’s an absolutely amazing website, where you can find a lot of performance-related data about any library available on NPM registry. Just type a name of the library you want to use and bundlephobia will tell you exactly how it will affect loading time of your application with different network conditions.

Instead of checking libraries one by one you can also scan whole package.json at once. It’s very helpful when you need to identify which library has potentially the biggest impact on your app’s performance. When it’s too high, it’s a sign that you should probably find smaller alternatives.

Analyzing your bundle

Bundlephobia is really helpful when you want to check certain libraries impact before adding it to your project but package.json analyzing can be misleading because the data that we’re getting is context-less. If you scan your dependencies and find out that some library is adding 700 kB to your bundle it doesn’t necessary mean that it’s contributing badly to your overall performance. This library can be loaded lazily or be included in a specific bundle that is downloaded only by administrators. Even though raw data can help identify performance issue you certainly shouldn’t make any decisions based only on this knowledge.

Thankfully there is a great webpack plugin that can fill this gap and provide you with contextfull information about your bundle - webpack-bundle-analyzer.

To add it to your project simply install it with npm or yarn:

npm install --save-dev webpack-bundle-analyzer

and include into your webpack config as a plugin:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

Now, every time you build your app you will see a graphical representation of generated code in a new tab. It will look more or else like this:

webpack bundle analyzer zoomable treemap

Every root-level rectangle represents a single JS bundle. We can inspect each of them and see which modules and third-party libraries are inside. We can also easily spot libraries and modules that are impacting our bundle size the most because the visual size of rectangles directly correlates to their weight.

Finding the weaknesses

Before we go further, I want to remind you that being too obsessed with performance optimization is usually a wasted effort. The time needed to optimize the library is usually the same for big and small ones. We should aim to find the most impactful issues and focus only on them. Micro optimizations will most likely remain unnoticeable.

You can find plenty of good practices focusing on those issues in previous articles (lazy loading, routing, prefetching) so today I will focus only on third party libraries

Do not include libraries that are only used on specific routes in the main bundle.

First of all - make sure that libraries that are used only on specific routes are not included into the main bundle. Many libraries require some kind of a configuration step to work. For example with Firebase SDK you need to run initializeApp function to configure the API keys before using any other functionality, Usually we put such stuff in our apps entry point (like main.js) to make sure it’s executed before other code. There is nothing wrong in that, you can even consider it a good practice, but you need to be aware that by doing this you are putting whole library in the main bundle. This bundle is gonna be downloaded by the user no matter which route he/she enters. ****

Main bundle should contain only so called critical dependencies - a minimal set of third party libraries that is required for your application to start. A good example of such dependencies are vue , vue-router or vuex.

Learn Vue.js With Vue School

Take a look at below screenshot from bundle analyzer:

Znalezione obrazy dla zapytania bundle analyzer

What we see here is a shared vendor bundle which should contain only critical dependencies. We can immediately spot leaflet module which is a JavaScript library responsible for displaying interactive maps. This is a feature that we probably need on a contact page but because it needs to be configured beforehand we can accidentally import it in main.js and because of that put it into the main bundle.

Smaller is not always better

It may seem intuitive and logical that a smaller library is always more performance-friendly than bigger one but thats not always true! In fact the way library is bundled is much more important than its overall size!

When someone is building a JavaScript library he/she usually bundles it in one file - just like we do with our applications. When we see a single big rectangle in the analyzer it means that whole library is concatenated into a single file. Unfortunately except very rare situations when dependency is exported as ES6 module bundlers can’t detect which parts of this big file were used and which not. Because of that they will include whole file with all features into your production bundle even if you will use only a single function from it.

Lodash is one of the most common examples of how badly this behavior can impact your page performance. Even if we import a single isNull function from lodash which is literally three lines of code whole library will be included into our bundle.

import { isNull } from 'lodash'

Quite a huge performance impact for three lines of code, isn’t it?

Fortunately many libraries provide alternative, more performance-friendly way of importing its features. Lodash, along with its concatenated javaScript bundle with all the features is also exposing them separately - one per file. If we import this feature from a separate file like this:

import isNull from 'lodash/isNull`

only the content of this file (which is just around ~500 bytes instead of 69 kilobytes) will be included in our bundle.

So it turns out we saved 24 kB of minified code (which is 15% of performance budget for a whole application) just by importing one library in a different way! Whenever you see suspiciously big rectangle in your bundle analyzer output for something very simple you probably don’t need major part of it. It’s worth checking out if there are alternative ways of importing this feature.
Best way of doing this is always official documentation but sometimes its lacking this information. In those situations it’s worth doing some Googling. If the library is popular (like lodash) there is a very high chance that someone already found a way of reducing its size.

Component libraries

Vue component libraries are a special type of dependencies that can impact your performance in a really bad way. By default most of them is not providing any way of importing components individually so we can’t get rid of ones that we don’t need …and this is not the worst part. Almost every common UI library is shipped in a from of Vue plugin that registers all its components globally when installed. Because of that we need to register this plugin before initializing our root Vue instance which leads to putting all libraries code into our main bundle that is always downloaded by users and prevents them from seeing anything until it’s downloaded and parsed.

For example - one of the most popular Ui libraries for Vue - Vuetify is also the one that can hurt your performance the most. Just take a look at those stats from bundlephobia!

124 kB is almost our whole performance budget! No matter how many components from Vuetify we will use (and on which routes) whole library will be pulled into our main bundle. It’s basically preventing our application from performing well.

Fortunately Vuetify authors are aware how big their library is and provided a very friendly way of dealing with this issue. If we dig a little bit on Vuetify docs we can find a document called Ă  la carte. It turns out that you can also import individual components from Vuetify (and many other popular libraries) almost the same way you can import individual features from lodash

There are few ways of doing this and you can read about all of them here. Below I described the easiest one.

Instead of importing the entire library as a single JS plugin which will end up in registering all of it’s components globally

// main.js
import Vuetify from 'vuetify'

Vue.use(Vuetify) 

// rest of code

you can directly import certain components exactly where you need them:

<!-- About.vue -->
<template>
  <v-card>
    <v-card-title>...</v-card-title>
    <v-card-text>...</v-card-text>
  </v-card>
</template>

<script>
import { VCard, VCardText, VCardTitle } from 'vuetify/lib'

export default {
  components: {
    VCard,
    VCardText,
    VCardTitle,
  }
}
</script>

By doing this you will not only get rid of all the unused components but also distribute the ones that you decided to use across different route bundles so no redundant code will be bundled.

This simple trick can save you a lot of troubles so it’s worth remembering!

Tip: Both Vuetify Nuxt module and Vue CLI 3 plugin will give you the same behavior out of the box via a dedicated Vuetify webpack loader.

Summary

Third party libraries are the main factor influencing bundle size of our applications. The way you import code from them is sometimes even more important than their overall size. Instead of importing a single object with all features its worth checking if there is alternative way that lets you import only the ones that you need. Sometimes its not obvious if you are importing a library in a right way. Thankfully webpack bundle analyzer and bundlephobie can help you in figure this out.

Learn Vue.js With Vue School

Leave a Reply

Your email address will not be published. Required fields are marked *

Up Next:

Portal - a new feature in Vue 3

Portal - a new feature in Vue 3