HTTP2 Push Header - Feature Suggestion #49

Open
2aces opened this Issue Jul 20, 2016 · 119 comments

Comments

@2aces

2aces commented Jul 20, 2016

Hey, @futtta . Celso Bessa, AO pt-br translator here.

As you know, HTTP2 push is a big deal in optimization now. We are getting great results in some of our projects with it and I think it would be great if AO included a mechanism to use those headers.

In some test setups we filtered AO final JS/CSS srcs and created our own headers using PHP/WP send headers ( https://codex.wordpress.org/Plugin_API/Action_Reference/send_headers ). this is not hard for us, but it might be not easy for an non technical user.

In other setups we used AO alongside http2 server push plugin by @daveros ), which is great, but it sends headers for all files in the original wp_enqueue queue but not for AO aggregated files (my guess is it uses a hook triggered before AO).

Looking this 2 cases, seems to me that having this on AO would help a lot of you user base.

What do you think? I won't be able to code anything for the next 4 weeks, but if you think it's a good feature, I can work on this in august.

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Jul 21, 2016

Owner

absolutely! :-)

but does this work when using page cache plugin and when AO is set to deliver static files? because in that case you might not have any PHP to start with?

Owner

futtta commented Jul 21, 2016

absolutely! :-)

but does this work when using page cache plugin and when AO is set to deliver static files? because in that case you might not have any PHP to start with?

@futtta futtta added the enhancement label Jul 21, 2016

@2aces

This comment has been minimized.

Show comment
Hide comment
@2aces

2aces Jul 21, 2016

All tests were done on WPengine (which uses a custom varnish implementation, I think) with and without Cloudflare. I guess their cache keeps the headers sent by the original PHP page in the static version.

I will check on other hosts and let you know the results ASAP.

2aces commented Jul 21, 2016

All tests were done on WPengine (which uses a custom varnish implementation, I think) with and without Cloudflare. I guess their cache keeps the headers sent by the original PHP page in the static version.

I will check on other hosts and let you know the results ASAP.

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Jul 21, 2016

Owner

So I read up on the topic a bit :-) and found this interesting article on smashing mag. about preloading, which focuses primarily on the HTML-based preloading (but also mentions the HTTP header-approach).

I think adding the preload both as HTTP resp. header and in the HTML (link rel=preload) would ensure that even in a fully static setup (where headers are not cached) the resources would be loaded over the existing HTTP/2 connection (@daveros does the same, actually)?

Owner

futtta commented Jul 21, 2016

So I read up on the topic a bit :-) and found this interesting article on smashing mag. about preloading, which focuses primarily on the HTML-based preloading (but also mentions the HTTP header-approach).

I think adding the preload both as HTTP resp. header and in the HTML (link rel=preload) would ensure that even in a fully static setup (where headers are not cached) the resources would be loaded over the existing HTTP/2 connection (@daveros does the same, actually)?

@2aces

This comment has been minimized.

Show comment
Hide comment
@2aces

2aces Jul 21, 2016

TL;DR

  • That's the approach we use on our production sites and apps. It should work with AO.
  • check both specifications drafts for preload and other resource hints and Ilya Grigorik notes on the subject.

The Long Version (food for thought)

So far, we use a custom plugin to each site, aggregating all functions, AO filters and techniques for optimization, including different resources hints ( https://www.w3.org/TR/resource-hints/ ) for different resources to ensure maximum browser support, depending on the resource importance, origin, position and how sure we are we will use a given resource:

    <link rel="preconnect" href="https://api.tiles.mapbox.com">
    <link rel="dns-prefetch" href="https://api.tiles.mapbox.com">
    <link rel="preload" as="script" crossorigin href="https://api.mapbox.com/mapbox.js/plugins/leaflet-omnivore/v0.2.0/leaflet-omnivore.min.js">
    <link rel="preconnect" href="https://api.mapbox.com/">
    <link rel="dns-prefetch" href="https://api.mapbox.com/">
    <link rel="preload" as="style" href="https://mydomain.com/path-to-ao-cache/ao-aggregated-style.css">
  • Line 1: we know we need to connect to that domain, but not exactly which resource. We use preconnect to suggest browsers which support it to initiate connection.
  • Line 2: this hint has a wider browser support, we use as fallback for browsers that don't support the previous hint to make the initial DNS lookup. In our tests, browsers supporting preconnect ignore this one if the connection on line 1 was made (all resource hints are suggestions... er, hints... and UA decides if it will process it) .
  • Line 3: We know exactly which resource we want and it's really important. Let's preload it in browsers that support it. Note that we need both the crossorigin andas parameters if said resource is external.
  • Lines 4 and 5: act as fallback for browsers not supporting preload, same way like lines 1 and 2.
  • Line 6: we know we will need this über important resource, lazy-loaded by AO. So, let's fetch it.

Since last week, we're shipping our plugins with our send header functions just with preload for the most important resources. That's what "triggered" our suggestion, because we thought it would be great for other AO users. :-)

I guess AO would work great with just preload on headers and inline, but if you want to have wider browser support, use preconnect and dns-prefetch as well. This should be filterable/optional because it will work great most of time, but sometimes it won't, depending on the aggregated file size, original CSS rules, dom complexity, etc

2aces commented Jul 21, 2016

TL;DR

  • That's the approach we use on our production sites and apps. It should work with AO.
  • check both specifications drafts for preload and other resource hints and Ilya Grigorik notes on the subject.

The Long Version (food for thought)

So far, we use a custom plugin to each site, aggregating all functions, AO filters and techniques for optimization, including different resources hints ( https://www.w3.org/TR/resource-hints/ ) for different resources to ensure maximum browser support, depending on the resource importance, origin, position and how sure we are we will use a given resource:

    <link rel="preconnect" href="https://api.tiles.mapbox.com">
    <link rel="dns-prefetch" href="https://api.tiles.mapbox.com">
    <link rel="preload" as="script" crossorigin href="https://api.mapbox.com/mapbox.js/plugins/leaflet-omnivore/v0.2.0/leaflet-omnivore.min.js">
    <link rel="preconnect" href="https://api.mapbox.com/">
    <link rel="dns-prefetch" href="https://api.mapbox.com/">
    <link rel="preload" as="style" href="https://mydomain.com/path-to-ao-cache/ao-aggregated-style.css">
  • Line 1: we know we need to connect to that domain, but not exactly which resource. We use preconnect to suggest browsers which support it to initiate connection.
  • Line 2: this hint has a wider browser support, we use as fallback for browsers that don't support the previous hint to make the initial DNS lookup. In our tests, browsers supporting preconnect ignore this one if the connection on line 1 was made (all resource hints are suggestions... er, hints... and UA decides if it will process it) .
  • Line 3: We know exactly which resource we want and it's really important. Let's preload it in browsers that support it. Note that we need both the crossorigin andas parameters if said resource is external.
  • Lines 4 and 5: act as fallback for browsers not supporting preload, same way like lines 1 and 2.
  • Line 6: we know we will need this über important resource, lazy-loaded by AO. So, let's fetch it.

Since last week, we're shipping our plugins with our send header functions just with preload for the most important resources. That's what "triggered" our suggestion, because we thought it would be great for other AO users. :-)

I guess AO would work great with just preload on headers and inline, but if you want to have wider browser support, use preconnect and dns-prefetch as well. This should be filterable/optional because it will work great most of time, but sometimes it won't, depending on the aggregated file size, original CSS rules, dom complexity, etc

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Jul 21, 2016

Owner

great work, interesting stuff! looking forward to your contributions!!

Owner

futtta commented Jul 21, 2016

great work, interesting stuff! looking forward to your contributions!!

@zytzagoo

This comment has been minimized.

Show comment
Hide comment
@zytzagoo

zytzagoo Aug 11, 2016

Collaborator

Some interesting details here: https://blog.yoav.ws/being_pushy/

Collaborator

zytzagoo commented Aug 11, 2016

Some interesting details here: https://blog.yoav.ws/being_pushy/

@2aces

This comment has been minimized.

Show comment
Hide comment
@2aces

2aces Aug 11, 2016

@zytzagoo interesting article indeed

2aces commented Aug 11, 2016

@zytzagoo interesting article indeed

@WebSwiftSEO

This comment has been minimized.

Show comment
Hide comment
@WebSwiftSEO

WebSwiftSEO Aug 12, 2016

If you guys need anything, just send me a Short message and I can Look into
it :-) ;-)

On Friday, 12 August 2016, 2ACES notifications@github.com wrote:

@zytzagoo https://github.com/zytzagoo interesting article indeed


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#49 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ARjhk8fr6H0knFE1nvjsvAxHR3qruc04ks5qe5cHgaJpZM4JRNhx
.

Best Regards

