Alex Holachek Frontend developer

How using bundle-wizard can help you build faster web apps

6 min read 1768

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 React or 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.

While popular modern JavaScript bundling tools, like webpack, are powerful ways to package vendor libraries and custom first-party code, the output they create can be a bit mysterious. When we build our JavaScript, we all hope that optimizations like tree-shaking and code-splitting are working optimally to reduce the amount of JavaScript we send to the client, but it can be tough to know for sure.

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 bundle-wizard to make it effortless to answer the question of precisely what JavaScript is actually making its way into your users’ hands. But before we do that, let’s take a moment to review why exactly it’s important to know what JavaScript is being loaded in the first place.

Ways JavaScript can slow down your app’s startup performance

JavaScript is a costly asset for a few reasons. While it’s pretty obvious that a large JavaScript file will take longer for the browser to download than a small one, a large JavaScript file is also potentially more expensive for the browser to process (parse and compile) than a corresponding image of a similar size.

Once the JavaScript is downloaded and compiled, the browser has to execute it. There are two potential user experience issues to look for here. First, unless you’ve server-rendered your app, the user will see a blank white screen or loading view for a potentially long time as your app works to build a page from scratch.

But even if you’ve had the foresight to pre-render the page you send down to the client, your JavaScript will still have to be downloaded, parsed, and executed before the user can interact with the page. If any of the JavaScript takes a long time to run—creating so-called long-tasks—your users could be faced with a completely non-responsive app that does not register clicks or keyboard input on desktop, or taps on mobile, for as long as it takes for their browsers to complete the JavaScript start-up work.

Many developers use powerful laptops on high-speed internet connections, and their apps tend to load quite fast on their development machines, obscuring performance issues. But for users on mobile phones dealing with occasional connectivity issues, especially those on lower-end devices with less-powerful CPUs, excess JavaScript can pose a real problem.

We made a custom demo for .
No really. Click here to check it out.

Using bundle-wizard

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 [site name], you can generate an interactive visualization that allows you to explore the JavaScript loaded by any entry point to your production site. As an example, running 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 vendors~Mweb.9ef0d432dd704f4f0943.js to the right, which contains JavaScript libraries like React and the polyfill library core-js.

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:

reddit comments section in bundle wizard

As you might expect for a message board site, the Post component is the largest of all the components contained in the major bundles.

Code coverage

The background colors of all the boxes represent how much of the code was actually run by the page on startup:

  1. 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
  2. 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
  3. 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).

Third-party scripts

Bundle-wizard provides the ability to toggle the visibility of all scripts, not just bundles with sourcemaps, by unchecking the “only show JS bundles with sourcemaps” option. This will help you compare the size of third-party scripts, such as analytics and tracking scripts, with the JavaScript module code that was bundled up and sent to the browser:

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.

Script priorities

When a user loads your site, their browser loads JavaScripts files in a certain order based on what it perceives to be the urgency of each request. Any bundle that is part of the critical request chain should have a high priority, while bundles containing code for initially unseen views, and third party scripts containing subsidiary functionality, should typically have a lower priority. If you visit the “Summary Tab” on the Reddit bundle-wizard example, you’ll see two lists of bundles—a “high priority” list that, as we would expect, shows mostly first-party bundled code, and a “low priority” list that is mostly third party scripts.

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.

Long tasks

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:

long task bundle wizard

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:

evaluate scripts in bundle wizard

To look into which functions might be contributing to this task time, we can take the following steps:

  1. Make sure the yellow “Evaluate script” bar you selected in step one is still selected
  2. Select the “Bottom-Up” tab
  3. Make sure the “No Grouping” option is selected
  4. Sort by “Self Time” in descending order

bundle-wizard tab

(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.

Conclusion

Please give 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.

: Full visibility into your 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.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Alex Holachek Frontend developer

Leave a Reply