Is it Possible to Defer or Async Pipeline for CSS/JS?

I’m in the site speed rabbit hole, and I’m stuck trying to get the pipelined CSS and JS to stop Render Blocking.

I’m basically stuck between enabling css_pipeline so I can combine and minify everything, and manually deferring each css file individually (and js) using base.html.twig.

It feels like I am missing something obvious. Can anyone point me in the right direction?

{% block stylesheets %}
            {% do assets.addCss('theme://css/widget.css', { priority: 20, loading: 'defer', position: 'after'}) %}
            {% do assets.addCss('theme://css/style.css', { priority: 20, loading: 'defer', position: 'after'}) %}
            {% do assets.addCss('theme://css/content.css', { priority: 20, loading: 'defer', position: 'after'}) %}
            {% do assets.addCss('theme://css/sidebar.css', { priority: 20, loading: 'defer', position: 'after'}) %}
            {% do assets.addCss('theme://css/lightbox.css', { priority: 20, loading: 'defer', position: 'after'}) %}
            {% do assets.addCss('theme://css/prism.css', { priority: 20, loading: 'defer', position: 'after'}) %}
            {% do assets.addCss('theme://css/custom.css', { priority: 20, loading: 'defer', position: 'after'}) %}

        {% endblock %}

@ejstauffer, Asset Manager’s option loading: 'defer' does not exist for CSS, only for JS. This is because stylesheets are render-blocking and do not support defer.

To improve the performance of loading/processing css, you can try the following:

  • Split the css in critical and non-critical parts.
  • Load the critical styles inline in <head>
  • Load the non-critical styles using:
    <link rel="preload" href="styles.css" as="style" 
       onload="this.onload=null;this.rel='stylesheet'">
    <noscript><link rel="stylesheet" href="styles.css"></noscript>
    

See Defer non-critical CSS for how-to and explaination.

Here is as far as I am able to get right now.

I am one set of single quotes and a type away from being able to do this through Asset Manager, and use Grav’s pipeline for combining and minifying everything. I just can’t get the output correct.

I have tried every version of this I can find. Single quotes, double quotes, html code, etc. No matter what I do, it spits out the html link using &#039; around stylesheet. I also end up with a type="text/css that I don’t want.

I have tried double quotes around the this.onload... entry, escaping the single quotes, with |raw and without, etc. It always outputs the same:

{{ assets.css('head', {rel: 'preload', as: 'style', onload: 'this.onload=null;this.'stylesheet''}) }}

This is what I get:

<link href="/assets/327aafe7a48971d6d895da85d9cbb966.css" type="text/css" rel="preload" as="style" onload="this.onload=null;this.&#039;stylesheet&#039;">

You can see the type and &#039; in the output.

What does work is this:

<link rel="preload" href="{{ url('theme://css/style.css',) }}" as="style" onload="this.onload=null;this.rel='stylesheet'">

The problem here is that its based on a single file at a time, doesn’t combine or minify. So I currently end up with eight separate files.

I’ve gotten deep down this hole, and still have it in the back of my mind that there is a YAML setting somewhere like css_preload=false. Given the importance of non-render blocking CSS and combining and minifying, there should be a more elegant solution that I may be completely looking past.

@ejstauffer, The <link> generated by Asset Manager might be slightly different from the snippet shown above, but…

  • did rel="preload" change to rel="stylesheet" indicating the onload javascript has worked?
  • and what does Lighthouse show when comparing a link without and with “deferring”? Did the opportunity “Eliminate render-blocking resources” move to “Passed Audits”?