Thursday, March 21, 2024

JavaScript Performance Optimization Tips: An Overview — SitePoint

JavaScript Performance Optimization Tips: An Overview — SitePoint

This post covers a lot of ground in a vast and rapidly changing landscape. Additionally, it discusses everyone's favorite topic: the JS Framework of the Month.

Try to adhere to the maxim "Tools, not rules" and limit the use of JS buzzwords. Please check the sources and conduct your own research after reading this post, as we won't be able to cover everything relevant to JS performance in 2000 words.

Let's start by addressing the following: more than 50% of your users are being excluded if you are only testing on your desktop device.

Given that Android devices under $100 are the preferred web gateway in emerging economies, this trend is only expected to increase. The next billion internet users will mostly visit your websites via mobile devices; the desktop is no longer the primary means of accessing the Internet.

It is not appropriate to test in Chrome DevTools device mode instead of using an actual device. Although it's a fundamentally different beast, using CPU and network throttling helps. Test using actual hardware.

You're most likely testing on your brand-new, $600 flagship phone even if you are using actual mobile devices. The problem is, your users don't actually own that device. The median gadget resembles a Moto G1, including a CPU and GPU that are incredibly weak and less than 1GB of RAM.

Let us examine its performance in parsing a typical JS bundle.

Addy Osmani: Average JS time spent on parsing and evaluating.

Hurt. This image is highly correlated and can be used as a predictor of overall JS performance, even if it only shows the parse and compile times of the JS (more on that later).

It's the World-Wide Web, not the Wealthy Western Web, to paraphrase Bruce Lawson. Hence, a device that is about 25 times slower than your MacBook or iPhone should be your goal for web performance. Give that some time to settle in. However, things worsen. Let's examine our true objectives.

Although it's impossible to categorize code as perfectly performant, we do have a user-centric performance model—the RAIL model—that we can use as a guide.

When an application reacts to a user action in less than 100 milliseconds, the user will consider it to be instantaneous. This holds true for tappable elements only; it does not apply to scrolling or dragging.

When scrolling and animating, we want to aim for a consistent 60 frames per second on a 60Hz panel. That yields a frame rate of about 16 ms. You really have 810 milliseconds to accomplish all the work out of the 16 millisecond limit; the remainder is consumed by the internals of the browser and other variations.

To enable the main thread to respond to user inputs, divide any costly, continually running tasks into smaller ones. A task should never delay user input by more than 50 milliseconds.

Aim for a page load time of less than 1000 milliseconds. Any more than that, and your users become agitated. Reaching the objective of making the page interactive on mobile devices—rather than just having it shown and navigable—is somewhat challenging. Actually, it's even less:

In real life, strive for a time-to-interactive mark of 5 seconds. Chrome makes use of it in their Lighthouse audit.

Are you sufficiently irritated? Alright. Let's get to work fixing the internet.

You may have seen that the time it takes for your website to load is the primary constraint. in particular, the download, parse, build, and execution times for JavaScript. The only option is to load JavaScript more sparingly and intelligently.

Think about what you're building before you optimize your code. Are you creating a VDOM library or a framework? Does your code require thousands of actions to be performed in real time? Are you working on a library that handles animations and/or user input under time constraints? If not, you might want to direct your time and efforts into a more worthwhile endeavor.

Writing performant code is important, but in the big picture, especially when discussing microoptimizations, it usually has little to no effect. Therefore, make sure to view the forest rather than just the trees before getting into a Stack Overflow dispute about.map vs. forEach vs. for loops by comparing data from JSperf.com. In most circumstances, it won't matter if 50k operations per second is 50 better than 1k operations per second on paper.

Essentially, the majority of non-performing JavaScript code is caused by the several processes that must be completed before the code can begin to execute, rather than by the actual code itself.

Here, we're discussing abstraction levels. Your computer's CPU executes machine code. The compiled binary format is used by the majority of the code that runs on your computer. (I should have said programs, but with all the Electron apps available these days, I said code.) That is to say, without requiring any OS-level abstractions, it operates natively on your hardware.

There is no pre-compiled JavaScript. It arrives in your browser, which functions as the OS for your JavaScript program, as readable code (via a somewhat slow network).

Prior to being compiled, the code must first be parsed, or read, and transformed into an indexable structure for computers. Before being executed by your device or browser, it is compiled into bytecode and then machine code.

The fact that JavaScript is single-threaded and operates on the browser's main thread is another crucial point to make. It follows that a single process can only operate at a time. If the DevTools performance timeline is full of yellow peaks and your CPU is operating at 100%, you will experience janky scrolling, lengthy or dropped frames, and other unpleasant things.

Paul Lewis: Nothing matters when everything matters.

Thus, a lot of work needs to be completed before your JavaScript runs. In the V8 engine of Chrome, parsing and compilation can account for up to 50% of the entire time that JS is executed.

Addy Osmani: Performance of JavaScript Startup.

There are methods around this, including utilizing asm.js to write code that is easier to compile to machine instructions and using service workers to complete tasks in the background and on a different thread, but that's a whole other discussion.

What you can do, though, is learn about what triggers paints and layouts and stay away from utilizing JS animation frameworks for everything. When there is no possible method to implement the animation using standard CSS transitions and animations, use the libraries.

They may be utilizing requestAnimationFrame(), composited properties, and CSS transitions, but they are still operating in JavaScript on the main thread. As there isn't much else they can do, they are essentially just bombarding your DOM with inline styles every 16 milliseconds. For the animations to remain fluid, you must ensure that all of your JavaScript code executes in less than 8 milliseconds every frame.

CSS transitions and animations, on the other hand, operate on the GPU's main thread when performed efficiently and don't result in relayouts or reflows.

This can provide your web apps the much-needed breathing room, since most animations execute either during user interaction or during loading.

For now, stick with CSS transitions and FLIP approaches; the Web Animations API feature set will allow you to create performant JS animations off the main thread.

Today, packages are everything. The days of Bower and several