Alexander Khramov
Alexander Khramov

Reputation: 59

CSS and deferred JS execution order

For example, I have a webpage with such structure in <head>, which is pretty common i guess:

<link rel="stylesheet" href="styles.css"> // here goes big css bundle
<script defer src="main.js"></script> // relatively small javascript bundle with defer attribute

And there is <span id="element"></span> on the page.

CSS bundle contains #element { display: none; }.
JS bundle contains (using jquery here):

$(document).ready(() => {
  console.log($('#element').css('display'));
});

The result will be different from time to time. Sometimes JS executes earlier than CSS and the result is 'inline', sometimes JS executes later and the result is 'none' as I want it to be.

I want my JS bundle to be non-blocking so I use deffered attribute. I am not able to simply put my JS bundle in the end of a page because I use turbolinks and it doesn't allow me to do it.

window:load is not a best option too, because it will be fired when not only css but all resources will be downloaded including images.

So I want JS to be not-blocking and be executed after CSS to get consistent and predictable results. Maybe there is something I can do?

Upvotes: 2

Views: 291

Answers (2)

Alexander Khramov
Alexander Khramov

Reputation: 59

I found another solution. You can just add a script without src attribute to the <head> with some code, an empty comment for example: <script>//</script>
And that's it. Now all the scripts, even deferred, will wait for styles to apply.
I'm not sure how it works, but I think deferred scripts are queued after a script without src which by standard must wait for css to apply.

Upvotes: 1

Andrew Myers
Andrew Myers

Reputation: 2786

One option is to add a load event handler for the link element which will then insert the script into the head. Since the script is dynamically added, it will automatically be async. Therefore, it would be non-blocking and executed after the CSS is loaded.

<link id="stylesheet" rel="stylesheet" href="styles.css">

<script>
  var link = document.getElementById('stylesheet');

  link.addEventListener('load', function () {
    var script = document.createElement('script');
    script.src = 'main.js';
    document.head.appendChild(script);
  });
</script>

However, this could cause you a problem. If the stylesheet is cached, then it might not emit a load event (because it's already loaded). For cases like that, you could try checking for link.sheet.cssRules.

Load events on <link> elements seem to historically be a troublesome issue, so I don't know how well this will work.

Here is a CodePen demonstrating the JS loading with a check for link.sheet.cssRules. It currently works for me in Chrome, FireFox, and Edge.

Upvotes: 1

Related Questions