In this post, there’s lots of stuff to cover across a wide and wildly changing landscape. It’s also a topic that covers everyone’s favorite: The JS Framework of the Month™.
We’ll try to stick to the “Tools, not rules" mantra and keep the JS buzzwords to a minimum. Since we won’t be able to cover everything related to JS performance in a 2000 word article, make sure you read the references and do your own research afterwards.
Setting the Stage
First of all, let’s get the following out of the way: if you’re testing exclusively on your desktop device, you’re excluding more than 50% of your users.
This trend will only continue to grow, as the emerging market’s preferred gateway to the web is a sub-$100 Android device. The era of the desktop as the main device to access the Internet is over, and the next billion internet users will visit your sites primarily through a mobile device.
Testing in Chrome DevTools’ device mode isn’t a valid substitute to testing on a real device. Using CPU and network throttling helps, but it’s a fundamentally different beast. Test on real devices.
Even if you are testing on real mobile devices, you’re probably doing so on your brand spanking new $600 flagship phone. The thing is, that’s not the device your users have. The median device is something along the lines of a Moto G1 — a device with under 1GB of RAM, and a very weak CPU and GPU.
Let’s see how it stacks up when parsing an average JS bundle.
Addy Osmani: Time spent in JS parse & eval for average JS.
Ouch. While this image only covers the parse and compile time of the JS (more on that later) and not general performance, it’s strongly correlated and can be treated as an indicator of general JS performance.
To quote Bruce Lawson, “it’s the World-Wide Web, not the Wealthy Western Web”. So, your target for web performance is a device that’s ~25x slower than your MacBook or iPhone. Let that sink in for a bit. But it gets worse. Let’s see what we’re actually aiming for.
What Exactly is Performant JS Code?
Now that we know what our target platform is, we can answer the next question: what is performant JS code?
While there’s no absolute classification of what defines performant code, we do have a user-centric performance model we can use as a reference: The RAIL model.
Sam Saccone: Planning for Performance: PRPL
If your app responds to a user action in under 100ms, the user perceives the response as immediate. This applies to tappable elements, but not when scrolling or dragging.
On a 60Hz monitor, we want to target a constant 60 frames per second when animating and scrolling. That results in around 16ms per frame. Out of that 16ms budget, you realistically have 8–10ms to do all the work, the rest taken up by the browser internals and other variances.
If you have an expensive, continuously running task, make sure to slice it into smaller chunks to allow the main thread to react to user inputs. You shouldn’t have a task that delays user input for more than 50ms.
You should target a page load in under 1000ms. Anything over, and your users start getting twitchy. This is a pretty difficult goal to reach on mobile devices as it relates to the page being interactive, not just having it painted on screen and scrollable. In practice, it’s even less:
Fast By Default: Modern Loading Best Practices (Chrome Dev Summit 2017)
In practice, aim for the 5s time-to-interactive mark. It’s what Chrome uses in their Lighthouse audit.
Now that we know the metrics, let’s have a look at some of the statistics:
53% of visits are abandoned if a mobile site takes more than three seconds to load
1 out of 2 people expect a page to load in less than 2 seconds
77% of mobile sites take longer than 10 seconds to load on 3G networks
19 seconds is the average load time for mobile sites on 3G networks.
And a bit more, courtesy of Addy Osmani:
apps became interactive in 8 seconds on desktop (using cable) and 16 seconds on mobile (Moto G4 over 3G)
at the median, developers shipped 410KB of gzipped JS for their pages.
Feeling sufficiently frustrated? Good. Let’s get to work and fix the web. ✊
Context is Everything
But what about the actual work that your code does aside from just booting up the website? There has to be some performance gains there, right?
Before you dive into optimizing your code, consider what you’re building. Are you building a framework or a VDOM library? Does your code need to do thousands of operations per second? Are you doing a time-critical library for handling user input and/or animations? If not, you may want to shift your time and energy somewhere more impactful.
It’s not that writing performant code doesn’t matter, but it usually makes little to no impact in the grand scheme of things, especially when talking about microoptimizations. So, before you get into a Stack Overflow argument about .map vs .forEach vs for loops by comparing results from JSperf.com, make sure to see the forest and not just the trees. 50k ops/s might sound 50× better than 1k ops/s on paper, but it won’t make a difference in most cases.