I've just noticed strange behaviour with rendering before script is being performed. I always thought, that there is no chance for browsers to dequeue a task from render queue and execute it before script tag, since this is a single thread. And as you will see, it's not true. There is a simple example of code below:
console.log('SCRIPT EXECUTED')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>HTML</title>
<script>
requestAnimationFrame(function animate(time) {
console.log('RAF', performance.now());
});
</script>
</head>
<body>
<h1>Hello world</h1>
<script src="./script.js"></script>
</body>
</html>
One may see inconsistent output in console. Sometimes render comes up before script and vice versa. It may cause to flickering in UI for example.
Could anyone give a meaningful explanations of this?
External classic scripts are parser-blocking, i.e. the HTML parser will wait until the script's execution before continuing parsing the remaining of the document.
<script src="data:text/javascript,console.log(document.body.children[document.body.children.length - 1].nodeName);"></script>
<span>Should log SCRIPT and not SPAN</span>
But to also be render-blocking, they need to be in the <head>
of the document.
/*
* Since StackSnippets do wrap the HTML & JS content in the <body>,
* we use another <iframe> for our demo.
* But StackSnippet's console isn't available there,
* so we use postMessage and log from here.
*/
onmessage = ({data}) => console.log(...data);
<iframe srcdoc="
<!DOCTYPE HTML>
<html>
<head>
<script>
requestAnimationFrame(function animate(time) {
parent.postMessage(['RAF', performance.now()], '*');
});
</script>
<script src='./script'></script>
<script>
parent.postMessage(['SCRIPT EXECUTED'], '*')
</script>
</head>
<body>
<h1>HELLO WORLD</h1>
</body>
</html>"></iframe>
Note that Firefox apparently doesn't support render-blocking yet.
Also, you may be interested in the blocking="render"
attribute, which allows a <script>
to not be parser blocking (defer
, async
, or module
) while being render-blocking, though this is currently only available for external scripts, but this might change in the near future.
onmessage = ({data}) => console.log(...data);
/* In Chrome this logs
* SCRIPT EXECUTED
* H1
* "RAF", a number
*/
<iframe srcdoc="
<!DOCTYPE HTML>
<html>
<head>
<script>
requestAnimationFrame(function animate(time) {
parent.postMessage(['RAF', performance.now()], '*');
});
</script>
<script defer src='data:text/javascript,parent.postMessage([document.body.children[document.body.children.length - 1].nodeName], `*`);'></script>
<script>
parent.postMessage(['SCRIPT EXECUTED'], '*')
</script>
<head>
<body>
<h1>HELLO WORLD</h1>
</body>
</html>"></iframe>