Keith Clark posted recently about loading CSS as early as possible, without the browser refusing to render anything until it downloaded it. Like <script async>, but for stylesheets. Maximum speed.

His solution sadly didn't withstand the first salvo of browser testing, which The Filament Group discovered some time ago. Hence, their LoadCSS library, which is the littlest style loader they could make that doesn't sacrifice robustness.

But it relies on JS. That gets my hackles up, largely because I am bad at JavaScript, but there are those "separation of concerns" and "don't introduce layer dependencies" thingos. And the more we cooperate with the lookahead pre-parser/preload scanner/byte burglar, the better. With JS loaders, the preloader can fill all available connections with images and such, leaving your CSS stuck behind them.

First whack: Async CSS with media="bogus" and a <link> at the foot

Say we've got our HTML structured like this:

    <!-- unimportant nonsense -->
    <link rel="stylesheet" href="style.css" media="bogus">
    <!-- other unimportant nonsense, such as content -->
    <link rel="stylesheet" href="style.css">

Just like putting <script> at the bottom, the <link> before the closing </body> should only start blocking when there's nothing left to block. Do whatever, browser, you can't ruin anything.

Meanwhile, way up in the <head>, browsers insist on downloading stylesheets with media attributes they could never fulfill, like media="print" when there's no printer connected, media="(min-width:500px)" on a smartphone, ormedia="do-not-download-this-means-you". This is because one could connect a printer, or rotate their phone, and all of a sudden media does apply. Dunno about that last one.

(Maybe the @import statement with media queries can work here? That shouldn't trigger the preloader, and thus allow the browser to make intelligent fetching decisions, but if it were that simple, surely we'd be using it.)

However, modern browsers don't block on these unmatching stylesheets. So if we prime the cache with the early, inapplicable-media download, any browser with half-decent networking code should just reuse the file it's already downloading/cached when it encounters the second <link>.

I whipped up a couple of test pages (I would have used CodePen, but I needed a clean waterfall chart. Sorry guys!) and ran them on WebPageTest:

The async CSS takes only 0.4 seconds, the regular takes a full second

These results are awesome: sliced the time right in half! But The Filament Group strikes again; I should have known they'd been there, done that.

When I said "modern browsers" I was telling a lie. Scott Jehl found out Firefox and IE still totally block. Wimp womp. If you want Firefox to fix this, please do vote on its Bugzilla here, but IE considers it a wontfix. (I did my own testing and the async version doesn't seem to make any difference in IE, so at least we're not making things worse? More testing needed.)

At this point, we might as well just use the Chromium-only <link rel="subresource"> instead since the browsers it works in are about the same (well, sans Safari) and is much less hacky. Which is a nice boost for Chromium folks, and the technique still works in other browsers, if not optimally. But is there another way?

Abusing the browser cache

What if we used something else to prime the cache and start downloading the CSS as fast as possible? Something like:

  • <script src="stylesheet.css" async defer></script>
  • <object data="stylesheet.css" type="text/css"></object>
  • <img src="stylesheet.css">

The first attempt with <script> is the only other resource browsers will fetch in the <head> quickly. But this is dangerous. Even if the JS parser didn't find anything to execute in there, it would still waste resources trying to. And it's only slightly more robust than using a JS loader anyway, since both fail in largely the same situations.

Using <object> in the <head> is an ancient preloading technique, since HTML 4.01 allowed it for arcane purposes. But that's disallowed and broken with the new HTML5 parsers.

Using <img> is pretty hacky, and according to this 2010 phpied blog post, Firefox uses a separate cache for images, so there's another no-go. And of course, we can't put an <img> in the <head>.

The Link header

This is just an HTTP header with identical functionality to the <link> element. Like this:

  Link: <style.css>; rel="stylesheet"

It doesn't get much faster than putting the style before the file. If we could prime caches with this, that would be perfect. I'll need to ask around/test for browser support (I know Firefox and old Opera/Presto do it), and if it blocks.

Just shove it in the <body>

While I was unknowingly retracing The Filament Group's steps on async CSS, I happened upon Yoav Weiss's blog post:

It seems this trick causes Chrome & Firefox to start the body earlier, and they simply don't block for body stylesheets.

Could it be as simple as that?

    <link rel="stylesheet" href="style.css">
  <!-- more such nonsense which is unimportant -->

We'll need to test it, of course. I'll fill in this table as I work my way through.

Browser Engine Async in body?
Blink Yes!
Gecko Yes!
WebKit Yes!
iOS WebKit
Presto Who can tell?

Do we have a standard for this?

As far as "please just let me mark a stylesheet as nonblocking," the W3C had the Resource Priorities standard. With it canceled, it's no good to us until its successor Resource Hints lands. Or is it!?

Internet Explorer 11 implemented it. And with conditional comments we can support IE 9 and lower:

  <!--[if IE]>
    <link rel="stylesheet" href="style.css"> <!-- blocking, but what else can ya do? -->
    <!--[if !IE]> -->
        <link rel="stylesheet" href="style.css" lazyload>
  <!-- <![endif]-->

But what about IE 10? It's got a full 1.29% usage share according to Caniuse, and it stopped supporting conditional comments. EHHHHHHHH. So close. For it and the long tail of niche/older browsers that never quite die off, this might be an acceptable loss to you. After all, it doesn't break, it just doesn't work as fast as we would like.

The future

When HTTP/2 hits, we can Server-Push to start loading CSS with the HTML simultaneously, and then we can just shove the <link> wherever the hell we feel like it. You could try messing around with that part today, if you like. The other good part is a new request in HTTP/2 is peanuts. Of course, we'll still have to support all the older HTTP 1.X clients...

For other upcoming tech, we have that Resource Hints spec, and those shiny new HTML Imports can import styles, and they don't block! Too bad Mozilla isn't big on them.

In conclusion

I need a drink.

66,031 11 54