Ylia Callan

WEB SWIFT SEO
Tips - Tools - Techniques

https://webswiftseo.com

If you guys need anything, just send me a Short message and I can Look into
it :-) ;-)

On Friday, 12 August 2016, 2ACES notifications@github.com wrote:

@zytzagoo https://github.com/zytzagoo interesting article indeed


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#49 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ARjhk8fr6H0knFE1nvjsvAxHR3qruc04ks5qe5cHgaJpZM4JRNhx
.

Best Regards

Ylia Callan

WEB SWIFT SEO
Tips - Tools - Techniques

https://webswiftseo.com

@trajano

This comment has been minimized.

Show comment
Hide comment
@trajano

trajano Aug 19, 2016

Can

$content = apply_filters( 'autoptimize_html_after_minify', $content );
be changed to pass in the URL for the cached + minified CSS and JS? That way we can do

function http2_server_push($content, $cached_js, $cached_css) {
  header(
    sprintf(
      'Link: <%s>; rel=preload; as=%s',
         $cached_js, 'script'
    )
  )
  header(
    sprintf(
      'Link: <%s>; rel=preload; as=%s',
         $cached_css, 'style'
    )
  )
}
add_filter('autoptimize_html_after_minify', 'http2_server_push')

trajano commented Aug 19, 2016

Can

$content = apply_filters( 'autoptimize_html_after_minify', $content );
be changed to pass in the URL for the cached + minified CSS and JS? That way we can do

function http2_server_push($content, $cached_js, $cached_css) {
  header(
    sprintf(
      'Link: <%s>; rel=preload; as=%s',
         $cached_js, 'script'
    )
  )
  header(
    sprintf(
      'Link: <%s>; rel=preload; as=%s',
         $cached_css, 'style'
    )
  )
}
add_filter('autoptimize_html_after_minify', 'http2_server_push')
@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Aug 19, 2016

Owner

well, I think at the very least we should hook into wordpress' send_headers action hook to avoid sending headers out of order.

Owner

futtta commented Aug 19, 2016

well, I think at the very least we should hook into wordpress' send_headers action hook to avoid sending headers out of order.

@trajano

This comment has been minimized.

Show comment
Hide comment
@trajano

trajano Aug 19, 2016

Anyway the workaround I have for now is

function http2_server_push($content) {
  $header = "Link: ";
  if (preg_match('#="([^"]+/js/autoptimize_[0-9a-f]+\.js)"#', $content, $matches)) {
    $header .= sprintf(
        '<%s>; rel=preload; as=%s,',
           $matches[1], 'script'
      );
  }
  if (preg_match('#="([^"]+/css/autoptimize_[0-9a-f]+\.css)"#', $content, $matches)) {
    $header .=
      sprintf(
        '<%s>; rel=preload; as=%s',
           $matches[1], 'style'
      );
  }
  header($header);
  return $content;
}
add_filter('autoptimize_html_after_minify', 'http2_server_push');

Seems to work a bit on my blog https://www.trajano.net/. I see the JS, CSS being loaded as soon as possible when I check the network graph.

trajano commented Aug 19, 2016

Anyway the workaround I have for now is

function http2_server_push($content) {
  $header = "Link: ";
  if (preg_match('#="([^"]+/js/autoptimize_[0-9a-f]+\.js)"#', $content, $matches)) {
    $header .= sprintf(
        '<%s>; rel=preload; as=%s,',
           $matches[1], 'script'
      );
  }
  if (preg_match('#="([^"]+/css/autoptimize_[0-9a-f]+\.css)"#', $content, $matches)) {
    $header .=
      sprintf(
        '<%s>; rel=preload; as=%s',
           $matches[1], 'style'
      );
  }
  header($header);
  return $content;
}
add_filter('autoptimize_html_after_minify', 'http2_server_push');

Seems to work a bit on my blog https://www.trajano.net/. I see the JS, CSS being loaded as soon as possible when I check the network graph.

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Aug 20, 2016

Owner

Nice! It does work; your AO JS is linked at the end of the HTML, with defer attribute, but it is indeed loaded immediately as per this webpagetest.org test which also shows that the initial request has this as header;

link: <https://www.trajano.net/wp-content/cache/autoptimize/js/autoptimize_7f1fb7f2c06f6c4218428fe4c1904176.js>; rel=preload; as=script,<https://www.trajano.net/wp-content/cache/autoptimize/css/autoptimize_dc58df74105fec34d124e8ddef6f0210.css>; rel=preload; as=style

the only thing which I can't deduct from the waterfall chart is that the preloaded CSS/ JS isn't render-blocking (it shouldn't as per the specs, obviously).

If I were you I would install a page cache plugin to minimize my TTFB, would be interesting to see if such a plugin also caches headers?

Owner

futtta commented Aug 20, 2016

Nice! It does work; your AO JS is linked at the end of the HTML, with defer attribute, but it is indeed loaded immediately as per this webpagetest.org test which also shows that the initial request has this as header;

link: <https://www.trajano.net/wp-content/cache/autoptimize/js/autoptimize_7f1fb7f2c06f6c4218428fe4c1904176.js>; rel=preload; as=script,<https://www.trajano.net/wp-content/cache/autoptimize/css/autoptimize_dc58df74105fec34d124e8ddef6f0210.css>; rel=preload; as=style

the only thing which I can't deduct from the waterfall chart is that the preloaded CSS/ JS isn't render-blocking (it shouldn't as per the specs, obviously).

If I were you I would install a page cache plugin to minimize my TTFB, would be interesting to see if such a plugin also caches headers?

@trajano

This comment has been minimized.

Show comment
Hide comment
@trajano

trajano Aug 20, 2016

I would agree with the page cache. I just never got around to doing it, I've tried a few before (like years ago) but they had some issues on a very limited memory machine and running on Oracle Linux with SELinux on full blast. It may be better now, but I never invested time on it.

However for one thing I don't like having to wait on the cache, I would rather it change as I change things. Since my blog is more for play around rather than a heavily utilized site.

trajano commented Aug 20, 2016

I would agree with the page cache. I just never got around to doing it, I've tried a few before (like years ago) but they had some issues on a very limited memory machine and running on Oracle Linux with SELinux on full blast. It may be better now, but I never invested time on it.

However for one thing I don't like having to wait on the cache, I would rather it change as I change things. Since my blog is more for play around rather than a heavily utilized site.

@trajano

This comment has been minimized.

Show comment
Hide comment
@trajano

trajano Aug 20, 2016

What I would like to know is if it were at all possible to send the headers ASAP then the content because it looks like it processes the whole page first. But then a cache would likely help there.

trajano commented Aug 20, 2016

What I would like to know is if it were at all possible to send the headers ASAP then the content because it looks like it processes the whole page first. But then a cache would likely help there.

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Aug 21, 2016

Owner

it simply has to process the page first, as:

  • headers are sent as part of the actual HTTP response
  • autoptimize needs the full page to extract & optimize the CSS/JS and you need AO to extract the to-be-preloaded URL's

so yeah, I would go the page cache route :-)

Owner

futtta commented Aug 21, 2016

it simply has to process the page first, as:

  • headers are sent as part of the actual HTTP response
  • autoptimize needs the full page to extract & optimize the CSS/JS and you need AO to extract the to-be-preloaded URL's

so yeah, I would go the page cache route :-)

@trajano

This comment has been minimized.

Show comment
Hide comment
@trajano

trajano Aug 21, 2016

