Content description

This article is not about the underlying principles of browser rendering or the nuts and nuts of front-end optimization, but rather a description of the process and an explanation of the principles behind it. This is because front-end optimization is a very large and scattered collection of knowledge, an article if you want to write about specific methods of optimization I’m afraid can only do some limited enumeration.

However, if you understand how the browser renders and how it works, you can actually master the guidelines. According to optimization principles, numerous specific optimization schemes can be implemented, and various precompilation, preloading, resource merging, and loading on demand schemes are optimized for browser rendering habits.

Key render path

When it comes to page rendering, there are several concepts that are highly relevant. The most important is the key render path, from which the other concepts can be expanded, as described below.

** Critical Rendering Path ** is the content related to the current user action. For example, when a user opens a page, the first screen displays the content related to the current user operation. Specifically, the browser receives HTML, CSS, JavaScript and other resources and processes them to render the Web page.

Understanding the process and principles of browser rendering is largely to optimize key rendering paths, but optimization should be a solution to a specific problem, so there are no rules for optimization. For example, in order to ensure the fastest display of the first screen content, progressive page rendering is usually mentioned, but in order to do progressive page rendering, it is necessary to do the splitting of resources, so what granularity to split, whether to split, different pages, different scenes different strategies. The determination of specific programs should consider both experience and engineering issues.

The process by which a browser renders a page

From a time consuming perspective, the browser requests, loads, and renders a page, and the time is spent doing five things:

  1. The DNS query
  2. A TCP connection
  3. An HTTP request is a response
  4. Server response
  5. Client-side rendering

This article discusses the fifth part, the browser rendering of content. This part (rendering tree building, layout and drawing) can be divided into the following five steps:

  1. Process HTML tags and build DOM trees.
  2. Process CSS tags and build CSSOM trees.
  3. Merge DOM and CSSOM into a render tree.
  4. Layout according to render tree to calculate geometric information for each node.
  5. Draw the individual nodes to the screen.

It’s important to understand that these five steps don’t have to be done all at once. If the DOM or CSSOM is modified, this process needs to be repeated in order to figure out which pixels need to be re-rendered on the screen. In real pages, CSS and JavaScript often modify the DOM and CSSOM multiple times, and here’s how they affect it.

Blocking render: CSS with JavaScript

When talking about resource blocking, it’s important to remember that modern browsers always load resources in parallel. For example, when an HTML Parser is blocked by a script, the Parser stops building the DOM but still recognizes the resources behind the script and preloads them.

Meanwhile, due to the following two points:

  1. By default, CSS is treated as a resource that blocks rendering, which means that the browser will not render any processed content until CSSOM is built.
  2. JavaScript can read and modify not only DOM properties, but also CSSOM properties.

Browsers delay JavaScript execution and DOM building when there are blocking CSS resources. In addition:

  1. When the browser encounters a script tag, DOM build is paused until the script completes execution.
  2. JavaScript can query and modify DOM and CSSOM.
  3. When CSSOM is built, JavaScript execution is paused until CSSOM is ready.

Therefore, the position of the script tag is important. In practice, the following two principles can be followed:

  1. CSS priority: CSS resources are introduced before JavaScript resources.
  2. JavaScript should affect DOM building as little as possible.

As browsers evolve faster and faster (Chrome’s current official stable version is 61), specific rendering strategies will evolve, but understanding these principles will make sense of how they evolve. Let’s look at how CSS and JavaScript block resources in detail.

CSS

<style> p { color: red; }</style>
<link rel="stylesheet" href="index.css">
Copy the code

Such link tags (inline or not) are treated as resources that block rendering, and the browser prioritises these CSS resources until CSSOM is built.

Both DOM and CSSOM are required in the key Render path of the Render Tree before the Tree is built. That is, HTML and CSS are resources that block rendering. HTML is obviously required, because the content, including the text we want to display, is stored in the DOM, so CSS is the solution.

The easiest thing to think about, of course, is to simplify CSS and provide it as quickly as possible. In addition, media types and media Queries can be used to unblock rendering.

<link href="index.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 30em) and (orientation: landscape)">
Copy the code

