Customer Success Engineer, Blue Triangle
The impact of JS file size
Reducing the impact of JS delivery
- Make JS load faster in the browser.
- Reduce how much JS you use to build your page.
- Minify all JS resources, and use minified third-party JS.
- Self-host third-party JS (or use a service worker).
- Compress JS.
- Use push and preload for critical JS.
- Only load JS when it’s needed.
- Eliminate dead JS code.
- Split JS files to deliver essential components.
- Load non-critical JS components asynchronously.
- Cache JS.
For the following, it’s important to make sure your user base has browser support for the methods implemented. Real User Monitoring lets you see which versions are most common among your users and should be considered essential for testing any changes you make to your site.
Make JS load faster in the browser
Reduce how much JS you use to build the page
Google Developers has produced guidelines based on user perceptions of performance delays, which you can use to judge whether JS is necessary on a page in conjunction with testing.
User Perception Of Performance Delays
0 to 16ms
Users are exceptionally good at tracking motion, and they dislike it when animations aren't smooth. They perceive animations as smooth so long as 60 new frames are rendered every second. That's 16ms per frame, including the time it takes for the browser to paint the new frame to the screen, leaving an app about 10ms to produce a frame.
0 to 100ms
Respond to user actions within this time window and users feel like the result is immediate. Any longer, and the connection between action and reaction is broken.
100 to 300ms
Users experience a slight perceptible delay.
300 to 1000ms
Within this window, things feel part of a natural and continuous progression of tasks. For most users on the web, loading pages or changing views represents a task.
1000ms or more
Beyond 1000 milliseconds (1 second), users lose focus on the task they are performing.
10000ms or more
Beyond 10000 milliseconds (10 seconds), users are frustrated and are likely to abandon tasks. They may or may not come back later.
Use minified JS files
Minification strips out unnecessary punctuation, white space, and new line characters. True minification also uses obfuscation, which renames variables to names that require fewer bytes of data for the browser to read than human-readable variable names.
Self-host third-party JS where applicable (or use a service worker)
An alternative to self-hosting third-party scripts is using a service worker to determine how often a piece of content should be fetched from the network, although this option may not be as accessible as self-hosting for sites with legacy architecture.
Use push and preload for critical JS
Server push and preload can both be used to deliver files that are critical to rendering the page earlier in the page load than normal.
A file is loaded with push when indicated server-side – the server receives a request and has been told beforehand that x, y, and z resources should be downloaded with it. Notably, the push methodology can only be used on HTTP/2.
Preload is a little different, where linked resources can be given the “preload” attribute in the HTML file. As soon as the page gets delivered and as HTML parsing is just beginning, these resources are requested from the server. This is faster than normal HTML parsing, because resources aren’t then requested in the order they are listed in the HTML.
Only load JS when it’s needed
Eliminate dead code with tree shaking
Tree shaking, or dead code elimination, gets rid of – you guessed it – code that isn’t included on your page. For example, say you’re downloading the minified JQuery library, but you only use a handful of the features offered by JQuery in your own code when building your page. Dead code elimination would allow you to download only the parts of the JQuery library that you’re using.
The term “tree-shaking” was popularized by Rollup, which implements this feature, but it’s also available through webpack and source-map-explorer. Notably, this functionality only works with ES2015 modules, using the syntax to reference JS bundles within other files. If you’re using CommonJS, there are some options available like webpack-common-shake, but you will likely run into limitations with CommonJS. Long-term, if you would like to use tree-shaking, ES2015 or later will be most useful.
Deliver essential components with route-based chunking, or code splitting
How you determine where to split your code depends on the kind of application you’re loading, who your users are, and what your backend architecture is like. There are a few types of code splitting:
- Vendor splitting separates your JS from JS provided by your framework vendor, like React or Vue. Vendor splitting prevents reloading a larger bundle when a feature in either code source is changed. Vendor splitting should be done in every web application.
- Splitting with entry points separates your code based on where in your code webpack starts to identify dependencies in your application. If your site does not use client-side routing, (e.g., SPA uses client-side routing), using entry points to split your code may make sense. With this kind of splitting, using a tool like Bundle Buddy to ensure that duplicate bundles aren’t included in a dependency tree can help pages load faster.
- Dynamic code splitting works well with applications that use client-side routing, like single-page applications, because not all users will use every piece of functionality on a page. This can be done using the dynamic import () statement, which returns a Promise. For that reason, you can also use dynamic imports with async/await. Parcel and webpack will both look for the dynamic import () statement to lazy load resources as they’re needed.
Load non-critical JS components asynchronously
defer attributes for external scripts is essential, especially with third-party scripts. Both
async attribute takes precedence over
Caching can be done several ways and is inter-related with some of the functionality of service workers. What caching allows you to do is determine the rules for when a browser should download a resource from the server instead of getting it much faster, no download required, from the cache.
Takeaways and the TL;DR
- Smaller page sizes
- Faster delivery
- More efficient processing
In this post, we cover the first two, which are interconnected. Smaller page sizes are possible when you reduce the amount of code you deliver to your users, thereby delivering them faster.