Tried using WP Super Cache (had it's share of issues with permissions and what not) but I got it working in the end. I lose the headers now :(

Content is faster though.

https://www.webpagetest.org/result/160821_V0_F2D/2/performance_optimization/#first_byte_time

trajano commented Aug 21, 2016

Tried using WP Super Cache (had it's share of issues with permissions and what not) but I got it working in the end. I lose the headers now :(

Content is faster though.

https://www.webpagetest.org/result/160821_V0_F2D/2/performance_optimization/#first_byte_time

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Aug 21, 2016

Owner

I guess (hope) there must be page caching plugins that also cache headers ...

Owner

futtta commented Aug 21, 2016

I guess (hope) there must be page caching plugins that also cache headers ...

@trajano

This comment has been minimized.

Show comment
Hide comment
@trajano

trajano Aug 21, 2016

Here's a slightly better one that I am using, it will scan through and preload all JS, CSS PNG and JPGs that are found in the content. I kind of want to remove the second regexp, but got lazy :)


                        $header = "Link: ";
                        $regexp = '#(src|href)="([^"]+\.(js|css|png|jpg)(\?[^"]+)?)"#';
                        if (preg_match_all($regexp, $uncompressed_file_data, $matches, PREG_SET_ORDER)) {
                                foreach ($matches as $match) {
                                        $file = $match[2];
                                        $type = $match[3];
                                        if ($type === 'js') {
                                                $type = 'script';
                                        } else if ($type === 'css') {
                                                $type = 'style';
                                        } else {
                                                $type = 'image';
                                        }
                                        $header .= sprintf('<%s>; rel=preload; as=%s,', $file, $type);
                                }
                        }
                        $regexp = str_replace('"', "'", $regexp);
                        if (preg_match_all($regexp, $uncompressed_file_data, $matches, PREG_SET_ORDER)) {
                                foreach ($matches as $match) {
                                        $file = $match[2];
                                        $type = $match[3];
                                        if ($type === 'js') {
                                                $type = 'script';
                                        } else if ($type === 'css') {
                                                $type = 'style';
                                        } else {
                                                $type = 'image';
                                        }
                                        $header .= sprintf('<%s>; rel=preload; as=%s,', $file, $type);
                                }
                        }
                        header(rtrim($header, ","));

trajano commented Aug 21, 2016

Here's a slightly better one that I am using, it will scan through and preload all JS, CSS PNG and JPGs that are found in the content. I kind of want to remove the second regexp, but got lazy :)


                        $header = "Link: ";
                        $regexp = '#(src|href)="([^"]+\.(js|css|png|jpg)(\?[^"]+)?)"#';
                        if (preg_match_all($regexp, $uncompressed_file_data, $matches, PREG_SET_ORDER)) {
                                foreach ($matches as $match) {
                                        $file = $match[2];
                                        $type = $match[3];
                                        if ($type === 'js') {
                                                $type = 'script';
                                        } else if ($type === 'css') {
                                                $type = 'style';
                                        } else {
                                                $type = 'image';
                                        }
                                        $header .= sprintf('<%s>; rel=preload; as=%s,', $file, $type);
                                }
                        }
                        $regexp = str_replace('"', "'", $regexp);
                        if (preg_match_all($regexp, $uncompressed_file_data, $matches, PREG_SET_ORDER)) {
                                foreach ($matches as $match) {
                                        $file = $match[2];
                                        $type = $match[3];
                                        if ($type === 'js') {
                                                $type = 'script';
                                        } else if ($type === 'css') {
                                                $type = 'style';
                                        } else {
                                                $type = 'image';
                                        }
                                        $header .= sprintf('<%s>; rel=preload; as=%s,', $file, $type);
                                }
                        }
                        header(rtrim($header, ","));

@vijayaraghavanramanan

This comment has been minimized.

Show comment
Hide comment
@vijayaraghavanramanan

vijayaraghavanramanan Aug 22, 2016

@2aces

There are two preloads around and it can cause confusion.

The W3 link you linked is not a push preload. It is just an instruction to the browser to fetch a resource with highest priority (and not execute it or use a code onload).

This can be done even if there is no HTTP2.

The other preload is of course pushing assets to the browser.

@2aces

There are two preloads around and it can cause confusion.

The W3 link you linked is not a push preload. It is just an instruction to the browser to fetch a resource with highest priority (and not execute it or use a code onload).

This can be done even if there is no HTTP2.

The other preload is of course pushing assets to the browser.

@trajano

This comment has been minimized.

Show comment
Hide comment
@trajano

trajano Aug 22, 2016

For server push you need two things: the Link header and a server such as nghttp2 that can parse the Link header and start sending. I haven't gotten it to work with nginx yet.

trajano commented Aug 22, 2016

For server push you need two things: the Link header and a server such as nghttp2 that can parse the Link header and start sending. I haven't gotten it to work with nginx yet.

@vijayaraghavanramanan

This comment has been minimized.

Show comment
Hide comment
@vijayaraghavanramanan

vijayaraghavanramanan Sep 3, 2016

Some thoughts:

Push optimisation is the best when the critical css is pushed and the html doesn't have any inline/critical css. It's a separate file.

Pull preload for the full css file will be as good as the push css.

It's pushing the critical css which can make the difference.

As Ilya Grigorik says:

n fact, if you have ever inlined a resource (CSS, JS, or an image), you've been "simulating" server push: an inlined resource is "pushed" as part of the parent document. The only difference is that HTTP 2.0 makes this pattern more efficient and far more powerful! ... HTTP 2.0 server push obsoletes inlining.

https://www.igvita.com/2013/06/12/innovating-with-http-2.0-server-push/

So if it's something to be pushed ... it's the critical css, not the full css.

Some thoughts:

Push optimisation is the best when the critical css is pushed and the html doesn't have any inline/critical css. It's a separate file.

Pull preload for the full css file will be as good as the push css.

It's pushing the critical css which can make the difference.

As Ilya Grigorik says:

n fact, if you have ever inlined a resource (CSS, JS, or an image), you've been "simulating" server push: an inlined resource is "pushed" as part of the parent document. The only difference is that HTTP 2.0 makes this pattern more efficient and far more powerful! ... HTTP 2.0 server push obsoletes inlining.

https://www.igvita.com/2013/06/12/innovating-with-http-2.0-server-push/

So if it's something to be pushed ... it's the critical css, not the full css.

@vijayaraghavanramanan

This comment has been minimized.

Show comment
Hide comment
@vijayaraghavanramanan

vijayaraghavanramanan Sep 3, 2016

In other words, the best optimization is:

  • critical css is not inlined and pushed as an external css.
  • the html has a render blocking link declaration for the critical css.
  • the full css is fetched via a pull preload, not pushed.

In other words, the best optimization is:

  • critical css is not inlined and pushed as an external css.
  • the html has a render blocking link declaration for the critical css.
  • the full css is fetched via a pull preload, not pushed.
@2aces

This comment has been minimized.

Show comment
Hide comment
@2aces

2aces Sep 6, 2016

Just to give some sanity check, we are not talking about only HTTP2 push header anymore, right? If so, maybe we should change the Issue title and description.

2aces commented Sep 6, 2016

Just to give some sanity check, we are not talking about only HTTP2 push header anymore, right? If so, maybe we should change the Issue title and description.

@2aces

This comment has been minimized.

Show comment
Hide comment
@2aces

2aces Sep 6, 2016

  1. we gotta evaluate which resources to support;
  2. we gotta evaluate which methods will be available (link element, http2 push);
  3. decide if we use WordPress 4.6 class or write our own;
  4. which ones will be available on settings and which ones only by filters;

What is sure is that every setup will have different demands and outcomes. I mean:

  • 1 page sites VS sites with lot of pages VS sites with something in between have different demands?
  • Theme templates which are almost the same VS too diferent post type and archives templates.

Specifically about preload AND HTTP2 push, we gotta be careful as @zytzagoo pointed out, it may result in overhead for subsequent page visits . In my specific test setups on WPEngine with sites with small diferences, it was worth anyway.

Build on what @vijayaraghavanramanan listed as the best optimization:

  • critical css is not inlined and pushed as an external css (1)
  • the html has a render blocking link declaration for the critical css (2)(3)
  • the full css is fetched via a pull preload, not pushed (4).
    - the full css is inserted on DOM dynamically as is done today when using inline CSS(5)
  • (1) optionally, via filter, keeping backwards compatibility and non-advanced users sanity).
  • (2) optionally as effect of the previous one.
  • (3) why not pushing it, specially when using an CDN, aiming to avoid blocking for long times while waiting the DNS lookup and download. After all this is the critical one and should be parsed and applied ASAP.
  • (4) why not an option of doing both, via filter?
  • (5) some browsers don't support preload and some servers won't have HTTP2 headers push support, and even they do, it may take some time to download the CSS, so, loading dynamically will keep users happy any way.

PS: @trajano your code looks efficient for this, as soon as I am able, I will test it.

2aces commented Sep 6, 2016

  1. we gotta evaluate which resources to support;
  2. we gotta evaluate which methods will be available (link element, http2 push);
  3. decide if we use WordPress 4.6 class or write our own;
  4. which ones will be available on settings and which ones only by filters;

What is sure is that every setup will have different demands and outcomes. I mean:

  • 1 page sites VS sites with lot of pages VS sites with something in between have different demands?
  • Theme templates which are almost the same VS too diferent post type and archives templates.

Specifically about preload AND HTTP2 push, we gotta be careful as @zytzagoo pointed out, it may result in overhead for subsequent page visits . In my specific test setups on WPEngine with sites with small diferences, it was worth anyway.

Build on what @vijayaraghavanramanan listed as the best optimization:

  • critical css is not inlined and pushed as an external css (1)
  • the html has a render blocking link declaration for the critical css (2)(3)
  • the full css is fetched via a pull preload, not pushed (4).
    - the full css is inserted on DOM dynamically as is done today when using inline CSS(5)
  • (1) optionally, via filter, keeping backwards compatibility and non-advanced users sanity).
  • (2) optionally as effect of the previous one.
  • (3) why not pushing it, specially when using an CDN, aiming to avoid blocking for long times while waiting the DNS lookup and download. After all this is the critical one and should be parsed and applied ASAP.
  • (4) why not an option of doing both, via filter?
  • (5) some browsers don't support preload and some servers won't have HTTP2 headers push support, and even they do, it may take some time to download the CSS, so, loading dynamically will keep users happy any way.

