Jump to content
Slate Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate Marble
Slate Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate Marble
Sign in to follow this  
Rss Bot

How to code faster, lighter JavaScript

Recommended Posts

Building interactive websites can involve sending JavaScript to your users. Often, too much of it. Have you been on a web page on your phone that looked like it had loaded, only to have nothing happen when you tapped a link or tried to scroll? We all have. 

Byte-for-byte, JavaScript is still the most expensive resource we send to mobile phones because it can delay interactivity in significant ways. In this feature we'll cover some strategies for delivering JavaScript efficiently to your users on mobile, while still giving them a valuable experience. On this page, we'll dig into exactly what's causing the problem. Jump to page 2 for some advice on exactly how to reduce your JavaScript load times, including some web design tools to help you monitor your sites effectively.  

Why is JS slowing your mobile sites down? 

When users access your site, you're probably sending down a lot of files, many of which are scripts. Perhaps you added a quick JavaScript library or plugin but didn't have a chance to check just how much code it was pulling in? It's happened to many of us. As much as I love JavaScript, it's always the most expensive part of your site. I'd like to explain why this can be a major issue.

Statistics from the HTTP Archive state of JavaScript report, July 2018

Statistics from the HTTP Archive state of JavaScript report, July 2018

Many popular sites ship megabytes of JavaScript to their mobile web users. The average web page today currently ship a little less – a median of about 350kB of minified and compressed JavaScript. Uncompressed, that bloats up to over 1MB of script a browser needs to process. Experiences that ship down this much JavaScript take more than 14 seconds to load and get interactive on mobile devices. 

A large factor of this is how long it takes to download code on a mobile network and then process it on a mobile CPU. Not only can that 350kB of script for a median site from earlier take a while to download, the reality is, if we look at popular sites, they actually ship down a lot more script than this. We're hitting this ceiling across both desktop and mobile web, where sites are sometimes shipping multiple megabytes of code that a browser then needs to process. The question to ask is: can you afford this much JavaScript?

Sites today will often send the following in their JavaScript bundles:

  • A suite of user-interface components (for example, code for widgets, carousels or drawers)
  • A client-side framework or user-interface library
  • Polyfills (often for modern browsers that don't need them)
  • Full libraries vs only what they use (for example, Moment.js and locales vs a smaller alternative like date-fns or Luxon)

This code adds up. The more there is, the longer it will take for a page to load.

Loading a modern web page

Loading is a journey infographic

Click the icon in the top right to expand the image

Loading a web page is like a film strip that has three key moments: 

  • Is it happening? The moment you're able to deliver some content to the screen. Has the navigation started, has the server started responding?
  • Is it useful? The moment when you've painted text or content that enables the user to derive value from the experience and engage with it.
  • Is it usable? The moment when a user can start meaningfully interacting with the experience and have something happen.

I mentioned this term 'interactive' earlier but what does that mean? For a page to be interactive, it must be capable of responding quickly to user input. A small JavaScript payload can ensure this happens fast. Whether a user clicks on a link or scrolls through a page, they need to see that something is actually happening in response to their actions. An experience that can't deliver on this will frustrate your users. 

When a browser runs many of the events you're probably going to need, it's likely going to do it on the same thread that handles user input. This thread is called the main thread. Too much (main thread) JavaScript can delay interactivity for visible elements. This can be a challenge for many companies.

Why is JavaScript so expensive?

So why exactly is JavaScript causing these problems? A request is sent to a server, which then returns some HTML. The browser parses that markup and discovers the necessary code (CSS and JavaScript) and resources (images, fonts etc) composing it. Once complete, the browser has to download and process these files.

If we want to be fast at JavaScript, we have to download it and process it quickly. That means we have to be fast at the network transmission and the parsing, compiling and execution of our scripts. If you spend a long time parsing and compiling script in a JavaScript engine, that delays how soon a user can interact with your experience. 

processing times infographic

Click the icon in the top right to enlarge

Keep in mind that resources on the web have different costs. A 200kB script has a different set of costs to a 200kB JPG. They might take the same amount of time to download but when it comes to processing the costs aren't the same. 

A JPEG image needs to be decoded, rasterised and painted on the screen. This can usually be done quickly. A JavaScript bundle needs to be downloaded and then parsed, compiled and executed. This can take longer than you might think on mobile hardware.

What is a good target for interactivity?

We on the Chrome team feel your baseline should be getting interactive in under five seconds on a slow 3G or 4G connection on a median mobile device. You might say: 'My users are all on fast networks and high-end phones!' But are they? You may be on 'fast' coffee-shop Wi-Fi but effectively only getting 2G or 3G speeds. Variability matters.

infographic javascript

Click the icon in the top right to expand the image

Mobile is a spectrum composed of low-end, median and high-end devices. If we're fortunate, we may have a high-end phone, but the reality is that not all users will have those devices.

They may be on a low-end or median phone and the disparity between these multiple classes of devices can be stark due to thermal throttling, difference in cache sizes, CPU, GPU – you can end up experiencing different processing times for resources like JavaScript, depending on the device you're using. Your users on low-end phones may even be in the US

Some users won't be on a fast network or have the latest and greatest phone, so it's vital that we start testing on real phones and networks. Fast devices and networks can actually sometimes be slow; variability can end up reducing the speed of absolutely everything. Test on a real phone or at least with mobile emulation. Developing with a slow baseline ensures everyone – both on fast and slow setups – benefits. 

infographic time to interactive google news

Click the icon in the top right to expand the image

Checking your analytics to understand what devices your users are accessing your site with is a useful exercise. WebPageTest has a number of Moto G4 phones preconfigured under the Mobile profiles. This is valuable in case you're unable to purchase your own set of median-class hardware for testing. 

It's really important to know your audience. Not every site needs to perform well on 2G on a low-end phone. That said, aiming for a high level of performance across the entire spectrum ensures that every potential user accessing your site has a chance to load it up fast.

Next page: Top tips for coding faster, lighter JavaScript

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. 

JavaScript code-splitting

Splitting large, monolithic JavaScript bundles can be done on a page, route or component basis

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. 

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.

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:

With React Loadable, we can dynamically import the gallery component as follows:

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.

Lighthouse audits

Lighthouse runs a series of audits against a page and generates a report on it

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

infographic Javascript

Click the icon in the top right to enlarge

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:

View the full article

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

×