Skip to content
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

Support for Automatically Minimize Render-Blocking CSS #17764

Closed
naveedahmed1 opened this issue May 19, 2020 · 15 comments
Closed

Support for Automatically Minimize Render-Blocking CSS #17764

naveedahmed1 opened this issue May 19, 2020 · 15 comments
Labels
area: @angular-devkit/build-angular feature Issue that requests a new feature
Milestone

Comments

@naveedahmed1
Copy link

naveedahmed1 commented May 19, 2020

🚀 Feature request

Command (mark with an x)

  • [x ] build

Description

The size of CSS for a normal website could be from 100kb to few hundred KBs.

For better performance we should inline the critical css in the head of the html document and lazy load rest of the css through an external file.

Currently Angular CLI adds the css file to the head of the index.html, which makes it render blocking.

Ideally we should have a solution like the one mentioned here https://dzone.com/articles/critical-css-and-webpack-automatically-minimize-re , so that during the build process Angular CLI automatically identify the css of the critical path, inline it in head of the index.html, and lazy load rest of the css using preload e.g.:

<link href="/style.96106fab.css" rel="preload" as="style" onload="this.rel='stylesheet'">

I am not sure how practical the solution described in this post is, but I think the least we could do is allow lazy loading of css e.g through an option in angular.json file that allow us to specify that this particular css file should be lazy loaded and when that option is enabled, Angular CLI should create below tag:

<link href="/style.hash.css" rel="preload" as="style" onload="this.rel='stylesheet'">

instead of

<link rel="stylesheet" href="styles.hash.css">

assuming we have already inline the critical css in index.html.

This Webpack plugin might be relevant and helpful https://www.npmjs.com/package/preload-webpack-plugin

@alan-agius4 alan-agius4 added area: @angular-devkit/build-angular feature Issue that requests a new feature labels May 20, 2020
@ngbot ngbot bot modified the milestone: Backlog May 20, 2020
@naveedahmed1
Copy link
Author

naveedahmed1 commented May 20, 2020

Actually there could be two approaches,

  1. In which Angular CLI could automatically identify Critical Path CSS using Critical plugin.
  2. Where developer add css of critical path directly in index.html and through some attribute in angular.json specify that the external css file should be added as preload tag.

Apps using Universal as well as App Shell should equally benefit from this.

There's also an official webpack plugin (https://github.com/numical/script-ext-html-webpack-plugin) which could be helpful.

I tried adding below to my extend.webpack.config.js file but somehow it didn't work (may be I'm missing something):

const HtmlWebpackPlugin = require('html-webpack-plugin')
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");