PS: @trajano your code looks efficient for this, as soon as I am able, I will test it.

@2aces

This comment has been minimized.

Show comment
Hide comment
@2aces

2aces Sep 6, 2016

About using WordPress class: it doesn't support preload right now. I think the best course of action would be expanding it for preload and HTTP2 push headers and if works good, we propose it to merge it on core.

2aces commented Sep 6, 2016

About using WordPress class: it doesn't support preload right now. I think the best course of action would be expanding it for preload and HTTP2 push headers and if works good, we propose it to merge it on core.

@vijayaraghavanramanan

This comment has been minimized.

Show comment
Hide comment
@vijayaraghavanramanan

vijayaraghavanramanan Sep 7, 2016

@2aces,

Correct.

Only Chrome/Opera support preload in stable and Firefox is building it but not in Nightly yet. My points were incomplete.

So I should say my points should read:

  • critical css is not inlined and pushed as an external css.
  • the html has a render blocking link declaration for the critical css.
  • the full css is fetched via a pull preload, not pushed and applied via "onload" in the link rel declaration.
  • for browsers which do not support preload, polyfill it with Autoptimize's existing js.
  • for these browsers also use a sessionstorage variable to optimize on second load, i.e, load the full css immediately in the head on repeat website view.

I mentioned not pushing the full css as one should push as less as possible. So html and critical css can load fast if that's the case.

@2aces,

Correct.

Only Chrome/Opera support preload in stable and Firefox is building it but not in Nightly yet. My points were incomplete.

So I should say my points should read:

  • critical css is not inlined and pushed as an external css.
  • the html has a render blocking link declaration for the critical css.
  • the full css is fetched via a pull preload, not pushed and applied via "onload" in the link rel declaration.
  • for browsers which do not support preload, polyfill it with Autoptimize's existing js.
  • for these browsers also use a sessionstorage variable to optimize on second load, i.e, load the full css immediately in the head on repeat website view.

I mentioned not pushing the full css as one should push as less as possible. So html and critical css can load fast if that's the case.

@2aces

This comment has been minimized.

Show comment
Hide comment
@2aces

2aces Sep 7, 2016

@vijayaraghavanramanan :

"for browsers which do not support preload, polyfill it with Autoptimize's existing js."
My understanding of preload draft specification and for all tests I conducted is that pushing the CSS using http2 push doesn't mean it is inserted in the DOM and parsed, it is only downloaded. Therefore, we need AO javascript even when the browser and server supports pushing headers.

"for these browsers also use a sessionstorage variable to optimize on second load, i.e, load the full css immediately in the head on repeat website view."
Can you elaborate?

2aces commented Sep 7, 2016

@vijayaraghavanramanan :

"for browsers which do not support preload, polyfill it with Autoptimize's existing js."
My understanding of preload draft specification and for all tests I conducted is that pushing the CSS using http2 push doesn't mean it is inserted in the DOM and parsed, it is only downloaded. Therefore, we need AO javascript even when the browser and server supports pushing headers.

"for these browsers also use a sessionstorage variable to optimize on second load, i.e, load the full css immediately in the head on repeat website view."
Can you elaborate?

@vijayaraghavanramanan

This comment has been minimized.

Show comment
Hide comment
@vijayaraghavanramanan

vijayaraghavanramanan Sep 7, 2016

@2aces

Right. It's just downloaded. But it allows what you can do onload. So that's why I said onload in point 3 in my previous comment.

So you can declare it like this:

<link rel="preload" href="http://www.example.com/wp-content/cache/autoptimize/css/autoptimize-hash.css" as="style" onload="preloadFinished(this)">

and before it define a function in javascript

<script>
function preloadFinished( el ) {

...

}
</script>

or pass href instead of this

About sessionstorage, what I meant was that once a visitor visits a site the css file is already in the cache. So for repeat views, you can do better than loading css in the footer. The browser now doesn't need to fetch the css from the server as it is in the cache.

So in the footer, add this line in javascript:

sessionStorage.fullaocssloaded = "true";

And in the header,

<script>
if (! relpreloadsupport) {
  if (sessionStorage.fullaocssloaded ) {
     //javascript to insert the full css immediately. 
 }
}
</script>

What the above code does is that it checks if there's a sessionStorage variable. If true, it means almost certainly that the css is in the cache. So why not load it in the head.

It's slightly more complicated as some browsers do not allow sessionStorage in incognito mode.

vijayaraghavanramanan commented Sep 7, 2016

@2aces

Right. It's just downloaded. But it allows what you can do onload. So that's why I said onload in point 3 in my previous comment.

So you can declare it like this:

<link rel="preload" href="http://www.example.com/wp-content/cache/autoptimize/css/autoptimize-hash.css" as="style" onload="preloadFinished(this)">

and before it define a function in javascript

<script>
function preloadFinished( el ) {

...

}
</script>

or pass href instead of this

About sessionstorage, what I meant was that once a visitor visits a site the css file is already in the cache. So for repeat views, you can do better than loading css in the footer. The browser now doesn't need to fetch the css from the server as it is in the cache.

So in the footer, add this line in javascript:

sessionStorage.fullaocssloaded = "true";

And in the header,

<script>
if (! relpreloadsupport) {
  if (sessionStorage.fullaocssloaded ) {
     //javascript to insert the full css immediately. 
 }
}
</script>

What the above code does is that it checks if there's a sessionStorage variable. If true, it means almost certainly that the css is in the cache. So why not load it in the head.

It's slightly more complicated as some browsers do not allow sessionStorage in incognito mode.

@vijayaraghavanramanan

This comment has been minimized.

Show comment
Hide comment
@vijayaraghavanramanan

vijayaraghavanramanan Sep 7, 2016

This is the full javascript code. It use requestAnimationFrame, but you can use Autoptimize's lCSS instead.

The code is a collection of various snippets at various points in the HTML, not to be used next to each other.

function preloadFinished(node) {
    var res = document.createElement("link");
    res.rel = "stylesheet";
    res.href = node.href;
    node.parentNode.insertBefore( res, node.nextSibling );
}

var linkSupportsPreload = function() {
    try {
        return document.createElement("link").relList.supports("preload");
    } catch (e) {
        return false;
    }
};

var sessionStorageAvailable = function() {
    var mod = 'modernizr';
    try {
        sessionStorage.setItem(mod, mod);
        sessionStorage.removeItem(mod);
        return true;
    } catch (e) {
        return false;
    }
};

var cb = function() {
    var links = document.getElementsByTagName("link");
    for (var i = 0; i < links.length; i++ ) {
        var link = links[i];
        if( link.rel === "preload" && link.getAttribute( "as" ) === "style" ) {
            preloadFinished(link);
        }
    }
}

if( !linkSupportsPreload() ) {
    if( (sessionStorageAvailable() && sessionStorage.fullcssloaded) || !sessionStorageAvailable() ) {
        cb();
    }
}

var rAF = (function() {
    return window.requestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || window.webkitRequestAnimationFrame || function( callback ) {
        window.setTimeout(callback, 1000 / 60);
    }
})();

if ( !linkSupportsPreload() ) {
    if( sessionStorageAvailable() && !sessionStorage.fullcssloaded ) {
        setTimeout(function() {
            rAF(cb);
            sessionStorage.fullcssloaded = "true";
        });
    }
}

This is the full javascript code. It use requestAnimationFrame, but you can use Autoptimize's lCSS instead.

The code is a collection of various snippets at various points in the HTML, not to be used next to each other.

function preloadFinished(node) {
    var res = document.createElement("link");
    res.rel = "stylesheet";
    res.href = node.href;
    node.parentNode.insertBefore( res, node.nextSibling );
}

var linkSupportsPreload = function() {
    try {
        return document.createElement("link").relList.supports("preload");
    } catch (e) {
        return false;
    }
};

var sessionStorageAvailable = function() {
    var mod = 'modernizr';
    try {
        sessionStorage.setItem(mod, mod);
        sessionStorage.removeItem(mod);
        return true;
    } catch (e) {
        return false;
    }
};

var cb = function() {
    var links = document.getElementsByTagName("link");
    for (var i = 0; i < links.length; i++ ) {
        var link = links[i];
        if( link.rel === "preload" && link.getAttribute( "as" ) === "style" ) {
            preloadFinished(link);
        }
    }
}

