Support async loading stylesheets with low-priority

tl;dr: We should offer a simple solution for async loading stylesheets at a low-priority. Something in the vein of <link async> or <link importance="low">.

The Filament Group recently posted a pattern for loading CSS asynchronously, leveraging a workaround setting a link's media attribute to print:

<link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'">

Although link[rel=preload] exists for achieving a similar pattern (toggle rel once loaded vs media), it fetches files early at a higher perceived priority. This can have the downside of de-prioritizing other critical requests.

An alternative to the print pattern above would be standardizing on a way to async load stylesheets with a low-priority. This could be achieved in a few ways:

  • Explore <link rel="stylesheet" href="/path/to/my.css" async>
  • Modify the behavior of Priority Hints such that <link rel="stylesheet" href="/path/to/my.css" importance="low"> both signaled low-network priority and that the link should be non-render blocking

Starting this discussion to explore whether this is overall problem is a pattern we would like to better solve in the web platform and if so, what the shape of a solution should be.

2 Likes

Curious if <link rel="stylesheet" href="/path/to/my.css" defer> is meaningful as well, in case the order of imports can changes which is overwriting which rulesets / styles.

I like the idea of native async

Would prefer an obvious signal e.g. async attribute as JS has over importance=low signalling non-blocking behaviour behaviour

We definitely want defer semantics here, regardless of the markup used. Current JS async semantics are… not good. They are racy by definition. We shouldn’t add that kind of raciness to CSS, unless people explicitly want it (and then they can use preload to recreate it :D).

4 Likes

Yes, async interrupts the parser, while defer waits for the page to finish parsing. External style sheets that are non-critical should probably not interrupt parsing.

Yes, async interrupts the parser

That’s a Chrome implementation detail, sounds like. In Gecko it doesn’t do that; it will run if/when the parser is interrupted, but will not trigger an interrupt itself.

1 Like

Is Firefox’s behavior in accordance with the spec? The spec text suggests that an async script always interrupts the parser.

For classic scripts, if the async attribute is present, then the classic script will be fetched in parallel to parsing and evaluated as soon as it is available (potentially before parsing completes).

https://html.spec.whatwg.org/#attr-script-async

How can you tell when the script is “available” except for “when it gets run”? In particular, in Firefox it’s not “available” until a task runs on the main thread notifying it that it’s available, and those tasks don’t interrupt the parser.

That’s as far as observable behavior goes. In spec terms, “available” is not really defined; it’s up to the browser to decide when the script is “available”.

1 Like

If we go for defer semantics (I guess we could have both), would it be in the same queue as <script defer>? That would be nice…

<link defer rel="stylesheet" href="style1.css">
<script defer src="script1.js"></script>
<script src="script2.js"></script>

This means the execution of script1.js would be blocked by the fetch & calculation of style1.css. Whereas script2.js would not be blocked by style1.css.

1 Like

I don’t want to disrupt this discussion, but FYI there’s the “A standard method for loading style sheets asynchronously” post too.