module.exports = {
  plugins: [
    new HtmlWebpackPlugin(),
    new ScriptExtHtmlWebpackPlugin({
      preload: /\.css$/
    })
}

@naveedahmed1
Copy link
Author

Here's the similar plugin for Vuejs https://github.com/andreashuber69/async-css-plugin

@SchnWalter
Copy link
Contributor

SchnWalter commented May 25, 2020

With a default Angular application, pre-loading the styles won't do any good. The whole application is rendered using JavaScript, so unless you are using Angular Universal, this extra configuration is of no real help.

And to avoid having huge initial styles, you can minimize your global styles (defined in angular.json) by using lazy modules and defining the module specific styles inside components for that module; You could have something like a wrapping "shell" component with ViewEncapsulation.None.

In which Angular CLI could automatically identify Critical Path CSS using Critical plugin.

Because of the dynamic nature of Angular application and the complexity behind the routing system that supports lazy modules, modules like that won't be of any use. Your best bet is to use lazy-modules with a shell component that deals with more generic styles and in general: just be careful when splitting the application into lazy-modules.

@naveedahmed1
Copy link
Author

Thank you so much @SchnWalter. I agree to your comment, my suggestion is mainly for the Universal apps and those apps that are using appshell model. What if we could add an option of preload in angular.json with each stylesheet, that when specified should generate something like <link href="/style.96106fab.css" rel="preload" as="style" onload="this.rel='stylesheet'">

@SchnWalter
Copy link
Contributor

Yes, but that's not for "Render-Blocking CSS", that's for all the global CSS, and for most angular projects, that won't give a noticeable performance boost for such a feature to be added to the core Angular tooling; this is something for a 3rd party builder.

@naveedahmed1
Copy link
Author

Ok, from your comment #17764 (comment) I am trying another approach, adding critical path css to the index.html and setting "extractCss": false,.

@alan-agius4
Copy link
Collaborator

extractCss is intended to be used only for development to speed up rebuild times.

In general injected global css should be render blocking as otherwise it will cause flickering when there is the transition between the server and client.

My recommendation would be to use both injected and non injected global styles and load the non injected global styles eagerly. Again this might cause flickering when used with SSR.

This issue is also related to #11395 which I think it make sense to continue tracking over there.

@alan-agius4
Copy link
Collaborator

Closing in favour of #11395. If there is a reason why this should be tracked separately please let me know.

@naveedahmed1
Copy link
Author

Thanks @alan-agius4 . I thinks its a different feature request but I wont mind if this could be merged and tracked with #11395 since the objective in both cases is almost same.

Originally my request was that Angular CLI should automatically detect the css for the critical/“above-the-fold” using https://github.com/addyosmani/critical plugin by Addy Osmani and put it inline in index.html where as remaining css should be loaded lazily and CLI should generate a tag similar to this for remaining css:

<link href="/style.96106fab.css" rel="preload" as="style" onload="this.rel='stylesheet'">

If this is too complex to implement and you think it wont add much value, at least there should be an option for user to specify how a particular style file should be added to production output.

For example, it would be great to add inline and preload options in styles in angular.json file.

"styles": [
      { "input": "src/critical-path-styles.scss", "inline": true },
      { "input": "src/lazy-styles.scss", "preload": true }
  ]

With above settings, Angular CLI should inline the contents of critical-path-styles.scss in head tag, and it should generate <link href="/lazy-styles.96106fab.css" rel="preload" as="style" onload="this.rel='stylesheet'"> for lazy-styles.scss.

@alan-agius4
Copy link
Collaborator

Originally my request was that Angular CLI should automatically detect the css for the critical/“above-the-fold” using https://github.com/addyosmani/critical plugin by Addy Osmani and put it inline in index.html where as remaining css should be loaded lazily and CLI should generate a tag similar to this for remaining css:

Unless I am misunderstanding something. This plugin will not work with JS frameworks such as Angular, because the plugin takes an HTML to detect which CSS classes are critical.

With regards to preload and inlining the entire contents, this actually can already be done by extending the default CLI builder.

That said inlining CSS, opens up another discussion around CSP (Content security policy).

@naveedahmed1
Copy link
Author

Unless I am misunderstanding something. This plugin will not work with JS frameworks such as Angular, because the plugin takes an HTML to detect which CSS classes are critical.

I had some idea, and it probably could work only in case of universal/prerendered app or apps using app-shell.

With regards to preload and inlining the entire contents, this actually can already be done by extending the default CLI builder.

Can you please share some more details on this? some docs or example may be?

That said inlining CSS, opens up another discussion around CSP (Content security policy).

I think Google also suggest inlining critical styles and asynchronously loading rest of the styles https://web.dev/render-blocking-resources/ , btw when we're using SSR it also inlines the styles in head of the document.

@alan-agius4
Copy link
Collaborator

alan-agius4 commented May 29, 2020

I had some idea, and it probably could work only in case of universal/prerendered app or apps using app-shell.

This will potentially slow down universal, because each response will need to get parsed and generate critical stylesheet on the fly because each page will have different critical css, with regards to pre-render, you might end up with different stylesheets for each page.

Can you please share some more details on this? some docs or example may be?

There are no docs about this, because while we allow customising your build we don't support it. You can take a look at https://www.npmjs.com/package/@angular-builders/custom-webpack#index-transform

btw when we're using SSR it also inlines the styles in head of the document.

Yeah indeed, that doesn't that's it's not a problem though.

@mgechev
Copy link
Member

mgechev commented Jun 4, 2020

@naveedahmed1 we're looking into this with Chrome. Discussing pros and cons of different approaches.

@naveedahmed1
Copy link
Author

That's great, Thank you so much @mgechev for the update :)

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Jul 6, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: @angular-devkit/build-angular feature Issue that requests a new feature
Projects
None yet
Development

No branches or pull requests

4 participants