Combat CSS blockage with this one weird little trick

CSS is the worst. Not only it blocks rendering, but it may slow down your JavaScript execution, depending on what’s around it. Let’s see an example. Baseline: before Here we have a more or less ordinary web page’s <head>: some initial inline JavaScript, followed by a couple of external styles and scripts and then an […]


This content originally appeared on Web Performance Calendar and was authored by Stoyan Stefanov


CSS is the worst. Not only it blocks rendering, but it may slow down your JavaScript execution, depending on what’s around it. Let’s see an example.

Baseline: before

Here we have a more or less ordinary web page’s <head>: some initial inline JavaScript, followed by a couple of external styles and scripts and then an inline script in the body.

<head>
  <script>
  const log = {};
  </script>
  <title>Baseline: before</title>
  
  <link rel="stylesheet" href="css1.css.php" type="text/css" />
  <script src="js.js" async></script>
  <link rel="stylesheet" href="css2.css.php" type="text/css"/>
  <script src="js2.js" async></script>
  
</head>
<body>
  <script>
    log['inline BODY script'] = +new Date - start;
  </script>
  <!-- -->
</body>

To spot any issues, the first external CSS files has been delayed by 5 seconds and the second by 10. Because… network hiccups happen, it’s not a perfect world out there.

And what do we have as a result (the numbers are millisecons since an initial timestamp in the HEAD):
table of timings example 1

You see that the inline head script and the external JavaScript execute as expected – as soon as possible. On the other hand, DOMContentLoaded and onload are blocked by the slowest CSS (about 10 seconds), which is unfortunate but it’s the nature of the beast. CSS does block rendering/DOMContentLoaded.

But why is the inline <body> script also taking 10+ seconds to run? It’s literally in there in the page. And the external JS is async on top of everything. Well, there’s a justification for this: the browser has no idea what this inline script does. Maybe it’s asking for styling or layout info, e.g. innerHeight of some element. To provide this the browser needs to apply all styles and do the layout. And so, looks like…

A slow CSS prevents JavaScript from executing.

Could this be true? Let’s investigate some more.

(You can view the full source and play with the example here.)

Baseline: after

In this example, let’s have an inline script after the first pair of external JS and CSS:

<head>
  <script>
  const log = {};
  </script>
  <title>Baseline: before</title>
  
  <link rel="stylesheet" href="css1.css.php" type="text/css" />
  <script src="js.js" async></script>
  
  <script>
    log['inline HEAD script'] = +new Date - start;
  </script> 
  
  <link rel="stylesheet" href="css2.css.php" type="text/css"/>
  <script src="js2.js" async></script>
  
</head>
<body>
  <script>
    log['inline BODY script'] = +new Date - start;
  </script>
  <!-- -->
</body>

And what happens now with the few metrics we’re tracking:

table of timings example 2

As you can see, now the inline <head> script execution was delayed. As much as the first CSS took to load.

Also the execution of the first external JS was delayed. By the same 5 seconds. Note: the download was not blocked, the download was fine (whew!) but the execution was delayed.

Both delays were obviously caused by the slow CSS, because the delays were 5 seconds – as much as it took the external CSS to load. So looks like this holds again:

A slow CSS prevents the JavaScript following it from executing.

Blocking JS execution is bad. In today’s JS-rich apps a delay like this is a life/death situation. If it was a “progressive enhancement” JS that makes a readable article even more enjoyable, fine. But an app not showing your email message you’ve beeen eagerly awaiting just because of a network packet hiccup in CSS… that sucks.