if( !linkSupportsPreload() ) {
    if( (sessionStorageAvailable() && sessionStorage.fullcssloaded) || !sessionStorageAvailable() ) {
        cb();
    }
}

var rAF = (function() {
    return window.requestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || window.webkitRequestAnimationFrame || function( callback ) {
        window.setTimeout(callback, 1000 / 60);
    }
})();

if ( !linkSupportsPreload() ) {
    if( sessionStorageAvailable() && !sessionStorage.fullcssloaded ) {
        setTimeout(function() {
            rAF(cb);
            sessionStorage.fullcssloaded = "true";
        });
    }
}
@vijayaraghavanramanan

This comment has been minimized.

Show comment
Hide comment
@vijayaraghavanramanan

vijayaraghavanramanan Sep 8, 2016

Btw in select cases, it makes sense to just have the full css pushed and block render with the full css and not have critical/above the fold css at all.

It won't have the problems of speed as the css arrives at the same time as the initial HTML, so rendering is fast as the usual "inline and defer"

But that's in the case where the full css is not a big file. If it's big, it makes sense to push the critical css file only.

Btw in select cases, it makes sense to just have the full css pushed and block render with the full css and not have critical/above the fold css at all.

It won't have the problems of speed as the css arrives at the same time as the initial HTML, so rendering is fast as the usual "inline and defer"

But that's in the case where the full css is not a big file. If it's big, it makes sense to push the critical css file only.

@vijayaraghavanramanan

This comment has been minimized.

Show comment
Hide comment
@vijayaraghavanramanan

vijayaraghavanramanan Sep 27, 2016

I have a slightly improved optimization method below. The simplest solution is of course is to push the full css. But this is not optimal as its size can be greater than 14KB. Some Wordpress bundled themes have things such as Genericons which are huge and its not optimal to push them.

My improvement to the previous comments is that for second loads, you don't need critical css. The server might still push it for second loads (but some are thinking of improvements to cancel it from the client side) and it's still better to not use it and thus avoid repaints.

  • Push critical.min.css and insert it synchronously via javascript depending upon whether the page has a sessionstorage variable. (My earlier comment said insert it via link). If the page doesn't have sessionstorage variable set to true, insert it synchronously, else don't use critical.min.css. Effectively first load uses critical.min.css but second load doesn't).
  • Fetch the full style.min.css via pull preload (not push) and apply it onload if the page doesn't have sessionstorage variable. If the page has sessionstorage variable set to true, don't pull preload and instead insert style.min.css via javascript synchronously.
  • For browsers not supporting pull preload, polyfill (the second point).

(Browsers which don't support HTTP2 push see very old behaviour, don't enjoy "inline and defer". Minor sacrifice since most modern browsers do support HTTP2 push).

vijayaraghavanramanan commented Sep 27, 2016

I have a slightly improved optimization method below. The simplest solution is of course is to push the full css. But this is not optimal as its size can be greater than 14KB. Some Wordpress bundled themes have things such as Genericons which are huge and its not optimal to push them.

My improvement to the previous comments is that for second loads, you don't need critical css. The server might still push it for second loads (but some are thinking of improvements to cancel it from the client side) and it's still better to not use it and thus avoid repaints.

  • Push critical.min.css and insert it synchronously via javascript depending upon whether the page has a sessionstorage variable. (My earlier comment said insert it via link). If the page doesn't have sessionstorage variable set to true, insert it synchronously, else don't use critical.min.css. Effectively first load uses critical.min.css but second load doesn't).
  • Fetch the full style.min.css via pull preload (not push) and apply it onload if the page doesn't have sessionstorage variable. If the page has sessionstorage variable set to true, don't pull preload and instead insert style.min.css via javascript synchronously.
  • For browsers not supporting pull preload, polyfill (the second point).

(Browsers which don't support HTTP2 push see very old behaviour, don't enjoy "inline and defer". Minor sacrifice since most modern browsers do support HTTP2 push).

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Sep 27, 2016

Owner

