Front End

Optimising Your JavaScript Bundle Size

Follow me as I trim my JavaScript bundle by half, going from 1.6MB to 800KB (250KB GZIPed)

Front end bundle sizes can quickly spiral out of control. It's good to take a breather and re-evaluate what libraries you're pulling in. I did this recently, lets look at the process I followed.

😴

TL;DR

source-map-explorer on NPM is a neat package that allows you analyse a JavaScript bundle and see a visual map on what sizes packages are using compared to the full bundle size. You can use this tool to identify areas where you might be able to tweak your includes, or replace/remove libraries entirely. The process I followed in this article allowed me to reduce my bundle size by around 50%.

Using pre-made libraries is a useful tool in the developers tool-belt. After all why reinvent the wheel? It allows us to speed up our development cycle and also, in most cases, use battle-tested libraries to ensure the best stability of our application.

I've been working on a freelance project for the last few months, it's a fairly complicated Laravel application with a rich front-end. The front-end makes use of various libraries including:

  • Font Awesome

  • Vue

  • Spatie's Media Library Pro - provides a reusable and feature-rich user interface for their Laravel Media Library package

  • Bootstrap

  • Full Calendar

  • TinyMCE

  • HTML5Sortable

  • Tom Select

  • Tiny Slider

  • Lodash

  • AlpineJS

  • Axios

A lot of these libraries are used for various front end features and at first glance cannot be trimmed and everything is being used. Lets get started by first investigating the impact of these libraries.

🕵️‍♀️ Investigation

The first step in our process is to investigate what the current situation is and where we think we'll be able to optimise.

A must-have tool when trying to optimise your JavaScript bundle is source-map-explorer, an NPM package that you install globally and can then use to process a source map and show a visual representation of your bundle.

First, install the package globally:

        
            npm install -g source-map-explorer
        
    

After running this command you'll now be able to generate a visual representation. For the purposes of this post we're not going to be using any of the options available, if you're interested in these options view the package's documentation.

We can now run the command to see what we're up against:

        
            source-map-explorer your-bundle.js
        
    

It's best to run this on a production build so you'll see more accurate sizes, this tool also assumes you have a source map matching the given bundle name, your-bundle.js.map in this instance.

Our starting point as generated by source map explorer.
Our starting point as generated by source map explorer.

This was the starting point I had. From this we can see there are three main contributors to our 1.59MB bundle size:

  1. Font Awesome racking up 663KB

  2. Full Calendar at 255KB

  3. Media Library Pro at 320KB

Lets do some additional research into each of these.

Font Awesome

I'm sure most people know that Font Awesome is an open-source icon library. Since version 5 there have been a few different ways to make use it. Via web fonts or SVG, with SVG providing additional benefits, primarily around the ability to strip icons out of your bundle and also having some advanced manipulation tools.

I'm using Vue as well so the vue-fontawesome package provides some wrappers for making Font Awesome behave with the v-if conditions, among other things.

I couldn't take advantage of the custom icon bundles since the website is powered via a custom CMS that has page blocks where the user can specify an optional icon, meaning I have no way of knowing what icons I need.

However, Font Awesome does provide a CDN. In most instances I prefer to avoid CDNs, primarily because it removes the ability for you to reduce your bundle via tree shaking or by only including modules you need. In this instance though, we can't remove the icons since we might need them. So a CDN would be a good solution since it moves the library into a separate HTTP request, which the browser could fetch concurrently and, being a CDN, it's likely the required assets are already cached.

📆 Full Calendar

This was a bit of a tricky one. Full Calendar is used for an events page so we could pull it out and only load it on that page, however, what if it's then used somewhere else? It's just additional maintenance.

Full Calendar does also have a CDN, however, the application is making use of additional plugins that would have to be loaded via their own requests, making this not an ideal solution.

Finally, Full Calendar doesn't appear to be super modular, and so doesn't provide a potential alternative like Font Awesome does. This means, for now at least, Full Calendar will have to remain as is.

📁 Media Library Pro

