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:
Spatie's Media Library Pro - provides a reusable and feature-rich user interface for their Laravel Media Library package
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.
The first step in our process is to investigate what the current situation is and where we think we'll be able to optimise.
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:
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
Lets do some additional research into each of these.
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.
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.
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-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.
Now it's time to apply our findings from our investigating and start removing, tweaking and "CDNing" 🤔 our code. Lets start with 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:
We're now under 1MB! Let's tackle 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.
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.