My 2c:

  • as HTTP/2 support doesn't only depend on client but also server and as I want AO support to be as broad as possible (and as I like clean and simple solutions), I would by default (so this could be different behind a filter, but that filter would for now default to false) keep inline CSS inline, also for 2nd loads, the overhead is minimal and this is never render-blocking (so only disadvantage is slightly bigger HTML payload).
  • the pull preload solution is fancy, but rather complex esp. with that polyfilling going on (e.g. I don't want to depend on modernizr in AO)
  • push is the future
Owner

futtta commented Sep 27, 2016

My 2c:

  • as HTTP/2 support doesn't only depend on client but also server and as I want AO support to be as broad as possible (and as I like clean and simple solutions), I would by default (so this could be different behind a filter, but that filter would for now default to false) keep inline CSS inline, also for 2nd loads, the overhead is minimal and this is never render-blocking (so only disadvantage is slightly bigger HTML payload).
  • the pull preload solution is fancy, but rather complex esp. with that polyfilling going on (e.g. I don't want to depend on modernizr in AO)
  • push is the future
@vijayaraghavanramanan

This comment has been minimized.

Show comment
Hide comment
@vijayaraghavanramanan

vijayaraghavanramanan Sep 27, 2016

Hi Frank,

Yeah, It's just catching up and would imagine only few percent of server fully supporting it. This is because in addition to supporting push, they also need to be HTTPS. Push can work without HTTPS but browsers have implemented it so that it can work only in HTTPS. So inlining critical css as AO default makes sense.

About my code, I took it from Modernizr but doesn't depend on it. So alternatively one can use this from Mozilla's site:

function storageAvailable(type) {
    try {
        var storage = window[type],
            x = '__storage_test__';
        storage.setItem(x, x);
        storage.removeItem(x);
        return true;
    }
    catch(e) {
        return false;
    }
}

if (storageAvailable('localStorage')) {
    // Yippee! We can use localStorage awesomeness
}
else {
    // Too bad, no localStorage for us
}

with sessionStorage instead of localStorage.

Hi Frank,

Yeah, It's just catching up and would imagine only few percent of server fully supporting it. This is because in addition to supporting push, they also need to be HTTPS. Push can work without HTTPS but browsers have implemented it so that it can work only in HTTPS. So inlining critical css as AO default makes sense.

About my code, I took it from Modernizr but doesn't depend on it. So alternatively one can use this from Mozilla's site:

function storageAvailable(type) {
    try {
        var storage = window[type],
            x = '__storage_test__';
        storage.setItem(x, x);
        storage.removeItem(x);
        return true;
    }
    catch(e) {
        return false;
    }
}

if (storageAvailable('localStorage')) {
    // Yippee! We can use localStorage awesomeness
}
else {
    // Too bad, no localStorage for us
}

with sessionStorage instead of localStorage.

@trajano

This comment has been minimized.

Show comment
Hide comment
@trajano

trajano Jan 23, 2017

I had a recent finding with HTTP2 Server Push, but it may just be a Chome bug/limitation. https://trajano.net/2017/01/double-downloads-with-http2-server-push/ if you preload and the resource is not dynamically added using scripts in Chrome you will get a double download.

However, given that we can probably do an optimization where the CSS is added to the DOM by the script and asynchronously loaded. With HTTP/2 Server Push the CSS can be preloaded in the background while the initial DOM is being processed and then bound later by the script.

trajano commented Jan 23, 2017

I had a recent finding with HTTP2 Server Push, but it may just be a Chome bug/limitation. https://trajano.net/2017/01/double-downloads-with-http2-server-push/ if you preload and the resource is not dynamically added using scripts in Chrome you will get a double download.

However, given that we can probably do an optimization where the CSS is added to the DOM by the script and asynchronously loaded. With HTTP/2 Server Push the CSS can be preloaded in the background while the initial DOM is being processed and then bound later by the script.

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Jan 25, 2017

Owner

weird that you got

The resource … was preloaded using link preload

while you were not preloading via a link but via the HTTP response header? or were you doing both?

Owner

futtta commented Jan 25, 2017

weird that you got

The resource … was preloaded using link preload

while you were not preloading via a link but via the HTTP response header? or were you doing both?

@trajano

This comment has been minimized.

Show comment
Hide comment
@trajano

trajano Jan 25, 2017

Either one will yield the same problem. Doing in Link header will just download a bit more data sooner I presume because of Server Side push. Again I think it could be a Chrome implementation issue, because I do not see anything in the spec that states that it needs to be loaded via script. But then again it could be worded differently.

trajano commented Jan 25, 2017

Either one will yield the same problem. Doing in Link header will just download a bit more data sooner I presume because of Server Side push. Again I think it could be a Chrome implementation issue, because I do not see anything in the spec that states that it needs to be loaded via script. But then again it could be worded differently.

@vijayaraghavanramanan

This comment has been minimized.

Show comment
Hide comment
@vijayaraghavanramanan

vijayaraghavanramanan Jan 27, 2017

I have seen those sort of Chrome warnings but it's usually because something is not done right.

This is a good page and I don't get any warning here on Chrome:

https://www.filamentgroup.com/lab/modernizing-delivery.html

I have seen those sort of Chrome warnings but it's usually because something is not done right.

This is a good page and I don't get any warning here on Chrome:

https://www.filamentgroup.com/lab/modernizing-delivery.html

@trajano

This comment has been minimized.

Show comment
Hide comment
@trajano

trajano Sep 3, 2017

You just need to know what the scripts would load (which is not really an easy thing to do in a general automated fashion)

Some candidates for this would be local fonts which can passed using preload and HTTP Push. Below-the-fold CSS can also be preloaded while the above the fold CSS is still part of the HTML that gets originally sent.

Lastly another candidate for preload would be the Ad network CSSes which are generally loaded via script.

In order for this to work well you need to have them part of the header rather than the body because if it is in the body then it will have to read a few bytes in and parse before it can do anything. You still need it in the body to actually be used by your page though.

The hack I did for this on an earlier version was to create a ".headers" file that gets sent if it is found and have a rule in the Apache configuration.

trajano commented Sep 3, 2017

You just need to know what the scripts would load (which is not really an easy thing to do in a general automated fashion)

Some candidates for this would be local fonts which can passed using preload and HTTP Push. Below-the-fold CSS can also be preloaded while the above the fold CSS is still part of the HTML that gets originally sent.

Lastly another candidate for preload would be the Ad network CSSes which are generally loaded via script.

In order for this to work well you need to have them part of the header rather than the body because if it is in the body then it will have to read a few bytes in and parse before it can do anything. You still need it in the body to actually be used by your page though.

The hack I did for this on an earlier version was to create a ".headers" file that gets sent if it is found and have a rule in the Apache configuration.

@kevin25

This comment has been minimized.

Show comment
Hide comment
@kevin25

kevin25 Sep 4, 2017

@futtta Do you have any sample?

kevin25 commented Sep 4, 2017

@futtta Do you have any sample?

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Sep 4, 2017

Owner
Owner

futtta commented Sep 4, 2017

@trajano

This comment has been minimized.

Show comment
Hide comment
@trajano

trajano Sep 4, 2017

@kevin25 http://www.javascriptkit.com/javatutors/loadjavascriptcss.shtml but I don't recommend it for anything above the fold. (i.e. anything shown on the first page before you scroll)

trajano commented Sep 4, 2017

@kevin25 http://www.javascriptkit.com/javatutors/loadjavascriptcss.shtml but I don't recommend it for anything above the fold. (i.e. anything shown on the first page before you scroll)

@grzegorz-janoszka

This comment has been minimized.

Show comment
Hide comment
@grzegorz-janoszka

grzegorz-janoszka Apr 18, 2018

I think the solutions in this post stopped working.
I have:

add_filter('autoptimize_filter_cache_getname','pushAOFiles');
function pushAOFiles($in) {
$pushType = substr($in,strrpos($in,".")+1) === "js" ? "script" : "style";
header('Link: <'.$in.'>; rel=preload; type='.$pushType,false);
return $in;
}
add_filter('autoptimize_filter_js_exclude','pushJQuery');
function pushJQuery($in) {
if (strpos($in,"js/jquery/jquery.js")!==false) {
$jQurl=includes_url("js/jquery/jquery.js");
header('Link: <'.$jQurl.'>; rel=preload; type=js',false);
}
}

and it seems it doesn't work anymore. Any help with that highly appreciated.

I think the solutions in this post stopped working.
I have:

add_filter('autoptimize_filter_cache_getname','pushAOFiles');
function pushAOFiles($in) {
$pushType = substr($in,strrpos($in,".")+1) === "js" ? "script" : "style";
header('Link: <'.$in.'>; rel=preload; type='.$pushType,false);
return $in;
}
add_filter('autoptimize_filter_js_exclude','pushJQuery');
function pushJQuery($in) {
if (strpos($in,"js/jquery/jquery.js")!==false) {
$jQurl=includes_url("js/jquery/jquery.js");
header('Link: <'.$jQurl.'>; rel=preload; type=js',false);
}
}

and it seems it doesn't work anymore. Any help with that highly appreciated.

@tdtgit

This comment has been minimized.

Show comment
Hide comment
@tdtgit

tdtgit Apr 18, 2018

The below function works with latest AO even.

function http2_server_push($content) {
	$header = "Link: ";
	  if (preg_match('#="([^"]+/js/autoptimize_[0-9a-f]+\.js)"#', $content, $matches)) {
	    $header .= sprintf(
	        '<%s>; rel=preload; as=%s,',
	           $matches[1], 'script'
	      );
	  }
		
	  if (preg_match('#="([^"]+/css/autoptimize_[0-9a-f]+\.css)"#', $content, $matches)) {
	    $header .=
	      sprintf(
	        '<%s>; rel=preload; as=%s',
	           $matches[1], 'style'
	      );
	  }
	  header($header);
	  return $content;
}
add_filter('autoptimize_html_after_minify', 'http2_server_push');

tdtgit commented Apr 18, 2018

The below function works with latest AO even.

function http2_server_push($content) {
	$header = "Link: ";
	  if (preg_match('#="([^"]+/js/autoptimize_[0-9a-f]+\.js)"#', $content, $matches)) {
	    $header .= sprintf(
	        '<%s>; rel=preload; as=%s,',
	           $matches[1], 'script'
	      );
	  }
		
	  if (preg_match('#="([^"]+/css/autoptimize_[0-9a-f]+\.css)"#', $content, $matches)) {
	    $header .=
	      sprintf(
	        '<%s>; rel=preload; as=%s',
	           $matches[1], 'style'
	      );
	  }
	  header($header);
	  return $content;
}
add_filter('autoptimize_html_after_minify', 'http2_server_push');
@grzegorz-janoszka

This comment has been minimized.

Show comment
Hide comment
@grzegorz-janoszka

grzegorz-janoszka Apr 18, 2018

Indeed, but it is sooo heavy! I am looking for a more light solution.

Indeed, but it is sooo heavy! I am looking for a more light solution.

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 18, 2018

Owner

what version are you testing against @grzegorz-janoszka ; 2.3.4 or 2.4-beta ?

Owner

futtta commented Apr 18, 2018

what version are you testing against @grzegorz-janoszka ; 2.3.4 or 2.4-beta ?

@grzegorz-janoszka

This comment has been minimized.

Show comment
Hide comment
@grzegorz-janoszka

grzegorz-janoszka Apr 18, 2018

Ah, indeed that's handy information. I am using 2.3.4 still.

Ah, indeed that's handy information. I am using 2.3.4 still.

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 18, 2018

Owner

just tested on my local dev machine, works for me @grzegorz-janoszka ?

image

Owner

futtta commented Apr 18, 2018

just tested on my local dev machine, works for me @grzegorz-janoszka ?

image

@tdtgit

This comment has been minimized.

Show comment
Hide comment
@tdtgit

tdtgit Apr 19, 2018

Hi @futtta, just wonder your preload script using type instead of as.

Timeline of using as, scripts and styles preloaded at top priority:
screen shot 2018-04-19 at 10 34 26

Timeline of using type, CSS files are not loaded even:
screen shot 2018-04-19 at 10 35 27

And maybe

header('link: <'.$jQurl.'>; rel=preload; type=js',false);

should be

header('link: <'.$jQurl.'>; rel=preload; as=script',false); too :)

tdtgit commented Apr 19, 2018

Hi @futtta, just wonder your preload script using type instead of as.

Timeline of using as, scripts and styles preloaded at top priority:
screen shot 2018-04-19 at 10 34 26

Timeline of using type, CSS files are not loaded even:
screen shot 2018-04-19 at 10 35 27

And maybe

header('link: <'.$jQurl.'>; rel=preload; type=js',false);

should be

header('link: <'.$jQurl.'>; rel=preload; as=script',false); too :)

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 19, 2018

Owner

absolutely @tdtgit :)

Owner

futtta commented Apr 19, 2018

absolutely @tdtgit :)

@grzegorz-janoszka

This comment has been minimized.

Show comment
Hide comment
@grzegorz-janoszka

grzegorz-janoszka Apr 19, 2018

@futta, thank you for testing. I noticed in the past that it sometimes worked, sometimes didn't. I checked all the headers called with Link: and they all have false as the second argument.
I was thinking that maybe wordpress already output some data and thus the header call is void.
I have another header call that always worked and it is at the 'template_redirect' level.
What main level of actions/filters is the code I pasted called?

