Front End
Optimising Your JavaScript Bundle Size
Using pre-made libraries is a useful tool in the developer’s 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. Let’s 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:
1npm 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:
1source-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.
This was the starting point I had. From this, we can see there are three main contributors to our 1.59MB bundle size:
Font Awesome racking up 663KB
Full Calendar at 255KB
Media Library Pro at 320KB
Let’s 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 of 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. Let’s 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 its 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 set up 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 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:
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:
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 won’t 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 of hours work and some tea!
There are 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.