# Performance

How do we actually know where we stand in terms of performance, and what exactly our performance bottlenecks are? Is it expensive JavaScript, slow web font delivery, heavy images, or sluggish rendering? Have we optimized enough with tree-shaking, scope hoisting, code-splitting, and all the fancy loading patterns with intersection observer, progressive hydration, clients hints, HTTP/3 and service workers? And, most importantly, where do we even start improving performance and how do we establish a performance culture long-term?

Back in the day, performance was often a mere afterthought. Often deferred till the very end of the project. Let's list some key points of performance in our front-end area.

# Critical Rendering Path

Optimizing the critical rendering path allows the browser to paint the page as quickly as possible: faster pages translate into higher engagement, more pages viewed, and improved conversion. To minimize the amount of time a visitor spends viewing a blank screen, we need to optimize which resources are loaded and in which order.

In fact, when we talk about the critical rendering path we are typically talking about the HTML markup, CSS, and JavaScript. Images do not block the initial render of the page—although we should also try to get the images painted as soon as possible. But we also need to know that the load event (also known as onload), is blocked on the image. The onload event marks the point at which all resources that the page requires have been downloaded and processed.

What happened?

  • We need to fetch and parse the CSS file to construct the CSSOM, and we need both the DOM and CSSOM to build the render tree.
  • Because the page also contains a parser blocking JavaScript file, the domContentLoaded event is blocked until the CSS file is downloaded and parsed: because the JavaScript might query the CSSOM, we must block the CSS file until it downloads before we can execute JavaScript.

But there are several strategies that can make our page render faster.

# Deferred Scripts VS Asynchronous Scripts

Setting the defer attribute on a <script> element signals to the browser that download should begin immediately but execution should be deferred.

Even though the <script> elements are included in the document <head>, they will not be executed until after the browser has received the closing </html> tag.

The async attribute is similar to defer in that it changes the way the script is processed. Also similar to defer, async applies only to external scripts and signals the browser to begin downloading the file immediately. Unlike defer, scripts marked as async are not guaranteed to execute in the order in which they are specified. The second script file might execute before the first, so it’s important that there are no dependencies between the two. It’s recommended that asynchronous scripts not modify the DOM as they are loading.

# Measure performance with the RAIL model

RAIL is a user-centric(以用户为中心) performance model that provides a structure for thinking about performance. RAIL stands for four distinct aspects of web app life cycle: response, animation, idle(空闲), and load.

  1. Response: process events in under 50ms

Goal: Complete a transition initiated by user input within 100 ms, so users feel like the interactions are instantaneous. (在 100 毫秒内完成由用户输入启动的转换,让用户感觉交互是即时的。)

  1. Animation: produce a frame in 10 ms

Produce each frame in an animation in 10 ms or less. Technically, the maximum budget for each frame is 16 ms (1000 ms / 60 frames per second ≈ 16 ms), but browsers need about 6 ms to render each frame, hence the guideline of 10 ms per frame.

  1. Idle: maximize idle time

Use idle time to complete deferred work. For example, for the initial page load, load as little data as possible, then use idle time to load the rest. Sites that load quickly have longer average sessions, lower bounce rates, and higher ad viewability. For subsequent loads, a good target is to load the page in under 2 seconds.

  1. Load: deliver content and become interactive in under 5 seconds

When pages load slowly, user attention wanders, and users perceive the task as broken.

# Debounce, Throttle and requestAnimationFrame

Having a debounced or throttled version of our function is especially useful when we are attaching the function to a DOM event. Why? Because we are giving ourselves a layer of control between the event and the execution of the function. Remember, we don’t control how often those DOM events are going to be emitted. But it can be changed.

  • Debounce

The Debounce technique allow us to "group" multiple sequential calls in a single one.

Imagine you are in an elevator. The doors begin to close, and suddenly another person tries to get on. The elevator doesn’t begin its function to change floors, the doors open again. Now it happens again with another person. The elevator is delaying its function (moving floors), but optimizing its resources.

  • Throttle

By using _.throttle, we don’t allow to our function to execute more than once every X milliseconds.

The main difference between this and debouncing is that throttle guarantees the execution of the function regularly, at least every X milliseconds.

  • requestAnimationFrame (rAF)

requestAnimationFrame is another way of rate-limiting the execution of a function.

It can be thought as a _.throttle(dosomething, 16). But with a much higher fidelity, since it’s a browser native API that aims for better accuracy.

    # Reference

    [1] Front-End Performance Checklist 2021 (opens new window)

    [2] Analyzing Critical Rendering Path Performance (opens new window)

    [3] Measure performance with the RAIL model (opens new window)

    [4] Lighthouse (opens new window)

    [5] Debouncing and Throttling Explained Through Examples (opens new window)