@futta, thank you for testing. I noticed in the past that it sometimes worked, sometimes didn't. I checked all the headers called with Link: and they all have false as the second argument.
I was thinking that maybe wordpress already output some data and thus the header call is void.
I have another header call that always worked and it is at the 'template_redirect' level.
What main level of actions/filters is the code I pasted called?

@tdtgit

This comment has been minimized.

Show comment
Hide comment
@tdtgit

tdtgit Apr 19, 2018

@grzegorz-janoszka Try my edited code here. @futta's code is running fine when adding Link preload to response header, but not correctly so browser isn't preloading resources at top priority.

tdtgit commented Apr 19, 2018

@grzegorz-janoszka Try my edited code here. @futta's code is running fine when adding Link preload to response header, but not correctly so browser isn't preloading resources at top priority.

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 19, 2018

Owner

it hooks into AO's autoptimize_filter_cache_getname (in autoptimizeCache.php) which is added in autoptimizeCache.php's getname() function which is called after CSS & JS minification as part of their cache() function.

Owner

futtta commented Apr 19, 2018

it hooks into AO's autoptimize_filter_cache_getname (in autoptimizeCache.php) which is added in autoptimizeCache.php's getname() function which is called after CSS & JS minification as part of their cache() function.

@grzegorz-janoszka

This comment has been minimized.

Show comment
Hide comment
@grzegorz-janoszka

grzegorz-janoszka Apr 19, 2018

@tdtgit I updated my code with your suggestion, thank you. But my problem is that the headers are not sent.
@futtta but on which level on the top-level action/filters is all that code happening?

@tdtgit I updated my code with your suggestion, thank you. But my problem is that the headers are not sent.
@futtta but on which level on the top-level action/filters is all that code happening?

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 19, 2018

Owner
Owner

futtta commented Apr 19, 2018

@tdtgit

This comment has been minimized.

Show comment
Hide comment
@tdtgit

tdtgit Apr 19, 2018

@grzegorz-janoszka Can you send me your site?
@futtta HTTP/2 Push is a cool feature and it's needed when HTTPS and HTTP/2 is became more popular. When will we see it as an option in AO plugin?

tdtgit commented Apr 19, 2018

@grzegorz-janoszka Can you send me your site?
@futtta HTTP/2 Push is a cool feature and it's needed when HTTPS and HTTP/2 is became more popular. When will we see it as an option in AO plugin?

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 19, 2018

Owner
Owner

futtta commented Apr 19, 2018

@grzegorz-janoszka

This comment has been minimized.

Show comment
Hide comment
@grzegorz-janoszka

grzegorz-janoszka Apr 19, 2018

I just disabled all other plugins and stripped my own plugin to just those two AO filters and still nothing - no link headers sent by autoptimize_filter_cache_getname :(
Really have no idea where to look for it.
Yesterday I updated nginx on my site to support http2_push_preload and I would like to test it... and I can't :(

I just disabled all other plugins and stripped my own plugin to just those two AO filters and still nothing - no link headers sent by autoptimize_filter_cache_getname :(
Really have no idea where to look for it.
Yesterday I updated nginx on my site to support http2_push_preload and I would like to test it... and I can't :(

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 19, 2018

Owner
Owner

futtta commented Apr 19, 2018

@tdtgit

This comment has been minimized.

Show comment
Hide comment
@tdtgit

tdtgit Apr 19, 2018

@futtta Why not the whole AO'ed CSS & JS files? Split the big one file to smaller files to take the advantage of HTTP/2 for multiple request is should considered too.

tdtgit commented Apr 19, 2018

@futtta Why not the whole AO'ed CSS & JS files? Split the big one file to smaller files to take the advantage of HTTP/2 for multiple request is should considered too.

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 19, 2018

Owner
Owner

futtta commented Apr 19, 2018

@grzegorz-janoszka

This comment has been minimized.

Show comment
Hide comment
@grzegorz-janoszka

grzegorz-janoszka Apr 20, 2018

@futta I have spent some time and for some reason I don't get the headers displayed. I know the code is executed with correct parameters as I've tried to put syslog into the function.
I have disabled all our plugins and still couldn't get it. I am testing it on my test blog, where there is no cache and I tested it - any changes in the code are immediately visible.
I guess the reason is that something is output before I call the header function. What is weird when I use echo in the function, I don't get the message in the html code. So I am not really sure when the code is executed as the echo doesn't get into the page and headers are not sent.
Is there a way to check which output has been already sent or why the headers function didn't work? My error log and access log do not contain anything like "headers already sent".

@futta I have spent some time and for some reason I don't get the headers displayed. I know the code is executed with correct parameters as I've tried to put syslog into the function.
I have disabled all our plugins and still couldn't get it. I am testing it on my test blog, where there is no cache and I tested it - any changes in the code are immediately visible.
I guess the reason is that something is output before I call the header function. What is weird when I use echo in the function, I don't get the message in the html code. So I am not really sure when the code is executed as the echo doesn't get into the page and headers are not sent.
Is there a way to check which output has been already sent or why the headers function didn't work? My error log and access log do not contain anything like "headers already sent".

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 20, 2018

Owner
Owner

futtta commented Apr 20, 2018

@grzegorz-janoszka

This comment has been minimized.

Show comment
Hide comment
@grzegorz-janoszka

grzegorz-janoszka Apr 20, 2018

There are no exceptions thrown, but after putting syslog messages here and there in the code I see that the filter is mostly not called. It is sometimes called when I visit the dashboard, but I check the headers using curl -I and then the AO filter is not called at all. Why would that be?

There are no exceptions thrown, but after putting syslog messages here and there in the code I see that the filter is mostly not called. It is sometimes called when I visit the dashboard, but I check the headers using curl -I and then the AO filter is not called at all. Why would that be?

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 21, 2018

Owner

Weird, as from a code point of view the logic dictates the filter is to be triggered each time AO gets triggered for all active minifiers (CSS or JS):

  1. for each minifier the steps are (see classes/autoptimizeMain.php in 2.4 or autoptimize.php in 2.3.x, code from 2.4)
                $instance->minify();
                $instance->cache();
                $instance->getcontent();
  1. in $instance->cache() autoptimizeCache's getname() is called unconditionally (below example for JS, cfr. classes/autoptimizeScripts.php):
        $this->url = AUTOPTIMIZE_CACHE_URL . $cache->getname();
  1. in cache->getname() the filter gets triggered, unconditionally (see classes/autoptimizeCache.php):
       apply_filters( 'autoptimize_filter_cache_getname', AUTOPTIMIZE_CACHE_URL . $this->filename );

I'll dive in later this weekend to see if I can reproduce somehow, but just tested on my dev-machine and
the filter always gets triggered here.

Owner

futtta commented Apr 21, 2018

Weird, as from a code point of view the logic dictates the filter is to be triggered each time AO gets triggered for all active minifiers (CSS or JS):

  1. for each minifier the steps are (see classes/autoptimizeMain.php in 2.4 or autoptimize.php in 2.3.x, code from 2.4)
                $instance->minify();
                $instance->cache();
                $instance->getcontent();
  1. in $instance->cache() autoptimizeCache's getname() is called unconditionally (below example for JS, cfr. classes/autoptimizeScripts.php):
        $this->url = AUTOPTIMIZE_CACHE_URL . $cache->getname();
  1. in cache->getname() the filter gets triggered, unconditionally (see classes/autoptimizeCache.php):
       apply_filters( 'autoptimize_filter_cache_getname', AUTOPTIMIZE_CACHE_URL . $this->filename );

I'll dive in later this weekend to see if I can reproduce somehow, but just tested on my dev-machine and
the filter always gets triggered here.

@grzegorz-janoszka

This comment has been minimized.

Show comment
Hide comment
@grzegorz-janoszka

grzegorz-janoszka Apr 24, 2018

Actually I had some issues with multiple nginx processes logging the same stuff and it got mixed up. Indeed I see the function called all the time.
I am not sure however it is called under template_redirect. I have my own function under template_redirect, I have put it now the the priority PHP_INT_MAX and in logs I still see my function trying to send headers (successfully) and then AO filter trying to send its own headers (unsuccessfully).

Actually I had some issues with multiple nginx processes logging the same stuff and it got mixed up. Indeed I see the function called all the time.
I am not sure however it is called under template_redirect. I have my own function under template_redirect, I have put it now the the priority PHP_INT_MAX and in logs I still see my function trying to send headers (successfully) and then AO filter trying to send its own headers (unsuccessfully).

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 24, 2018

Owner
Owner

futtta commented Apr 24, 2018

@grzegorz-janoszka

This comment has been minimized.

Show comment
Hide comment
@grzegorz-janoszka

grzegorz-janoszka Apr 25, 2018

Oh my, I've spent so much time on that and now I finally know. I was checking my headers using curl -I, but this thing calls only the head and somehow then the AO headers are not sent.
When I called properly curl -v, I see all the headers.
I am really sorry for false alarm. AO is a great plugin.
So just to summary, is it good to link js files with "rel=preload; as=script" and css files with "rel=preload; type=style"?

Oh my, I've spent so much time on that and now I finally know. I was checking my headers using curl -I, but this thing calls only the head and somehow then the AO headers are not sent.
When I called properly curl -v, I see all the headers.
I am really sorry for false alarm. AO is a great plugin.
So just to summary, is it good to link js files with "rel=preload; as=script" and css files with "rel=preload; type=style"?

@grzegorz-janoszka

This comment has been minimized.

Show comment
Hide comment
@grzegorz-janoszka

grzegorz-janoszka Apr 25, 2018

One last update, the original code: containing "Link: <'.$in.'>" doesn't work with nginx directive http2_push_preload.
Finally I have push running on my website, but I had to use:

header('Link: <'.parse_url($in,PHP_URL_PATH).'>; rel=preload; as=script',false);

It would be awesome if AO could just push such headers all but itself :)

One last update, the original code: containing "Link: <'.$in.'>" doesn't work with nginx directive http2_push_preload.
Finally I have push running on my website, but I had to use:

header('Link: <'.parse_url($in,PHP_URL_PATH).'>; rel=preload; as=script',false);

It would be awesome if AO could just push such headers all but itself :)

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 25, 2018

