When I visit your web app for the first time, what code will I download? That question is pretty broad, so let me try to be a bit more specific.
Say I’m visiting the most popular entry point of your app — perhaps a login, signup, or landing page — which JS libraries, such as
lodash, will I load? What custom first-party code, such as UI components, routing, and data fetching, will be required to build the page? Finally, which third party scripts, such as Google Analytics or Intercom, will be fetched?
If you’re not quite sure of the answer, you’re not alone.
The situation can get even more confusing when it comes to third-party scripts, such as those that execute tracking and analytics. Sometimes these scripts are added to the page via a tag manager, which means that frontend developers often don’t have much control over, or awareness of, which third party scripts are being loaded on the page.
I’m going to show you a small tool that I built called
Now that I’ve hopefully established how important it is to be aware of the code you’re sending to your users, let’s look at how bundle-wizard can help.
By running the command
npx bundle-wizard reddit.com creates the following visualization of the code loaded to display Reddit’s mobile site:
(You can check out a live version of this visualization here created from the Reddit mobile site as it was on 03/31/20).
Let’s take a quick look at how to interpret this view. First, we can see two very large bundles along with some smaller ones. The large bundles are divided between
Mweb.b4e4245f311b33152097.js on the left, which contains much of the page’s custom code, and
Before even getting into the other information that
bundle-wizard provides, we can begin to see some interesting avenues for further exploration. First, is it really necessary to load 84kb of
core-js polyfill even for users with modern browsers? Second, given that bundles should generally be less than 100kb for best performance, would it be possible to chunk these two big bundles down into multiple smaller ones?
If you click on a rectangle, for instance, the
src/app/components square inside
Mweb.b4e4245f311b33152097.js, you can see a detail view:
As you might expect for a message board site, the
Post component is the largest of all the components contained in the major bundles.
The background colors of all the boxes represent how much of the code was actually run by the page on startup:
- Red blocks of code went mostly untouched by the browser. These bundles are probably low hanging fruit that you could defer loading without much additional effort
- Orange and yellow blocks of code were partially run by the browser— it might be worth looking into whether parts of the code could be chunked and deferred
- Green blocks were entirely run by the browser on page startup. But take note! This does not necessarily mean the code could not be deferred or removed—for instance, a huge block of polyfill code might not have been necessary to load on a recent version of Chrome, but it could have been fully run by the browser nonetheless.
One thing that’s helpful to remember as we scout for optimizations is to focus on the easy wins first. You might see, for instance, that the
Register/index.js is largely untouched on initial page load. This makes sense—registration is a thing most users will only need to do once. But since the component is only 13kb minified, it might not make sense to optimize right away. (One exception is if other code surrounding registration flow would push the combined size up. But from a preliminary look through the bundles, that doesn’t seem to be the case).
Once we render all scripts in the visualization, we can see that an ad script,
https://securepubads.g.doubleclick.net, takes over as the third-largest JS bundle loaded on the page. At only 17% coverage, it seems like this script is possibly loading a fair amount of unnecessary code. To be fair, however, Reddit seems to be loading a fairly small amount of third-party scripts compared to other sites.
If we, on the contrary, found some critical path bundles that were in the low-priority list, it would be worth it to explore using priority hints to load them earlier. Conversely, we might come across some large or computationally expensive third-party scripts that had a high priority. In that case, we could experiment with adding a
defer attribute to the script tag to load it with a lower priority, or even removing them entirely if it was determined that their business benefit didn’t counteract the performance hit they caused.
So far, we’ve looked at optimizations that are mostly aimed to reduce download time and script parse time. We haven’t touched on script execution time yet, although that is potentially the most expensive step of all.
Helpfully, bundle-wizard warns you of long tasks it detected on app startup that were initiated by JS script execution In the Reddit example, we can see a little 🚨 icon next to the large
Mweb.b4e4245f311b33152097.js bundle. When we hover over it, we see just how long the long-task was in the tooltip:
Unfortunately, while it can alert us to the problem, bundle-wizard isn’t much help in helping us figure out how to fix it. For that, we can fire up the Chrome Devtools in an incognito window and run a performance profile on the
reddit.com mobile site.
As expected, the profile we create has a long task generated by an “Evaluate Script” action on
Mweb.b4e4245f311b33152097.js, which we can see when we select the yellow bar underneath the gray long task indicator and view the summary tab on the bottom:
To look into which functions might be contributing to this task time, we can take the following steps:
- Make sure the yellow “Evaluate script” bar you selected in step one is still selected
- Select the “Bottom-Up” tab
- Make sure the “No Grouping” option is selected
- Sort by “Self Time” in descending order
(Note: this step usually works best on a localhost development build, as it tends to be easier to jump right to the offending lines of code by clicking the links on the right-hand side).
You might notice other long tasks in the profile as well, not directly attributable to one of the JS bundles – those can be explored in a similar manner.
bundle-wizard a spin on your own projects and see what you learn! For full details of how to use the tool, including instructions on how to use it to measure apps running locally, check out the project’s README.
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.