The first resource loads and blocks. The second resource sets the media type, which loads but does not block, and the print statement is only used when printing web pages. The third resource provides media queries that block rendering when conditions are met.

JavaScript

JavaScript is a little more complicated than CSS. Look at the following code:

<p>Do not go gentle into that good night,</p>
<script>console.log("inline")</script>
<p>Old age should burn and rave at close of day;</p>
<script src="app.js"></script>
<p>Rage, rage against the dying of the light.</p>
Copy the code
<p>Do not go gentle into that good night,</p>
<script src="app.js"></script>
<p>Old age should burn and rave at close of day;</p>
<script>console.log("inline")</script>
<p>Rage, rage against the dying of the light.</p>
Copy the code

Such script tags block HTML parsing, whether inline-script or not. The P tag above is parsed from top to bottom, and this process is interrupted by two pieces of JavaScript each time (during loading and execution).

So in real engineering, we often put resources at the bottom of the document.

Change the blocking mode: defer and async

Why defer and async from script loading? Because both of these things happen because of those blocking conditions. In other words, defer and Async can change those blocking situations.

First, note that the async and defer properties are not valid for inline-script, so the code for the three script labels in the following example is executed from top to bottom.

<! Print 1, 2, 3 from top to bottom
<script async>
  console.log("1");
</script>
<script defer>
  console.log("2");
</script>
<script>
  console.log("3");
</script>
Copy the code

Therefore, the next two sections discuss script tags with SRC attributes.

defer

<script src="app1.js" defer></script>
<script src="app2.js" defer></script>
<script src="app3.js" defer></script>
Copy the code

The defer attribute represents delayed execution of the imported JavaScript, meaning that the HTML does not stop parsing when the JavaScript loads, and the two processes are parallel. After the entire Document has been parsed and deferred -script has loaded (in no particular order), all the JavaScript code loaded by deferred -script is executed and the DOMContentLoaded event is triggered.

Defer does not change the order in which the code in the script executes; the sample code executes 1, 2, and 3. So defer differs from regular Script in two ways: it does not block HTML parsing when loading the JavaScript file, and the execution phase is deferred to after the HTML tag parsing is complete.

async

<script src="app.js" async></script>
<script src="ad.js" async></script>
<script src="statistics.js" async></script>
Copy the code

The async property represents the JavaScript introduced by asynchronous execution, and the difference from defer is that it will start executing if it is already loaded — either at the HTML parsing stage or after DOMContentLoaded is triggered. Note that JavaScript loaded this way still blocks the load event. In other words, async-script may be executed before or after DOMContentLoaded is fired, but must be executed before Load is fired.

The order in which multiple async-scripts are executed is uncertain. Note that the async property defaults to True when script tags are added dynamically to document, a topic we’ll continue in the next section.

document.createElement

Scripts created with document.createElement are asynchronous by default, as shown in the following example.

console.log(document.createElement("script").async); // true
Copy the code

So introducing JavaScript files by dynamically adding script tags does not block the page by default. If you want to execute synchronously, you need to set async property to false.

What if you use document.createElement to create a link tag?

const style = document.createElement("link");
style.rel = "stylesheet";
style.href = "index.css";
document.head.appendChild(style); / / jam?
Copy the code

In fact, this can only be determined by experimentation. What is known is that Chrome does not block rendering anymore. Firefox and IE used to block rendering.

Document. Write and innerHTML

Link or script tags added via document.write are equivalent to tags added to document, Because it operates on document Stream (so using Document. write for loaded pages automatically calls Document. open, which overwrites the content of the document). Normally, link blocks rendering and script executes synchronously. This is not recommended, however, and Chrome already displays a warning that it may be banned in the future. If an async property is added to a script introduced in this way, Chrome checks for homogeneity, which is not allowed for non-homogenous async-scripts.

If you use innerHTML to introduce a script tag, the JavaScript inside it is not executed. Of course, you can do this manually through eval(), but it’s not recommended. I’ve tried this out in Chrome if the link tag is introduced. Also, outerHTML and insertAdjacentHTML() should act the same way, and I didn’t experiment. These three should be used for text manipulation, that is, using them only to add text or plain HTML elements.

The resources

Mobile Analysis in PageSpeed Insights Web Fundamentals MDN – HTML element reference