Owner
Owner

futtta commented Apr 25, 2018

@grzegorz-janoszka

This comment has been minimized.

Show comment
Hide comment
@grzegorz-janoszka

grzegorz-janoszka Apr 25, 2018

is "as=" also for styles? should it be "as=style"?
I am not using Chrome, but I tested it with nghttp2 and https://http2-push.io/ and they are showing resources on website pushed.
So far I am pushing only css and js from AO, The image files are not pushed. Well, actually the tiniest ones are pushed as I have 3 small SVG sprites that I put inline in the CSS just to save http requests on files that are 600-800 bytes long.
I am aware of some disadvantages of this push (no caching for css/js on the browser side) and I will closely monitor how it works.
Thank you so much for your fantastic support.

is "as=" also for styles? should it be "as=style"?
I am not using Chrome, but I tested it with nghttp2 and https://http2-push.io/ and they are showing resources on website pushed.
So far I am pushing only css and js from AO, The image files are not pushed. Well, actually the tiniest ones are pushed as I have 3 small SVG sprites that I put inline in the CSS just to save http requests on files that are 600-800 bytes long.
I am aware of some disadvantages of this push (no caching for css/js on the browser side) and I will closely monitor how it works.
Thank you so much for your fantastic support.

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 25, 2018

Owner
Owner

futtta commented Apr 25, 2018

@grzegorz-janoszka

This comment has been minimized.

Show comment
Hide comment
@grzegorz-janoszka

grzegorz-janoszka Apr 26, 2018

Last question - I see that just before the filtering you are adding the protocol and site name to to create URL from URI. But in the filter I have to do for nginx the opposite - to strip the protocol and site. Any possibility to simplify that?

Last question - I see that just before the filtering you are adding the protocol and site name to to create URL from URI. But in the filter I have to do for nginx the opposite - to strip the protocol and site. Any possibility to simplify that?

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 26, 2018

Owner

AUTOPTIMIZE_CACHE_URL doesn't only contain protocol and site, but also the path to the AO cache directory. and the site is needed later on for CDN-replacement purposes, so guess you'll have to keep the your filter strip it :-)

Owner

futtta commented Apr 26, 2018

AUTOPTIMIZE_CACHE_URL doesn't only contain protocol and site, but also the path to the AO cache directory. and the site is needed later on for CDN-replacement purposes, so guess you'll have to keep the your filter strip it :-)

@tdtgit

This comment has been minimized.

Show comment
Hide comment
@tdtgit

tdtgit Apr 30, 2018

Hi @futtta, have a good week.

I have spent my time to test 3 scenarios: inline all CSS, minify and aggregate, minify but not aggregate and I found out the third scenario work like a charm. Thanks to AO 2.4, HTTP/2 Push and you :)

screen shot 2018-04-30 at 14 01 34

Just more question:

1. Why not minify inline CSS to files and then include it like another. Or is there a problem or not effective to using it? Example:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .my-first-line-css-section { /* ... */ }
    </style>
</head>
<body>
    
</body>
</html>

Will become this:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <!-- Will become this -->
    <link rel='preload' as='style' onload="this.onload=null;this.rel='stylesheet'" href="./ao-single.with-random-ID.css">
</head>
<body>
    <!-- Will become this -->
    <link rel='preload' as='style' onload="this.onload=null;this.rel='stylesheet'" href="./ao-single.with-random-ID.css">
    <div>
        <div>
            <div>
                <div>

                </div>
            </div>
        </div>
    </div>
    <!-- Will become this -->
    <link rel='preload' as='style' onload="this.onload=null;this.rel='stylesheet'" href="./ao-single.with-random-ID.css">
</body>
</html>

2. Exclude JS unexpected not working for me, AO 2.4 and latest stable even.

tdtgit commented Apr 30, 2018

Hi @futtta, have a good week.

I have spent my time to test 3 scenarios: inline all CSS, minify and aggregate, minify but not aggregate and I found out the third scenario work like a charm. Thanks to AO 2.4, HTTP/2 Push and you :)

screen shot 2018-04-30 at 14 01 34

Just more question:

1. Why not minify inline CSS to files and then include it like another. Or is there a problem or not effective to using it? Example:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .my-first-line-css-section { /* ... */ }
    </style>
</head>
<body>
    
</body>
</html>

Will become this:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <!-- Will become this -->
    <link rel='preload' as='style' onload="this.onload=null;this.rel='stylesheet'" href="./ao-single.with-random-ID.css">
</head>
<body>
    <!-- Will become this -->
    <link rel='preload' as='style' onload="this.onload=null;this.rel='stylesheet'" href="./ao-single.with-random-ID.css">
    <div>
        <div>
            <div>
                <div>

                </div>
            </div>
        </div>
    </div>
    <!-- Will become this -->
    <link rel='preload' as='style' onload="this.onload=null;this.rel='stylesheet'" href="./ao-single.with-random-ID.css">
</body>
</html>

2. Exclude JS unexpected not working for me, AO 2.4 and latest stable even.

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 30, 2018

Owner

re 1. inline CSS has no perf impact that I know off, so don't really see a need to extract it and make it external?

re 2. can you give some more info?

Owner

futtta commented Apr 30, 2018

re 1. inline CSS has no perf impact that I know off, so don't really see a need to extract it and make it external?

re 2. can you give some more info?

@tdtgit

This comment has been minimized.

Show comment
Hide comment
@tdtgit

tdtgit Apr 30, 2018

1. I know, Pagespeed Insights doesn't judged inline CSS minify too. But every bytes count 🤓 So which AO's hook or filter I can research and write my own integration? If it's working, that will fixed my #124 too.

2. My current setting:

My Google Tagmanager tag is aggregated to AO minified JS too. So I check again and found out it's not working if I provide gtm or googletagmanager in option field even. I can't remember why and when it stopped exclude.

screen shot 2018-04-30 at 14 44 49

tdtgit commented Apr 30, 2018

1. I know, Pagespeed Insights doesn't judged inline CSS minify too. But every bytes count 🤓 So which AO's hook or filter I can research and write my own integration? If it's working, that will fixed my #124 too.

2. My current setting:

My Google Tagmanager tag is aggregated to AO minified JS too. So I check again and found out it's not working if I provide gtm or googletagmanager in option field even. I can't remember why and when it stopped exclude.

screen shot 2018-04-30 at 14 44 49

@futtta

This comment has been minimized.

Show comment
Hide comment
@futtta

futtta Apr 30, 2018

Owner
  1. I guess autoptimize_filter_html_before_minify, where you could try to extract all inline CSS and/ or JS, write it to a file and insert a link to the file where the minification can pick it up?
  2. should work, but let's not discuss here as not related to HTTP/2 at all. you can mail me at frank-at-optimizingmatters-dot-com so we can look into this
Owner

futtta commented Apr 30, 2018

  1. I guess autoptimize_filter_html_before_minify, where you could try to extract all inline CSS and/ or JS, write it to a file and insert a link to the file where the minification can pick it up?
  2. should work, but let's not discuss here as not related to HTTP/2 at all. you can mail me at frank-at-optimizingmatters-dot-com so we can look into this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment