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

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

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

Comments

Projects
None yet
10 participants
@scottjehl
Copy link
Member

scottjehl commented May 6, 2015

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

This comment has been minimized.

Copy link

igrigorik commented May 7, 2015

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

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

/cc @yoavweiss

@scottjehl

This comment has been minimized.

Copy link
Member

scottjehl commented May 7, 2015

thanks @igrigorik :)

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

@igrigorik

This comment has been minimized.

Copy link

igrigorik commented May 7, 2015

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

This comment has been minimized.

Copy link
Contributor

addyosmani commented May 7, 2015

What you have now LGTM.

@jakearchibald

This comment has been minimized.

Copy link

jakearchibald commented May 7, 2015

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

This comment has been minimized.

Copy link
Member

scottjehl commented May 7, 2015

@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

This comment has been minimized.

@scottjehl

This comment has been minimized.

Copy link
Member

scottjehl commented May 7, 2015

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

This comment has been minimized.

Copy link

jakearchibald commented May 9, 2015

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

This comment has been minimized.

Copy link

jakearchibald commented May 9, 2015

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

This comment has been minimized.

Copy link
Member

scottjehl commented May 10, 2015

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

This comment has been minimized.

Copy link
Member

scottjehl commented May 10, 2015

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

This comment has been minimized.

Copy link

jakearchibald commented May 10, 2015

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

This comment has been minimized.

Copy link

jakearchibald commented May 10, 2015

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

@scottjehl

This comment has been minimized.

Copy link
Member

scottjehl commented May 10, 2015

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

This comment has been minimized.

Copy link

igrigorik commented May 11, 2015

@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

This comment has been minimized.

Copy link
Member

scottjehl commented Aug 28, 2015

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

@yoavweiss

This comment has been minimized.

Copy link

yoavweiss commented Aug 28, 2015

Still open: w3c/preload#7

@scottjehl

This comment has been minimized.

Copy link
Member

scottjehl commented Aug 28, 2015

ah yes, sorry. thanks @yoavweiss

@scottjehl

This comment has been minimized.

Copy link
Member

scottjehl commented Nov 19, 2015

@aFarkas

This comment has been minimized.

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

This comment has been minimized.

Copy link

yoavweiss commented Jan 17, 2016

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

This comment has been minimized.

Copy link

aFarkas commented Jan 19, 2016

@yoavweiss
THX!

scottjehl pushed a commit that referenced this issue Jan 26, 2016

scottjehl pushed a commit that referenced this issue Jan 26, 2016

@scottjehl

This comment has been minimized.

Copy link
Member

scottjehl commented Jan 27, 2016

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

@scottjehl scottjehl closed this Jan 27, 2016

@MarcoHengstenberg

This comment has been minimized.

Copy link

MarcoHengstenberg commented Jan 27, 2016

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

This comment has been minimized.

Copy link

lili21 commented Feb 28, 2017

the demo is broken. I am using chrome 56.

2017-02-28 1 54 00

@bsed

This comment has been minimized.

Copy link

bsed commented Aug 7, 2017

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

@vince844

This comment has been minimized.

Copy link

vince844 commented Mar 18, 2018

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 join this conversation on GitHub. Already have an account? Sign in to comment