Learnings from researching fault tolerent CSS delivery

Orde Saunders' avatarUpdated: Published: by Orde Saunders

Whilst researching fault tolerant CSS delivery I went down a number of blind alleys and, whilst they didn't end up as part of the final post, I'm sharing some of them here out of interest.

Caveat lector: Note that I haven't investigated these as thoroughly as I normally would before I post here.

Adding a stylesheet by injecting <link rel="stylesheet"> before the DOMContentLoaded event will block DOMContentLoaded until the stylesheet request completes. This is a problem if your JavaScript is waiting for DOMContentLoaded before it executes: you don't have all the styles and you don't have your JavaScript running.

Injecting a <link>using the DOMContentLoaded event is too late, there's a noticeable flash of partially styled content. It will also delay the page load event.

Injecting a <link> element does not seem to completely block script execution if done after DOMContentLoaded but I've not in any way systematically tested this - it's just something I think I noticed whilst trying to sort out other problems.

<script href="…" async> and DOMContentLoaded

If you set the async flag on an external script then there's a real probability* that the script will execute after the DOMContentLoaded event has fired. If you are binding your JavaScript initialisation code to this event then it will never run. You'll need to check the state of the DOM first:

if (document.readyState !== 'loading') {
  // DOMContentLoaded has already fired.
  init();
} else {
  document.addEventListener('DOMContentLoaded', init);
}

* I.e. It's going to happen. Maybe not every time, but once is enough in this case.

Detaching from events

If you add a time out to try and detach your code from running as part of an event then I found I needed to set the time out to at least 100ms. (I say "at least" but 100ms worked in Firefox and Chrome and I didn't systematically test it until I found the lowest possible value.)

// This will run as part of the event and delay it's completion.
document.addEventListener('DOMContentLoaded', function() {
  setTimeout(function () {
    // Code that takes a long time to complete.
  }, 1);
});

// This will run after the event has completed.
document.addEventListener('DOMContentLoaded', function() {
  setTimeout(function () {
    // Code that takes a long time to complete.
  }, 100);
});