Finally, Media Library Pro. As mentioned above this is an extension to Spatie's Media Library for Laravel, it's a paid extension but as a side note, it saved me an incredible amount of time setting up file uploads both on the front end and back end!

Now, back to optimising. Media Library Pro is quite a large package, it has a lot of functionality, and while I believe Spatie are working on improving the bundle size, it's not something we have control over as it's pulled in via a private Composer repository rather than through NPM.

However, we're only using the Vue dependencies which has two different plugins; media-library-pro-vue2-attachment and media-library-pro-vue2-collection. I know these work very similar, so I might be able to remove one of them and make use of the other everywhere.

Now, let's make all of this a reality by implementing these changes.

🔨 Implementation

Now it's time to apply our findings from our investigating and start removing, tweaking and "CDNing" 🤔 our code. Lets start with Font Awesome.

Font Awesome

We found from our investigation that we couldn't manually prune the build we use for Font Awesome. However, we might be able to make use of Font Awesome's CDN to at least move it into it's own request on another domain.

To start we needed to create a "Kit" on Font Awesome's website. This requires an account and will allow you to create CDN hosted kits.

Once we have the Kit, Font Awesome will provide us with a script tag to include in the head of our document. I also removed any references to the Font Awesome package in the JS file and rebuilt my bundle.

I setup my Kit to use SVG as the technology, disabled Font Awesome 4 compatibility and enabled auto accessibility. This means most of my icons just started working...

Why most? Well, the reason Font Awesome provides a Vue wrapper is because of how it handles the SVG icons. When it sees an element with an icon class, the element will be replaced with SVG code. This is great, except when you're wanting to toggle the visibility of icons with Vue (or other front-end frameworks). For example adding v-if to a spinner icon will cause the icon to keep being recreated, rather than hidden and reshown.

The website isn't making use of any of the advanced features SVG provides, so I decided to edit my Kit to use "Web Font" instead. I then adjusted the icons inside my Vue templates as needed.

This meant everything worked as it did before, except now, we're pulling Font Awesome from a CDN rather than including it in our bundle. This change also meant Font Awesome was smaller because we're now just using Web Fonts rather than the SVG engine. The ~660KB package we were including was now ~ 450KB request, without GZIP.

Let's see what our bundle looks like now:

Our bundle map now that we've removed Font Awesome
Our bundle map now that we've removed Font Awesome

We're now under 1MB! Let's tackle Media Library Pro.

📁 Media Library Pro

This one was much more straight forward. The two Vue components provided with this library offer similar functionality, but for different use cases:

  • Attachment - Provides a way to upload a single attachment

  • Collection - Provides a way to manage a collection against a model, reordering, multiple uploads etc.

I needed the collection component for one of the pages as it was handling multiple uploads. The attachment component though was only being used in a single place.

I was able to swap out the attachment component with the collection component and then specify max-items="1" on the collection, mimicking the functionality of the attachment component.

After testing everything still worked as I expected, I then rerun the source map explorer one final time:

The bundle map following our final change.
The bundle map following our final change.

As you can see before GZIP we're now sitting around 800KB and we're only pulling in one of the Vue components.

📝 Conclusion

We started at around 1.6MB for our JavaScript bundle size. After making two changes to our libraries and some minor changes to our code we're now building an 800KB JavaScript bundle and loading Font Awesome at about 450KB. Overall, that's a size saving of around 350KB. However, the performance benefit of using a CDN means in most cases we wont load those 450KB, meaning we've potentially halved our bundle size. Also, it's worth noting that with GZIP compression our bundle could be as small as around 240 - 300KB.

Not a bad improvement for a couple hours work and some tea!

There is likely some further tweaks I could make here, researching more info Full Calendar, maybe finding alternatives etc. However, it's likely you'll start to see diminishing returns and so it's best to deal with the large changes with high gains first, then tweak and prune over time if needed to further optimise.

Tagged Under: Front End, JavaScript, Optimisation, Bundle, Source Maps, Source Map Explorer, source-map-explorer, Font Awesome, Full Calendar, Laravel Media Library, Laravel Media Library Pro,