-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Async css #3983
Comments
While the JS-based packaging does not have an issue with loading CSS on demand of JS module( lazy or in run time), IMO having coherent phased load behavior over all resources including script, images, fonts, css, etc has sense to be unified and be a part of HTML standard. So I would extend this feature request to "unify the load behavior for web page resources" instead of just CSS lazy load. Also current separation of synchronous/async/delayed behavior does not reflect the need for modern web app. In complex apps the order of load is phased and each phase accounts dependency graph. Extending loading parameters with "depend on" and "load order" is a good addition to proposal. All seems to be easy polyfilled for backward compatibility. |
There are two related but orthogonal issues here:
As an example of why they are orthogonal, consider that it currently seems, that marking a script "async" will give it the lowest priority. But even though a script might not be needed for the first render, it might be desirable to have it load before, say, large decorative images on the page. This issue is about (1) for the stylesheet resource type. |
Perhaps we can add a semantic that |
Maybe we can revive this issue? Async CSS is pretty essential. It's the reason libraries like Font Awesome have to resort to <link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'"> |
I still frequently find reasons for loading CSS asynchronously in my work. As @LeaVerou mentioned, the "print media hack" is commonly referenced as a workaround, but its reliance on JavaScript to apply CSS is a major downside. The addition of an I'd love to volunteer my time to consult on specification or implementation work if that's helpful in any way. Thank you! |
CSP settings avoiding inline JS also complicate this common workaround. Another win for a native implementation. |
I'm happy to submit a proposal for Why has putting those |
@noamr I like how that sounds but I think As for the question of in-body links, my understanding is they are sync and block rendering for HTML that follows them. Useful for different contexts but not the same. [update: browser behavior varies a lot here!] |
That's incorrect AFAICT. We should be clear here with the use of the word
So the following should just work: <head>
<title>...</title>
<link rel=stylesheet href="blocking.css">
<!-- stuff -->
</head>
<body>
<link rel=stylesheet href="non-blocking.css">
<!-- stuff -->
</body> |
These too are blocking. See https://codepen.io/bramus/pen/NPKYWrX/23a03a80e29b92fa80a41701959d1dba for a demo. UPDATE: In Chrome and Safari. Not in Firefox. |
Yea I believe this is not per spec. It's a browser heuristic, not sure I like it :) |
Yeah, browsers all behave differently. 8 years ago, Jake wrote about it. |
Anyway, putting the I'm afraid that |
It was also covered by @csswizardry in https://csswizardry.com/2018/11/css-and-network-performance/#place-link-relstylesheet--in-body |
Perhaps we should have official chrome/MDN documentation about this.
I still don't get the use case of the "preload it and apply when it's downloaded" a lot of people use, or for Scripts and styles were not born equal, we should be careful with trying to apply the exact same semantics on them without weighing the consequences. |
No, it is not: Safari and Firefox do not behave reliably and will block rendering of content before the Even if all browsers behaved accordingly, it would still be great to have a spec and tests ensuring they keep behaving that way.
The technique is used to avoid spending time rendering hidden content (whether it's "below the fold" or content that will only be displayed after some interaction) for the initial page load. |
Thanks for the context, I'd like to hear thoughts from @smaug---- / @zcorpan about this; Also from someone from Apple (@annevk?) Also I see why this is more about being |
Setting aside the notion that the current behavior should be spec'ed and interoperable, I'm still struggling with The technique may make this FoUC not likely but still possible, which makes it racy/brittle. Where Am I getting anything wrong here? |
Note that a parser-inserted If it's desired to change that in addition to whether it blocks rendering, using It's already possible to opt in to FOUC-style loading with some JS. If it's a common thing developers want (and they can somehow manage to avoid exposing users to FOUC), it seems nice to provide a declarative way to do so. However I am also a bit concerned about cargo-culting |
Thanks for chiming in. My mistake on the Broadly, I don't think I disagree with many of your preferences about the use cases you mention, but I typically see this tool used to address different aims. My experience is that async CSS is typically used to mitigate/improve rendering performance in existing sites rather than as a building block for greenfield UI pattern development. As an aside, many CMSs in particular offer very little control over asset loading in the body as opposed to the head, so dev teams don't always have the option of that sort of composition in the body or the foot of the page even if it made sense for their needs. In performance audits of large CMS driven sites, I find it's common to encounter render-blocking (and often third-party) links to CSS that isn't applicable to HTML in the initial page rendering. A popular way to get these out of the critical path and improve rendering time (and reduce SPOFs) is the print-media workaround, which relies on scripting and isn't available to many sites with CSP prohibiting That's just one example. Another is when teams adopt a "critical" css loading strategy to move CSS loading into a tiered-priority groups: styles that are found to be applicable to the initially delivered HTML are deemed critical and loaded either inline or synchronously, while the rest is layered in async. Poor implementations of this approach certainly exist, but I've seen plenty of teams use it to great success. Overall, it's a long-lived tool in performance teams' belts that should be available without JS hackery. |
I think fonts with And with Google Fonts being used on 60% of the web, there is potentially room for significant performance improvements by allow them to specify it as async by default. Plus as per @LeaVerou 's comment in #3983 (comment), it's similar for other font providers. Of course you could just move the fonts CSS link to just before the P.S. We can talk about whether |
Overall this looks like a benefit to be supported natively I guess we don't want to turn this thread into bike shedding, but if an attribute is to be added I was curious on use cases, but it does seem like fonts are the biggest one. For main-body CSS most sites don't split CSS into parts of the page which are viewed above or below the fold. There's some use case too for lazy loaded content, but I wonder if some of that overlaps with CSS Module Scripts which could dynamically load extra CSS along with additional content (although I guess this is specific to Shadow DOM stuff right now).
I think this is a genuine concern, from the brief search I've done for usage of the the "print media" trick, it does look like some sites are doing it on their main CSS. I don't know if there's a genuine reason for this, or they're copy-pasting the idea from somewhere else without fully understanding the impact. |
Both the fonts and the non-critical-css use cases are:
I think this tradeoff makes both of these use cases rather advanced, requiring care and expertise. When using I would therefore hesitate before exposing this behavior declaratively in the web platform, especially behind a keyword that seems positive, easy to use and harmless like Perhaps there are other use cases that are not in the category of "I know what I'm doing and I knowingly take the risk of FoUC"? |
The preload-to-stylesheet toggle you mention is another of many ways to produce a similar effect, but preload incurs a higher priority fetch that typically doesn't pair with the goals of async css. There are other alternatives to media toggling that offer the low priority fetch fwiw: for example, The problem with all of these toggle hacks is that the mechanics of starting with an irrelevant setting and changing it back are not at all intuitive to anyone looking for a way to fetch a stylesheet without blocking the critical rendering path. Plus, they make CSS application reliant on scripting, and CSPs often block authors from using inline scripting for the onload handler anyway. |
Understood, perhaps there can introduce something subtle where I think that this requires a tradeoff where things are perhaps not JS-bound but also not too enticing for non-experts, to avoid abuse resulting in increased FoUC. |
@chrishtr Barry said as much already, but using the end of the page like that introduces the potential for a very late fetch, and more generally requires us to (ab)use source order to configure loading behavior, which isn't something we need to do for other asset types anymore. For example, we used to need to put scripts at the end of the page but |
It won't be too late due to the preload scanner, and if the developer wants it to be even sooner they can add a preload to the head. Right?
Style sheets are special though, because they directly affect rendering of the content. (Scripts, on the other hand, often have nothing to do directly with rendering.) Therefore style sheets have a direct, intimate connection to the HTML. This is why I think developers just need to know that "style sheets load before anything after it in the HTML" (or "style sheets always apply to content after them in the HTML [but not always to content before, during loading]") and then act accordingly. I hear you that centralizing things can be good for ergonomics for developers who understand what is going on, but I agree with @noamr that there is a corresponding risk for developers who are less versed in this; putting the sheet at the "end of the body" (a) already works (in Chromium and mostly Gecko so far) and (b) hopefully makes them think more about the implications in a more intuitive way. |
You are probably closer to the particulars on that part than me, but my understanding is you'd be relying on the amount of HTML that has streamed, so fetch timing by source-order location would correlate to the size of the HTML document and vary by network connectivity. To be fair, the fetch timing drawback of this approach is one of the lesser reasons that it feels like an improper fit. It's more that it's an unintuitive pattern to need to employ for loading behavior, and doesn't fit with how teams typically configure their assets. The expected convention for many other resource types now is to be able to declaratively control your resources' fetch behavior via attributes, independent of their location in source order. As some of the use cases above described, a stylesheet fit for async loading typically doesn't contain styles that need to block rendering, so it's not a matter of finding the right dependent element to precede with a link. It's more literally like an asynchronous script use case: the stylesheet isn't critical for initial rendering and is expected to load in parallel and apply whenever it lands (as the print-media workaround has been popularly used to do for quite some time). |
Yes, the size of the HTML document will delay the load somewhat. But since the preload scanner is a separate parser that is optimized to find just resource links, the delay should be short. And for sites which don't want even that delay, they can add a preload in the head. |
Realistically, if the size of the HTML is enough to be measurable for the induced delay, there are going to be a LOT of resources in it and the discovery time is going to be a rounding error compared to basic ordering and prioritization. |
As I said above, the loading delay can be worked around (with a Being able to load AND async execute in the And the only way to work around that is with the FYI, I ran an HTTP Archive query and 17,534 sites (out of 16,257,087) use this hack: To be honest that's less than I thought would be using it! |
@chrishtr the important part is not if it's fetched early but when it is applied, which depends on the length of the HTML for the "stylesheet at the end of body" solution. It would be OK only for short documents, but not all documents are short. @tunetheweb 0.1% of all pages in httparchive is a lot for a hack. I expect more pages would use |
@zcorpan Thanks. I agree that's a lot of usage. I can appreciate the concern for potential misuse of any feature. I do think there's precedent for opt-in standard features that can cause undesirable performance when used for common/default use cases (which is why it's great they're opt-in). For example, |
I'd prefer to remain consistent with scripts and have There might be some help that tooling can provide. Whilst I don't have a static solution to deter mis-use, I do think the UA can track if the viewport was styled (on startup, before arbitary time) by an async link, then warn the user of bad practice in the console. I think you could also lint against "main.css" having an async attribute, something I've seen when searching for the print-styles hack. |
I wonder if instead of enabling this we could seek alternative solutions to the issue of loading stylesheets for non-critical/below-the-fold UI that don't cause this UX degradation and somehow mitigate the (ergonomic?) shortcomings of the load-early/apply-on-time technique? @jasonwilliams I think trying to treat styles & scripts in the same way is not the right way to go about this. Those beasts are different. See this comment. |
There are concerns that people might use this feature incorrectly. To assess whether that concern is great enough to make this feature less intuitive by design, maybe it'd help to see examples for other opt-in features that were popularly mistaken for a default in this way. My inclination is to expect that authors will be as capable of understanding this opt-in attribute as they already are of many other non-default attributes (many of which come with behaviors that may or may not fit a use case). Maybe there's precedent I'm not aware of, and it'd help here. Throughout the thread, folks seem to agree on the utility of this feature. There's clearly a great deal of usage for the workaround on the web, and the number of +1s here show large support for the proposal. |
I wonder if we could do something like the following: <link rel=stylesheet href="widget.css" for="widget" > Where this would load the style, but would only apply it when an element with an ID of |
It would work for all the other use-cases (below-the-fold, non-critical etc). I think that for |
Oops, I edited your comment instead of replying by mistake :) |
@tunetheweb I think we could have two separate solutions here. <!-- This would load (like preload), but would only apply & block parsing when an element with ID="some-id" is seen -->
<!-- If the target element is something like an async script, we can keep parsing but block the script execution on the stylesheet -->
<link rel=stylesheet href="widget.css" for="some-id">
<!-- This would load the stylesheet asynchronously, but would only apply font descriptors and whatever they need -->
<link rel="font stylesheet" href="https://fonts.example.com/api?my-font"> We could then update the Google Fonts recommended I wonder if there are more use cases.
|
Sure thing, though I hope that introducing more use cases here won't cause a distraction - we already have a couple of common cases that we've all agreed are valid enough. Other use cases would include patterns where sections of HTML are included somewhere in the page and not used in the initial page render, but may be visible at a later time. I'm riffing here, but I could imagine a rather complex hidden-by-default dialog/popover/navigation section of a document that may be paired with its own stylesheet (a common situation for UI components). You may not want that stylesheet to block rendering of the "dialog" HTML nor any other HTML that follows it because it's meant to be hidden until it's revealed sometime long after page load. That's a nice case for an async stylesheet that doesn't disrupt the rest of the page, and serves to free up the critical path. There are surely many parallels with embed snippets (3rd party or not) that bring their own stylesheet: a help-chat widget that can layer in and appear whenever it happens to load, for example. Along those lines, Google fonts is an example of an embed code that causes undesirable blocking behavior that can be mitigated by going async. It's a popular example of a fairly common problem with embeds. My experience is that asynchronous use cases happen often in complex UIs and even the two main use cases cited throughout the thread show that this pattern is frequently not tied to any particular element's rendering or even existence at load time. We mentioned why the font loading example isn't tied to the rendering of one particular element, but the critical CSS patterns we've cited aren't either. Nowadays, these patterns have less to do with older, finicky "above the fold" style isolation, and instead are about a simpler distinction of synchronously loading CSS that is necessary for rendering HTML that's going to be visible in the initial page load, and asynchronously loading the rest of the CSS (which might well be the CSS for the entire website, which if it's a SPA for example is quite elegant.). In all cases, this pattern is used in the aim of a faster initial page render, and is not used at the cost of any layout shifts of FOUC.
The To back up and refocus...The And on that particular topic, it's worth noting that Developers understand the difference between default, defer, and async and it's great we have options. Sometimes, with scripts and indeed CSS, async is just what you need. |
Thanks for the details! If the dialog is opened before the CSS is loaded, that would cause an FoUC. As a user I encounter many websites with this pattern unfortunately... You click a dialog, and it's unstyled for a sec before its CSS loaded. |
Those are the main use cases I have run into, yes. There were a few examples mentioned in there - if the dialog one isn't as compelling, the "help-chat" widget (substitute any non-essential overlay UI here) example seems to me common and reasonable. |
Having spoken with @scottjehl offline, I recognize that a valid use case for this is when the elements being styled asynchronously have some kind of default CSS that makes them hidden until the CSS is loaded, which would make them "styled into being visible". This is a legitimate use case because the resulting UX is not a FoUC. I wonder if the web platform should enable this use case directly, or if that's enough to justify the simpler-but-footgunny "async" attribute. |
How do you intend this to work when the For markup coming from the network, it does not seems as problematic to allow arbitrary elements to block the parser, though it doesn't seem particularly great for performance to have to perform a "does the parser need to be blocked on ID match" on every element", but perhaps it can be optimized well enough by making documents carry a flag indicating whether this feature is in use at all, which would provide for a quick check for all the pages that don't use the proposed feature. |
Lazy loading themes would be useful. |
Sure! How would you envision this working when the user changes theme before the theme's css is loaded? |
As per the WHATNOT discussion, created a WICG proposal: WICG/proposals#195 |
A comment that expands on many of the use cases in this issue, as well as why the existing "print media" workaround already does exactly what we need, and only has a few drawbacks that a standard |
Allow css files to be marked as async meaning that they will not block rendering. The syntax could be a new attribute on the link element or a new value for "rel" attribute (that already have link types such as "dns-prefetch", "preconnect", "prefetch", and "preload" that also seems to serve a technical, how-to-load purpose), say "
<link rel='stylesheet async' type='text/css' href='theme.css'>
".This functionality is similar to the async attribute on the script element, and the font-display descriptor for the @font-face CSS at-rule.
A web search will find quite a few articles about how to do this in a more or less hackish way, so there seems to be a demand for it. It seems that the browsers could rather easily add this in a much more reliable way as the browser e.g. knows if the resource is already in the cache and can control the priority of requests.
(I did read the contributing guidelines, but as I read them, they only talk about submitting pull requests and not about submitting issues)
The text was updated successfully, but these errors were encountered: