Keith Clark posted recently about loading CSS as early as possible, without the browser refusing to render until it finished. 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.
First whack: Async CSS with
media="bogus" and a
<link> at the foot
Say we have HTML structured like this:
<head> <!-- unimportant nonsense --> <link rel="stylesheet" href="style.css" media="bogus"/> </head> <body> <!-- other unimportant nonsense, such as content --> <link rel="stylesheet" href="style.css"/> </body>
<script> at the bottom,
<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" without a printer connected,
media="(min-width:500px)" on a smartphone, or
media="do-not-download-this-means-you". This is because one could connect a printer, or rotate their phone, and suddenly
media does apply. (Dunno about that last one.)
@import statement with media queries can work here? That shouldn’t trigger the preloader, thus allowing 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
I whipped up a couple of test pages and ran them on WebPageTest:
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 vote on its Bugzilla here, but IE says wontfix. (I did my own testing and the async version doesn’t seem to make a difference in IE, so at least it doesn’t worsen things? More testing needed.)
At this point, might as well use the Chromium-only
<link rel="subresource"> instead, since it’s 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 high in the
<head>? Something like:
<script src="stylesheet.css" async defer></script>
<object data="stylesheet.css" type="text/css"></object>
Scripts are the only other resource browsers will preload in the
<head>. But that’s dangerous. Even if the JS parser didn’t find anything to execute, it still wastes resources trying to. And it’s only slightly more robust than a JS loader anyway, since both fail in largely the same situations.
<img> is pretty hacky, and according to this 2010 phpied blog post, browsers can use separate caches for images, so that’s another no-go. And we can’t put
<img> in the
This is an HTTP header identical to the
<link> element. Like this:
Link: <style.css>; rel=stylesheet
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 test for browser support (I know Firefox and old Opera/Presto do it), and if it blocks.
Shove it in the
While I was unknowingly retracing The Filament Group’s steps on async CSS, I happened upon Yoav Weiss’s 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 easy as that?
<body> <link rel="stylesheet" href="style.css"/> <!-- more such nonsense which is unimportant --> </body>
Need to test it, of course. I'll fill in this table as I work my way through.
|Browser Engine||Async in body?|
|Presto||Who can tell?|
Do we have a standard for this?
Internet Explorer 11 implemented it. And with conditional comments we can support IE 9 and lower:
<head> <!-- blocking, but what else can ya do? --> <!--[if IE]> <link rel="stylesheet" href="style.css"/> <![endif]--> </head> <body> <!--[if !IE]> --> <link rel="stylesheet" href="style.css" lazyload="1"/> <!-- <![endif]--> </body>
But what about IE 10? It stopped supporting conditional comments. EHHHHHHHH. So close. For it and the long tail of niche/older browsers that never die off, this might be an acceptable loss. After all, it doesn’t break, only doesn’t work as fast as we would like.
With HTTP/2, we can Server-Push to load CSS with the HTML simultaneously, and shove the
<link> wherever we feel like. The other good part is a new request in HTTP/2 is peanuts. Though, we’ll still have to support all the older HTTP 1.X clients…
<link rel="preload" as="stylesheet"> is the new hotness, but it’s still being ironed out.
I need a drink.