script element is the 6th most popular HTML element in use (ahead of elements like
i, among countless others). We spend around 14 times as many bytes on it as we do on HTML, the building block of the web, and 6 times as many bytes as CSS.
We mentioned that the
It’s a bit disappointing that there isn’t a bigger gap here. While it’s dangerous to make too many assumptions about network or processing power based on whether the device in use is a phone or a desktop (or somewhere in between), it’s worth noting that HTTP Archive mobile tests are done by emulating a Moto G4 and a 3G network. In other words, if there was any work being done to adapt to less-than-ideal circumstances by passing down less code, these tests should be showing it.
That 153 KB equates to ~37% of the total script size that we send down to mobile devices. There’s definitely some room for improvement here.
One mechanism we have to potentially reduce the amount of code we send down is to take advantage of the
nomodule pattern. With this pattern, we create two sets of bundles: one bundle intended for modern browsers and one intended for legacy browsers. The bundle intended for modern browsers gets a
type=module and the bundle intended for legacy browsers gets a
This approach lets us create smaller bundles with modern syntax optimized for the browsers that support it, while providing conditionally loaded polyfills and different syntax to the browsers that don’t.
nomodule is broadening, but still relatively new. As a result, adoption is still a bit low. Only 3.6% of mobile pages use at least one script with
type=module and only 0.7% of mobile pages use at least one script with
type=nomodule to support legacy browsers.
Where does it come from?
Let’s drill into that a bit to see just how much third-party script we’re serving up.
Right up until the median, sites serve roughly the same number of first-party scripts as they do third-party scripts. At the median, 9 scripts per page are first-party, compared to 10 per page from third-parties. From there, the gap widens a bit: the more scripts a site serves in the total, the more likely it is that the majority of those scripts are from third-party sources.
script element, it must pause parsing of the HTML until the script has been downloaded, parsed, and executed. It’s a significant bottleneck and a common contributor to pages that are slow to render.
async attribute), which only halts the HTML parser during the parse and execution phases and not during the download phase, or deferred (with the
defer attribute), which doesn’t halt the HTML parser at all. Both attributes are only available on external scripts—inline scripts cannot have them applied.
On mobile, external scripts comprise 59.0% of all script elements found.
Of those external scripts, only 12.2% of them are loaded with the
async attribute and 6.0% of them are loaded with the
defer provides us with the best loading performance (by ensuring downloading the script happens in parallel to other work, and execution waits until after the page can be displayed), we would hope to see that percentage a bit higher. In fact, as it is that 6.0% is slightly inflated.
Back when supporting IE8 and IE9 was more common, it was relatively common to use both the
defer attributes. With both attributes in place, any browser supporting both will use
async. IE8 and IE9, which don’t support
async will fall back to
Nowadays, the pattern is unnecessary for the vast majority of sites and any script loaded with the pattern in place will interrupt the HTML parser when it needs to be executed, instead of deferring until the page has loaded. The pattern is still used surprisingly often, with 11.4% of mobile pages serving at least one script with that pattern in place. In other words, at least some of the 6% of scripts that use
defer aren’t getting the full benefits of the
There is an encouraging story here, though.
Harry Roberts tweeted about the anti-pattern on Twitter, which is what prompted us to check to see how frequently this was occurring in the wild. Rick Viscomi checked to see who the top culprits were, and it turns out “stats.wp.com” was the source of the most common offenders. @Kraft from Automattic replied, and the pattern will now be removed going forward.
One of the great things about the openness of the web is how one observation can lead to meaningful change and that’s exactly what happened here.
prefetch hint lets developers signify that a resource will be used on the next page navigation, therefore the browser should try to download it when the browser is idle.
preload hint signifies that a resource will be used on the current page and that the browser should download it right away at a higher priority.
Of those, nearly all of the usage is coming from
preload. While 16.6% of mobile pages use at least one
There’s a risk, particularly with
preload, of using too many hints and reducing their effectiveness, so it’s worth looking at the pages that do use these hints to see how many they’re using.
At the median, pages that use a
preload hint only use one. The long tail gets a bit more interesting, with 12
prefetch hints used at the 90th percentile and 7
preload hints used on the 90th as well. For more detail on resource hints, check out this year’s Resource Hints chapter.
As with any text-based resource on the web, we can save a significant number of bytes through minimization and compression. Neither of these are new optimizations—they’ve been around for quite awhile—so we should expect to see them applied in more cases than not.
The chart above shows that most pages tested (77%) get a score of 0.90 or above, meaning that few unminified scripts are found.
Minification is a great way to help reduce file size, but compression is even more effective and, therefore, more important—it provides the bulk of network savings more often than not.
Once again, this appears to be an area where third-party scripts are actually doing better than first-party scripts. If we break the compression methods out by first- and third-party, we see that 24% of third-party scripts have Brotli applied, compared to only 15% of first-party scripts.
Third-party scripts are also least likely to be served without any compression at all: 12% of third-party scripts have neither Gzip nor Brotli applied, compared to 19% of first-party scripts.
It’s worth taking a closer look at those scripts that don’t have compression applied. Compression becomes more efficient in terms of savings the more content it has to work with. In other words, if the file is tiny, sometimes the cost of compressing the file doesn’t outweight the miniscule reduction in file size.
Thankfully, that’s exactly what we see, particularly in third-party scripts where 90% of uncompressed scripts are less than 5 KB in size. On the other hand, 49% of uncompressed first-party scripts are less than 5 KB and 37% of uncompressed first-party scripts are over 10 KB. So while we do see a lot of small uncompressed first-party scripts, there are still quite a few that would benefit from some compression.
What do we use?
It’s important that we think critically about the tools we use to build the web and what the trade-offs are, so it makes sense to look closely at what we see in use today.
The popular libraries in use are largely unchanged from last year, with jQuery continuing to dominate usage and only one of the top 21 libraries falling out (lazy.js, replaced by DataTables). In fact, even the percentages of the top libraries has barely changed from last year.
Last year, Houssein posited a few reasons for why jQuery’s dominance continues:
WordPress, which is used in more than 30% of sites, includes jQuery by default. Switching from jQuery to a newer client-side library can take time depending on how large an application is, and many sites may consist of jQuery in addition to newer client-side libraries.
Both are very sound guesses, and it seems the situation hasn’t changed much on either front.
In fact, the dominance of jQuery is supported even further when you stop to consider that, of the top 10 libraries, 6 of them are either jQuery or require jQuery in order to be used: jQuery UI, jQuery Migrate, FancyBox, Lightbox and Slick.
When we look at the frameworks, we also don’t see much of a dramatic change in terms of adoption in the main frameworks that were highlighted last year. Vue.js has seen a significant increase, and AMP grew a bit, but most of them are more or less where they were a year ago.
It’s worth noting that the detection issue that was noted last year still applies, and still impacts the results here. It’s possible that there has been a significant change in popularity for a few more of these tools, but we just don’t see it with the way the data is currently collected.
What it all means
More interesting to us than the popularity of the tools themselves is the impact they have on the things we build.
First, it’s worth noting that while we may think of the usage of one tool versus another, in reality, we rarely only use a single library or framework in production. Only 21% of pages analyzed report only one library or framework. Two or three frameworks are pretty common, and the long-tail gets very long, very quickly.
When we look at the common combinations that we see in production, most of them are to be expected. Knowing jQuery’s dominance, it’s unsurprising that most of the popular combinations include jQuery and any number of jQuery-related plugins.
|jQuery, jQuery Migrate||658,628||10.4%|
|jQuery, jQuery UI||289,074||4.6%|
|jQuery, jQuery Migrate, jQuery UI||140,466||2.2%|
|Modernizr, jQuery, jQuery Migrate||85,296||1.3%|
|GSAP, Lodash, React, RequireJS, Zepto||61,935||1.0%|
|Modernizr, jQuery, jQuery UI||61,152||1.0%|
|Modernizr, jQuery, jQuery Migrate, jQuery UI||53,924||0.8%|
|Slick, jQuery, jQuery Migrate||51,686||0.8%|
|Lightbox, jQuery, jQuery Migrate||50,557||0.8%|
|FancyBox, jQuery, jQuery UI||44,193||0.7%|
|FancyBox, jQuery, jQuery Migrate||31,259||0.5%|
|MooTools, jQuery, jQuery Migrate||28,795||0.5%|
We do also see a fair amount of more “modern” frameworks like React, Vue, and Angular paired with jQuery, for example as a result of migration or inclusion by third-parties.
|Combination||Without jQuery||With jQuery|
|GSAP, Lodash, React, RequireJS, Zepto||1.0%|
|React, jQuery, jQuery Migrate||0.4%|
|GSAP, Hammer.js, Lodash, React, RequireJS, Zepto||0.2%|
More importantly, all these tools typically mean more code and more processing time.
The graph below shows the median bytes for pages where any of the top 35 most commonly detected frameworks were found, broken down by client.
On one of the spectrum are frameworks like React or Angular or Ember, which tend to ship a lot of code regardless of the client. On the other end, we see minimalist frameworks like Alpine.js and Svelte showing very promising results. Defaults are very important, and it seems that by starting with highly performant defaults, Svelte and Alpine are both succeeding (so far… the sample size is pretty small) in creating a lighter set of pages.
We get a very similar picture when looking at main thread time for pages where these tools were detected.
Ember’s mobile main thread time jumps out and kind of distorts the graph with how long it takes. (I spent some more time looking into this and it appears to be heavily influenced by one particular platform using this framework inefficiently, rather than an underlying problem with Ember itself.) Pulling it out makes the picture a bit easier to understand.
Tools like React, GSAP, and RequireJS tend to spend a lot of time on the main thread of the browser, regardless of whether it’s a desktop or mobile page view. The same tools that tend to lead to less code overall—tools like Alpine and Svelte—also tend to lead to lower impact on the main thread.
The gap between the experience a framework provides for desktop and mobile is also worth digging into. Mobile traffic is becoming increasingly dominant, and it’s critical that our tools perform as well as possible for mobile pageviews. The bigger the gap we see between desktop and mobile performance for a framework, the bigger the red flag.
As you would expect, there’s a gap for all tools in use due to the lower processing power of the emulated Moto G4. Ember and Polymer seem to jump out as particularly egregious examples, while tools like RxJS and Mustache vary only minorly from desktop to mobile.
What’s the impact?
If you recall, there was only a 30 KB difference between what is shipped to a mobile device versus a desktop device. Depending on your point of view, you could be forgiven for not getting too upset about the small gap in the amount of code sent to a desktop browser versus a mobile one—after all, what’s an extra 30 KB or so at the median, right?
The biggest problem comes when all of that code gets served to a low-to-middle-end device, something a bit less like the kind of devices most developers are likely to have, and a bit more like the kind of devices you’ll see from the majority of people across the world. That relatively small gap between desktop and mobile is much more dramatic when we look at it in terms of processing time.
The above chart uses the Pearson coefficient of correlation. There’s a long, kinda complex definition of what that means precisely, but the gist is that we’re looking for the strength of the correlation between two different numbers. If we find a coefficient of 1.00, we’d have a direct positive correlation. A correlation of 0.00 would show no connection between two numbers. Anything below 0.00 indicates a negative correlation—in other words, as one number goes up the other one decreases.
The most likely explanation for this is that Lighthouse’s accessibility tests aren’t as comprehensive (yet!) as what is available through other tools, like WebAIM, that have accessibility as their primary focus.
One other helpful audit that Lighthouse runs is to check for known security vulnerabilities in third-party libraries. It does this by detecting which libraries and frameworks are used on a given page, and what version is used of each. Then it checks Snyk’s open-source vulnerability database to see what vulnerabilities have been discovered in the identified tools.
This is what we call the jQuery effect. Remember how we saw that jQuery is used on a whopping 83% of pages? Several older versions of jQuery contain known vulnerabilities, which comprises the vast majority of the vulnerabilities this audit checks.
Of the roughly 5 million or so mobile pages that are tested against, 81% of them contain a vulnerable version of jQuery—a sizeable lead over the second most commonly found vulnerable library—jQuery UI at 15.6%.
In other words, if we can get folks to migrate away from those outdated, vulnerable versions of jQuery, we would see the number of sites with known vulnerabilities plummet (at least, until we start finding some in the newer frameworks).
The bulk of the vulnerabilities found fall into the “medium” severity category.
Carefully weighing the cost of the scripts we add to our pages and being willing to place a critical eye on our tools and ask more of them are our best bets for ensuring that we build a web that is accessible, performant, and safe.