How to code faster, lighter JavaScript
Top advice on how to optimise your JavaScript for mobile.
Many small changes can lead to big gains. Enable users to interact with your site with the least amount of friction. Run the smallest amount of JavaScript to deliver real value. This can mean taking incremental steps to get there but, in the end, your users will thank you. Here's some advice for making that happen.
Introduce code splitting
Code splitting helps you break up your JavaScript so you only load the code a user needs upfront and lazy-load the rest. This helps avoid shipping a monolithic main.js file to your users containing JavaScript for the whole site versus just what the page needs.
The best approach to introduce code splitting into your site is using the dynamic import() syntax. What follows is an example of using JavaScript Modules to statically 'import' some math code. Because we're not loading this code dynamically (lazily) when it's needed, it will end up in our default JavaScript bundle.
import { add } from './math';
console.log(add(30, 15));
After switching to dynamic import(), we can lazily pull in the math utilities when they are needed. This could be when the user is about to use a component requiring it, or navigating to a new route that relies on this functionality. Below we import math after a button click.
const btn = document.getElementById('load');
btn.addEventListener('click', () => {
import('./math').then(math => {
console.log(math.add(30, 15));
});
});
When a JavaScript module bundler like Webpack sees this import() syntax, it starts code splitting your app. This means dynamic code can get pushed out into a separate file that is only loaded when it is needed.
Code splitting can be done at the page, route or component level. Tools like Create React App, Next.js, Preact-CLI, Gatsby and others support it out of the box. Guides to accomplish this are available for React, Vue.js and Angular.
If you're using React, I'm happy to recommend React Loadable, a higher-order component for loading components efficiently. It wraps dynamic imports in a nice API for introducing code splitting into an app at a given component.
Here is an example statically importing a gallery component in React:
import GalleryComponent from './GalleryComponent';
const MyComponent = () => (
<GalleryComponent/>
);
With React Loadable, we can dynamically import the gallery component as follows:
import Loadable from 'react-loadable';
const LoadableGalleryComponent = Loadable({
loader: () => import('./GalleryComponent'),
loading: () => <div>Loading...</div>,
});
const MyComponent = () => (
<LoadableGalleryComponent/>
);
Many large teams have seen big wins off the back of code splitting recently. In an effort to rewrite their mobile web experiences to make sure users were able to interact with their sites as soon as possible, both Twitter and Tinder saw up to a 50 per cent improvement in time to interactive when they adopted aggressive code splitting.
Audit your workflow
Stacks like Next.js, Preact CLI and PWA Starter Kit try to enforce good defaults for quickly loading and getting interactive on average mobile hardware.
Another thing many of these sites have done is adopt auditing as part of their workflow. Thankfully, the JavaScript ecosystem has a number of great tools to help with bundle analysis. Tools like Webpack Bundle Analyzer, Source Map Explorer and Bundle Buddy enable you to audit your bundles for opportunities to trim them down.
If you're unsure whether you have any issues with JavaScript performance, check out Lighthouse. Lighthouse is a tool baked into the Chrome Developer Tools and is also available as a Chrome extension. It gives you an in-depth analysis that highlights opportunities to improve performance.
We've recently added support for flagging high JavaScript boot-up time to Lighthouse. This audit highlights scripts that might be spending a long time parsing/compiling, which delays interactivity. You can look at this audit as opportunities to either split up those scripts or just do less work.
Check you're not shipping unused code
Another thing you can do is make sure you're not shipping unused code down to your users: Code Coverage is a feature in Chrome DevTools that alerts you to unused JavaScript (and CSS) in your pages. Load up a page in DevTools and the Coverage tab will display how much code was executed vs how much was loaded. You can improve the performance of your pages by only shipping the code that a user needs.
This can be valuable for identifying opportunities to split up scripts and defer the loading of non-critical ones until they're needed. Thankfully, there are ways we can we can try to work around this and one way is having a performance budget in place.
Devise a performance budget
Performance budgets are critical because they keep everybody on the same page. They create a culture of shared enthusiasm for constantly improving the user experience and team accountability. Budgets define measurable constraints so a team can meet their performance goals. As you have to live within the constraints of budgets, performance is a consideration at each step, as opposed to an afterthought. Per Tim Kadlec, metrics for performance budgets can include:
- Milestone timings: Timings based on the user experience loading a page (e.g. time-to-interactive).
- Quality-based metrics: Based on raw values (e.g. weight of JavaScript, number of HTTP requests), focused on the browser experience.
- Rule-based metrics: Scores generated by tools such as Lighthouse or WebPageTest; often a single number or series to grade your site.
Performance is more often a cultural challenge than a technical one. Discuss performance during planning sessions. Ask business stakeholders what their performance expectations are. Do they understand how performance can impact the business metrics they care about? Ask engineering teams how they plan to address performance bottlenecks. While the answers here can be unsatisfactory, they get the conversation started.
What about tooling for performance budgets? You can set up Lighthouse scoring budgets in continuous integration with the Lighthouse CI project. A number of performance monitoring services support setting perf budgets and budget alerts including Calibre, Treo and SpeedCurve.
4 quick ways to lessen JS load times
Modern sites often combine all of their JavaScript into a single, large bundle. When JavaScript is served this way, download and processing times can be significant on mobile devices and networks. Here are a few tips for how to ensure you load your JavaScript quickly.
01. Only load the JS required for the current page
Prioritise what a user will need and lazy-load the rest with code splitting. This gives you the best chance at loading and getting interactive fast. Learn to audit your JavaScript code to discover opportunities to remove non-critical code.
02. Optimise your JavaScript
Use compression, minification and other JS optimisation techniques. Compression and minification are good optimisations for shipping fewer bytes of JavaScript to your users. If you’re already gzipping JavaScript, consider evaluating Brotli for even more savings. Building a site using Webpack and a framework? Tree shaking (removing unused imported code), trimming unused libraries and polyfills, opting for leaner versions of utilities all add up to some nice savings.
03. Assess the UX benefits
If client-side JavaScript isn’t benefiting the user experience, ask yourself if it’s really necessary. Maybe server-side-rendered HTML would actually be faster. Consider limiting the use of client-side frameworks to pages that absolutely require them. Server-rendering and client-rendering are a disaster if done poorly.
04. Embrace performance budgets
Embrace performance budgets and learn to live within them. For mobile, aim for a JS budget of < 170kB minified/compressed. Uncompressed this is still ~0.7MB of code. Budgets are critical to success; however, they can’t magically fix performance in isolation. Team culture, structure and enforcement matter.
Resources
Real-world performance budgets
A deep-dive into why performance budgets matter. This guide by Alex Russell questions if we can afford all the JavaScript we load for users on median mobile phones given their impact on user experience.
Reducing JavaScript payloads with code splitting
A practical guide to reducing how much JavaScript you’re loading Webpack or Parcel. It also includes links to code-splitting guides for React, Angular and others.
Reducing JavaScript payloads with tree shaking
Tree shaking is a form of dead code elimination. This guide covers how to remove JavaScript imports not being used in your web pages to help trim down your JavaScript bundles.
Lighthouse
Lighthouse is a free automated tool for improving the quality of web pages by the Chrome team. It has audits for performance, accessibility and more.
Pinterest case study
Pinterest reduced its JavaScript bundles from 2.5MB to < 200kB and reduced time-to-interactive from 23 seconds to 5.6 seconds. Revenue went up 44 per cent, sign-ups are up 753 per cent, weekly active users on mobile web are up 103 per cent.
AutoTrader case study
AutoTrader reduced its JavaScript bundle sizes by 56 per cent and reduced time-to-interactive by ~50 per cent.
This article was originally published in net, the world's best-selling magazine for web designers and developers. Buy issue 313 or subscribe.
Read more:
Thank you for reading 5 articles this month* Join now for unlimited access
Enjoy your first month for just £1 / $1 / €1
*Read 5 free articles per month without a subscription
Join now for unlimited access
Try first month for just £1 / $1 / €1
Get the Creative Bloq Newsletter
Daily design news, reviews, how-tos and more, as picked by the editors.