Skip to content
This repository has been archived by the owner on Oct 21, 2022. It is now read-only.

Experiment with how to use loadCSS to polyfill link[rel=preload] #59

Closed
scottjehl opened this issue May 6, 2015 · 28 comments
Closed

Experiment with how to use loadCSS to polyfill link[rel=preload] #59

scottjehl opened this issue May 6, 2015 · 28 comments

Comments

@scottjehl
Copy link
Member

In the following demo page I've referenced a slow-loading CSS file with link[rel=preload] and polyfilled its request using loadCSS if a feature test for preload fails. _(Note: the slow-responding CSS file is not critical to this test, but it's useful for visually observing whether or not the CSS request blocks rendering across our test pages).

It works, but I'm unsure whether the syntax and logic lines up with how this feature is intended to work.

http://filamentgroup.github.io/loadCSS/test/preload.html

@igrigorik
Copy link

onload="this.rel='stylesheet'" - ha, clever :)

On first pass, yeah, I think that looks correct.

/cc @yoavweiss

@scottjehl
Copy link
Member Author

thanks @igrigorik :)

...was there an alternative way you were thinking it could be enabled if not the rel toggle?

@igrigorik
Copy link

Assuming that works, what you have is much better (terser) than I had in mind for this particular case. So, no.. That said, for more complicated cases, where you may want to delay the stylesheet until later, or block it on other fetches, you'd probably want to use an external function, etc.

@addyosmani
Copy link
Contributor

What you have now LGTM.

@jakearchibald
Copy link

That's pretty cool. Although I'd still like the blocking solution so I can do something like:

<style>/* inlined css */</style>
content content content
<link rel="stylesheet" href="css-for-next-bit-of-content">
content content content
<link rel="stylesheet" href="more-css-for-next-bit-of-content">
content content content
footer

And have the browser take care of render-blocking for me.

@scottjehl
Copy link
Member Author

@jakearchibald I think that approach is a really interesting idea but it leaves me with some questions about how it would work:

  1. Based on the sites I've built with inline+async CSS loading so far, I think I'm of the opinion that it's preferable to allow the lower region of a page (below the "fold") to render immediately while the full CSS is loading asynchronously, even if the inline CSS doesn't contain all of the styles that region needs to render to its intended state. At least in the layouts I've worked with, the inline CSS tends to carry CSS for the overall page layout that frames page content both above and below "the fold," and a great deal of styles typically cascade through both. Have you found cases where this doesn't happen? Might you have an example of a layout where the below-the-fold region is unusable enough to warrant hiding it while additional CSS is loading?
  2. If this behavior for link in body lands in Chrome, do you think there will be a render-blocking time limit for those links in the body, similar to how browsers are handling their behavior of hiding text while custom fonts are loading? I worry the blocking behavior will make for yet a potential single point of failure for a portion of the page content.
  3. How do you envision this working in a template setting? My initial impression is that in order to pull this off in a large-scale site, you'd need to evaluate the template's "critical" css with a tool like the ones we use today, and then return to the HTML template to insert the link(s) throughout the body of the page, just after the portion of content that was deemed critical in the visual layout?
  4. We typically pair the inline CSS + async full.css approach in such a way that we only need to embed inline CSS for the first page someone visits on a site. After that, the full CSS can be assumed to be cached in the browser and other templates on the site can reference it directly instead of including inline CSS at all. We do this by setting a cookie once the full css has loaded, which is somewhat of an inference but seems to work well. That said, it rests on the idea that the inline CSS for a template is a subset of the full CSS for all templates. Any thoughts on how the body links approach would take advantage of caching in a similar way?
  5. Where can I post these questions instead? :)

Thanks @jakearchibald !

@igrigorik
Copy link

@scottjehl
Copy link
Member Author

Thanks @igrigorik.

Allowing link in body from a validation perspective seems reasonable enough.

Maybe my questions can be summarized with this: In what conditions would it make sense for a developer to place blockinglinks throughout the body if they could instead load the full CSS without blocking render, like the demo page above does? For example, the following image compares a page on Filament's site when fully rendered, to that same page with only its inlined "critical" styles. It seems like the layout on the right is usable enough to show immediately, even if some inner elements will repaint when the full css arrives. Is this the situation the link in body approach aims to avoid?

@jakearchibald
Copy link