(You can view the full source and play with the example #2 here.)

Sync vs async JS

How come none the external JS files wasn’t blocked in the first example? Because they are marked async and therefore obviously don’t care about getting styling info on time. But the inline JS is treated as synchronous and therefore blocked by a slow CSS.

Additionally once the inline (sync) JS waits for the slow CSS, the async second external JS is blocked too. Bummer.

Seems like encountering a sync JS causes everything else to wait for the slow CSS.

How can we fix this? Maybe… if we can make the inline JS async too. It’s sync by convention (see above), but can we make it async? Worth trying, for sure.

Just make it async already

Simple, right? Just add an async attribute to the inline <script>.

<head>
  <script>
  const log = {};
  </script>
  <title>Baseline: before</title>
  
  <link rel="stylesheet" href="css1.css.php" type="text/css" />
  <script src="js.js" async></script>
  
  <script async>
    log['inline HEAD script'] = +new Date - start;
  </script>  
  
  <link rel="stylesheet" href="css2.css.php" type="text/css"/>
  <script src="js2.js" async></script>
  
</head>
<body>
  <script async>
    log['inline BODY script'] = +new Date - start;
  </script>
  <!-- -->
</body>

Sorry to say that accomplishes nothing. Try it yourself here.

How about a defer?

That defer attribute should count for something, right?

<head>
  <script>
  const log = {};
  </script>
  <title>Baseline: before</title>
  
  <link rel="stylesheet" href="css1.css.php" type="text/css" />
  <script src="js.js" async></script>
  
  <script defer>
    log['inline HEAD script'] = +new Date - start;
  </script>  
  
  <link rel="stylesheet" href="css2.css.php" type="text/css"/>
  <script src="js2.js" async></script>
  
</head>
<body>
  <script defer>
    log['inline BODY script'] = +new Date - start;
  </script>
  <!-- -->
</body>

Sorry, nope.

Did someone say… ay, ay, ay, data URI?

Here’s the “weird little trick” (sorry for the obnoxious expression from yesteryear’s clickbait buzz-style headlines). The idea is to kinda “externalize” the inline script and as a side effect, benefit from the async attribute that external scripts have going for them.

Implementation is pretty simple, URL-encode the JavaScript code and use it as src prefixed by data:text/javascript,. It’s not even base64-encoded or anything.

<head>
  <script>
  const log = {};
  </script>
  <title>Baseline: before</title>
  
  <link rel="stylesheet" href="css1.css.php" type="text/css" />
  <script src="js.js" async></script>
  
  <script 
    async 
    src="data:text/javascript,log%5B%27inline%20HEAD%20script%27%5D%20%3D%20%2Bnew%20Date%20-%20start%3B">
  </script>   
  
  <link rel="stylesheet" href="css2.css.php" type="text/css"/>
  <script src="js2.js" async></script>
  
</head>
<body>
  <script 
    async 
    src="data:text/javascript,log%5B%27inline%20BODY%20script%27%5D%20%3D%20%2Bnew%20Date%20-%20start%3B">
  </script>  
  
  <!-- -->
</body>

Here is example #3 in action.

And the results? The speak for themselves:

table of timings example 3

Suddenly all JS executes with no delay and even DOMContentLoaded is unblocked. The onload is no worse then before while everything else is better.

Caveat: in Firefox you get flush of partially-styled content before the first CSS arrives and another one after the first CSS. So three layouts in total, one for every time new styling info arrives. Is this important? You be the judge. Do you want to split all CSS into critical and stuff that can wait? Up to you.

Conclusion

So here is it – a little trick that can prevent CSS from blocking by simply dataURI-fying your inline JavaScript. Weird, isn’t it? Thoughts?

Remember: in addition to the data URI, do not forget async attribute.


This content originally appeared on Web Performance Calendar and was authored by Stoyan Stefanov


Print Share Comment Cite Upload Translate Updates
APA

Stoyan Stefanov | Sciencx (2021-12-13T11:02:00+00:00) Combat CSS blockage with this one weird little trick. Retrieved from https://www.scien.cx/2021/12/13/combat-css-blockage-with-this-one-weird-little-trick/

MLA
" » Combat CSS blockage with this one weird little trick." Stoyan Stefanov | Sciencx - Monday December 13, 2021, https://www.scien.cx/2021/12/13/combat-css-blockage-with-this-one-weird-little-trick/
HARVARD
Stoyan Stefanov | Sciencx Monday December 13, 2021 » Combat CSS blockage with this one weird little trick., viewed ,<https://www.scien.cx/2021/12/13/combat-css-blockage-with-this-one-weird-little-trick/>
VANCOUVER
Stoyan Stefanov | Sciencx - » Combat CSS blockage with this one weird little trick. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/12/13/combat-css-blockage-with-this-one-weird-little-trick/
CHICAGO
" » Combat CSS blockage with this one weird little trick." Stoyan Stefanov | Sciencx - Accessed . https://www.scien.cx/2021/12/13/combat-css-blockage-with-this-one-weird-little-trick/
IEEE
" » Combat CSS blockage with this one weird little trick." Stoyan Stefanov | Sciencx [Online]. Available: https://www.scien.cx/2021/12/13/combat-css-blockage-with-this-one-weird-little-trick/. [Accessed: ]
rf:citation
» Combat CSS blockage with this one weird little trick | Stoyan Stefanov | Sciencx | https://www.scien.cx/2021/12/13/combat-css-blockage-with-this-one-weird-little-trick/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.