If you are doing performance optimization, you will definitely want to optimize the location of the script tag and the link tag. Of course, most of the views are that the script tag is placed before the </body>, the link tag is placed in the title; Or it can be combined with Async, defer, Preload, or prefech, with the sole purpose of making the page appear to the user as quickly as possible. Only the parts of the HTML file that are retrieved by the browser will be discussed below.
Browser rendering process
The browser retrieves the HTML file and begins rendering. The WebKit engine is used as an example.
- Parsing the HTML produces a DOM tree
- Parsing CSS styles produces a CSSOM tree
- DOM tree and CSSOM Tree RenderTree
- RenderTree(Layout): Determines the position on the screen
- To draw (paint)
- Composite: Combine multiple layers
In order to improve the user experience, the rendering engine renders the result to the user as soon as possible. It does not wait for all HTML to be parsed before rendering. It renders the received part of the page while the network layer obtains the document (Experiment 3 will prove this).
Preparation of experimental environment
1. Simulation server: required files hand.js, style.css, server.js
// server.js
const http = require('http');
const fs = require('fs')
http.createServer(function (request, response) {
if (request.url === '/index.html') {
fs.readFile('./index.html'.(err, data) = > {
response.setHeader('Content-Type'.'text/html');
if(! err) { response.end(data); }else {
response.end('html not found'); }})}if (request.url === '/hand.js') {
fs.readFile('./static/hand.js'.(err, data) = > {
response.setHeader('Content-Type'.'text/javascript');
if(! err) {setTimeout(() = > {
response.end(data);
}, 100);
}else {
response.end('html not found'); }})}if (request.url === '/style.css') {
fs.readFile('./static/style.css'.(err, data) = > {
response.setHeader('Content-Type'.'text/css');
if(! err) {setTimeout(() = > {
response.end(data);
}, 1000);
}else {
response.end('html not found'); }})}if (request.url === '/favicon.ico') {
response.end()
}
}).listen(8888);
console.log('port 8888')
// hand.js(no content)
Copy the code
// style.css
p {
color: red;
}
Copy the code
The front part
<! --index.html-->
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<p>I was the first</p>
<p>I was the second</p>
<p>I was the third</p>
</body>
</html>
Copy the code
Note: Please ignore the first render frame reported by experiments 1, 3, 8, 9 and 10 in the following experimental pictures (because the browser will record a render frame before the page refresh).
The script tag
Why do script tags block page rendering?
Javascript can manipulate the DOM tree, but the browser doesn’t know if there’s any code in the script that manipulates the DOM (document.write, etc.), so assume the worst: stop parsing the DOM, so it’s more accurate to say that script tags prevent parsing the DOM
Explain some of the terms used in the following experiment (all explanations are from MDN)
DCL(DOMContentLoaded): The DOMContentLoaded event is fired when the HTML is fully loaded and parsed, without waiting for the stylesheet, image, or subframe to finish loading
L(Load): The load event is triggered when the entire page and all dependent resources such as stylesheets and images have been loaded
FP(First paint): Page navigation and the browser render the first pixel of a web page on the screen
Abbreviations of the above nouns will appear in the experiment screenshots below
Experiment 1: Inlining script tags
<head>
<meta charset="UTF-8">
<script>
var a = 0
for (let i = 0; i< 1000000000; i++) {
a += 1
}
</script>
</head>
<body>
<p>I was the first</p>
</body>
Copy the code
<head>
<meta charset="UTF-8">
</head>
<body>
<p>I was the first</p>
<script>
var a = 0
for (let i = 0; i< 1000000000; i++) {
a += 1
}
</script>
</body>
Copy the code
Experiment 2: Inlining script tags (a different implementation)
<head>
<meta charset="UTF-8">
</head>
<body>
<script>
const node = document.getElementsByTagName('p')
console.log(node[0]) // undefined
</script>
<p>I was the first</p>
</body>
<head>
<meta charset="UTF-8">
</head>
<body>
<p>I was the first</p>
<script>
const node = document.getElementsByTagName('p')
console.log(node[0])
</script>
</body>
Copy the code
Experiment 3: An externally introduced script tag is placed in the title
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="http://localhost:8888/hand.js"></script>
</head>
<body>
<p>I was the first</p>
</body>
</html>
Copy the code
Experiment 4: An externally introduced Script tag is placed in the body
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<p>I was the first</p>
<p>I was the second</p>
<script src="http://localhost:8888/hand.js"></script>
<p>I was the third</p>
</body>
</html>
Copy the code
Experiment 5: An externally introduced script tag is placed before </body>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<p>I was the first</p>
<p>I was the second</p>
<p>I was the third</p>
<script src="http://localhost:8888/hand.js"></script>
</body>
</html>
Copy the code
Experiment 6: An externally introduced script tag is placed in the title and async parameter is added
<head>
<meta charset="UTF-8">
<script src="http://localhost:8888/hand.js" async></script>
</head>
<body>
<p>I was the first</p>
<p>I was the second</p>
<p>I was the third</p>
</body>
Copy the code
Experiment 7: The externally introduced Script tag is placed in the title and the defer parameter is added
<head>
<meta charset="UTF-8">
<script src="http://localhost:8888/hand.js" defer></script>
</head>
<body>
<p>I was the first</p>
<p>I was the second</p>
<p>I was the third</p>
</body>
Copy the code
Conclusion:
- An inline script blocks DOM parsing and does not prerender the previously parsed DOM
- External script tags block DOM parsing, but previously parsed DOM browsers render first
- Adding async and defer forces the script tag not to block DOM parsing
- Defer blocks the DOMContentLoaded event, while Async does not
The CSS introduced by the link label
We typically refer to CSS style files with the link tag. If you look at vue’s packaged files, you’ll see that some of its script files are also introduced via the Link tag. But we won’t discuss that in this article. Style files do not block DOM parsing, but they do block DOM rendering. Let’s use a few experiments to prove how CSS blocks DOM rendering
Experiment 8: The CSS introduced by the link tag is placed in the title
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="http://localhost:8888/style.css">
</head>
<body>
<p>I was the first</p>
<p>I was the second</p>
<p>I was the third</p>
</body>
Copy the code
Experiment 9: The LINK tag introduces CSS before </body>
<head>
<meta charset="UTF-8">
</head>
<body>
<p>I was the first</p>
<p>I was the second</p>
<p>I was the third</p>
<link rel="stylesheet" href="http://localhost:8888/style.css">
</body>
Copy the code
Conclusion:
- CSS does not block DOM parsing, but it does block DOM rendering
- CSS at the end of the body will transition from unstyled to styled pages, which will make the user experience very bad
Script and CSS tags coexist
A static file must have both script and link tags, and they affect each other.
Experiment 10: Both link and script are placed in title, and link is placed before script
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="http://localhost:8888/style.css" />
<script src="http://localhost:8888/hand.js"></script>
</head>
<body>
<p>I was the first</p>
<p>I was the second</p>
<p>I was the third</p>
</body>
Copy the code
Experiment 11: Link and script are placed in title, and link is placed after script
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="http://localhost:8888/hand.js"></script>
<link rel="stylesheet" href="http://localhost:8888/style.css" />
</head>
<body>
<p>I was the first</p>
<p>I was the second</p>
<p>I was the third</p>
</body>
Copy the code
Experiment 12: Link in title, script before </body> (Case 1)
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="http://localhost:8888/style.css" />
</head>
<body>
<p>I was the first</p>
<p>I was the second</p>
<p>I was the third</p>
<script src="http://localhost:8888/hand.js"></script>
</body>
Copy the code
Experiment 13: Link in title, script before </body> (Case 2)
// Let's change the return time of the serve.js file and CSS
if (request.url === '/hand.js') {
fs.readFile('./static/hand.js'.(err, data) = > {
response.setHeader('Content-Type'.'text/javascript');
if(! err) {setTimeout(() = > {
response.end(data);
}, 1000);
}else {
response.end('html not found'); }})}if (request.url === '/style.css') {
fs.readFile('./static/style.css'.(err, data) = > {
response.setHeader('Content-Type'.'text/css');
if(! err) {setTimeout(() = > {
response.end(data);
}, 100);
}else {
response.end('html not found'); }})}Copy the code
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="http://localhost:8888/style.css" />
</head>
<body>
<p>I was the first</p>
<p>I was the second</p>
<p>I was the third</p>
<script src="http://localhost:8888/hand.js"></script>
</body>
Copy the code
Conclusion:
- CSS does not block the loading of external scripts, but it does block the execution of JS (GUI thread and JS thread are mutually exclusive, because it is possible for JS to manipulate CSS).
- Best practice: Place the script tag at the end of the body and the style tag before the body
Reference documentation
- Order Loading Thoughtfully
- Rendering Performance