Based on the sites I've built with inline+async CSS loading so far, I think I'm of the opinion that it's preferable to allow the lower region of a page (below the "fold") to render immediately while the full CSS is loading asynchronously, even if the inline CSS doesn't contain all of the styles that region needs to render to its intended state.

Can you provide some examples? My feeling was, although the inlined CSS may be able to render parts of below-the-fold, it'll often lead to the page jumping around as the additional CSS loads. You can avoid this by guarding against it with extra CSS, and you should be able to do that with a truly async stylesheet, but I figured it'd be too complex for the default.

I worry the blocking behavior will make for yet another potential single point of failure for a portion of the page content.

I don't think it's "another" point of failure, as it already is. The current state for <link> in body is to block until the CSS loads or fails to load. However, at the moment we block as soon as the <link> is discovered, which can result in blocking content before it.

How do you envision this working in a template setting?

It's down to the page to decide what's first-render critical, rather than a page module. A single page module may be critical on one page, but secondary on another. Does a fully async stylesheet change the pattern here?

We typically pair the inline CSS + async full.css approach in such a way that we only need to embed inline CSS for the first page someone visits on a site. After that, the full CSS can be assumed to be cached in the browser

I don't think that's a great assumption unless you're in full control of the cache (eg ServiceWorker). Stuff falls out of the cache all the time, way more frequently than cookies for instance.

Can you talk me a bit more through what you load inline and what you load async, and what the rendering looks like in between? I don't think I've got the right mental model of the problem.

I saw it like this:

<style>/* inlined css */</style>
CONTENT SET 1
<link rel="stylesheet" href="css-for-next-bit-of-content">
CONTENT SET 2
<link rel="stylesheet" href="more-css-for-next-bit-of-content">
CONTENT SET 3
footer

For the uncached load, you'd inline the CSS for CONTENT SET 1, getting a quick first render. This would be the site header, and at least content title & first few paragraphs. This CSS should aim to be < 20k, meaning it doesn't matter so much if it's delivered with every page. You'd be blocked on the additional stylesheets as they download. With a fully cached load, the block would be less.

Transitioning to HTTP/2, you'd switch the inline css for a normal <link>, then push that along with the page response. You may push other sheets and prioritise accordingly.

For a social media stream, it may be more like this:

<style>/* inlined css */</style>
<link rel="stylesheet" href="feed-extras" async>
<div class="main-feed">
  <article>
    CONTENT
    <div class="buttons-etc"></div>
  </article>
</div>
<link rel="stylesheet" href="secondary">
<div class="secondary"></div>
<link rel="stylesheet" href="tertiary">
<div class="tertiary"></div>

On desktop, secondary & tertiary may be left & right columns. So the inline CSS would get you the content of the feed. feed-extras would bring in buttons-etc, but the inlined CSS would have reserved space for them, so there's no jumping around when this loads (it may even use transitions to fade in).

I guess the pattern here is that particular modules have the capability to fast-load, meaning they inline the essentials, but it's down to the parent module (or page) to decide if they should use that mode for that layout.

@jakearchibald
Copy link

It seems like the layout on the right is usable enough to show immediately

Hmm, maybe I'm wrong, but I feel that the rendering on the right is broken and should be avoided. If the user sees it jump from that to the correct rendering, that further confirms to them that the initial render was broken.

@scottjehl
Copy link
Member Author

Hey @jakearchibald. Thanks for the feedback :)

I definitely agree with you that the layout on the right is incomplete. I just think it's preferable to show content that isn't fully rendered instead of showing nothing at all. If there's a short time after which a browser will stop blocking rendering and show the content following a link, this might be less of a problem I suppose. But without that, it could leave portions of a content (that have already been downloaded) inaccessible for a long period of time. We started inlining CSS to avoid that problem

As a parallel example, it seems like folks have come to an agreement that hiding text for more than a few seconds while custom fonts load is not in our users' best interests. A progressive render in that case, and I think maybe in this case too, seems preferable to showing nothing.

All that said, I'm starting to wonder if an ideal first-approach to "critical css" inlining is to try to capture all CSS necessary for a view, rather than just for the "above fold" portion. Then the async fetch is merely for caching CSS for the next view. Mileage would vary though...

@scottjehl
Copy link
Member Author

Oh - your points about the transition to http2 are great. And the part about the cookie & cache: yeah... hrm. I love the idea about improving that with service worker. In absense of that, it has seemed like a worthwhile assumption, since it often works as we'd prefer, and in the case that it doesn't, the user ends up with a blocking CSS reference, which I'd consider more "unoptimized" than "broken" at least.

@jakearchibald
Copy link

I just think it's preferable to show content that isn't fully rendered instead of showing nothing at all

I'm not so sure, but I don't have any evidence. I take your point about fonts, but I still think a late font switch is a poor experience. I really like the font rendering proposal, as I can have an early fallback, but disable a switch after that.

async fetch is merely for caching CSS for the next view

That's what I did for SVGOMG, I think it works well for low-content "apps" that have most of their complexity an interaction away. With https://wiki-offline.herokuapp.com/wiki/Hulk_Hogan I load the CSS for the article async, but hide the article element until it loads. Can't decide if this is the right approach though (in this case the CSS tends to load before the article content).

@jakearchibald
Copy link

Btw, I haven't done a test to see how inlining compares to HTTP/2 push. A lot of assumption on my part.

@scottjehl
Copy link
Member Author

Btw, I haven't done a test to see how inlining compares to HTTP/2 push

Yeah. We've been going under the assumption that a lot of these workarounds will be unnecessary with Push, but I guess as long as we're serving HTTP1, it's nice to make it as fast as we can.

thanks!

@igrigorik
Copy link

@scottjehl @jakearchibald note that the "scenario on the right" (#59 (comment)) is the behavior you get today in FF and IE: https://www.w3.org/Bugs/Public/show_bug.cgi?id=27303#c27 - not saying it's correct though.. I agree with Jake, I'd prefer to avoid flashing unstyled content. The proposal in the linked bug is to have a safe(r) in-between. That said, if we can't get agreement on that, I'm wondering if we should change Chrome to use FF/IE's strategy. /cc @pmeenan

@scottjehl
Copy link
Member Author

@igrigorik is there an updated approach to detecting link[rel=preload] that we should be aware of at this time? Thanks.

@yoavweiss
Copy link

Still open: w3c/preload#7

@scottjehl
Copy link
Member Author

ah yes, sorry. thanks @yoavweiss

@scottjehl
Copy link
Member Author

Looks like this will be relevant to our interests here https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/xEoWkGEd_g4/Pkn_o92EAwAJ

@aFarkas
Copy link

aFarkas commented Jan 17, 2016

@scottjehl
The following pattern:

<link rel="preload" href="asnyc.css" as="stylesheet" onload="this.rel='stylesheet'">

Does not work in Chrome canary (49.0.2623.0 canary (64-bit)), although it supports preloadaccording to the relList.supports method, because the link never fires an onload event. I think (not sure) this is a bug.

@jakearchibald

I actually very like the idea of blocking below the link/script element content and have all content above visible. Something that would be even more convenient, if we would have an attribute, that allows us to include a link/script only once:

<link rel="stylesheet" href="module-a.css" once>
<div class="module-a">
    <!-- .... -->
</div>
<link rel="stylesheet" href="module-b.css" once>
<script src="module-b.js" once async></script>
<div class="module-b">
    <!-- .... -->
</div>


<link rel="stylesheet" href="module-c.css" once>
<div class="module-c">
    <!-- .... -->
</div>
<link rel="stylesheet" href="module-b.css" once>
<script src="module-b.js" once async></script>
<div class="module-b">
    <!-- .... -->
</div>

@yoavweiss
Copy link

Regarding preload's onload event, it is a bug. Fix is at https://codereview.chromium.org/1586563014/ and I hope to land it very soon

@aFarkas
Copy link

aFarkas commented Jan 19, 2016

@yoavweiss
THX!

scottjehl added a commit that referenced this issue Jan 26, 2016
scottjehl added a commit that referenced this issue Jan 26, 2016
@scottjehl
Copy link
Member Author

Now that Canary's support is testable, there's a workflow script and example for this in master now.
Will tag for release soon.

@MarcoHengstenberg
Copy link

Uuuuh nice. Can't wait!
Currently working on a test-case to use preload with webfonts and some delayed CSS (in combination) and I have a few headaches with the way it turns out currently. =)

@lili21
Copy link

lili21 commented Feb 28, 2017

the demo is broken. I am using chrome 56.

2017-02-28 1 54 00

@bsed
Copy link

bsed commented Aug 7, 2017

onload="this.rel='stylesheet'" good ~

@vince844
Copy link

Hi tanks for your work, It works great on Pagespeed desktop but it doesn't seem to work for mobile as i still get the error message on PageSpeed Insight, is is a google bug?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants