angular / angular-cli

CLI tool for Angular
https://cli.angular.io
MIT License
26.73k stars 11.98k forks source link

Support for Automatically Minimize Render-Blocking CSS #17764

Closed naveedahmed1 closed 4 years ago

naveedahmed1 commented 4 years ago

🚀 Feature request

Command (mark with an x)

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

naveedahmed1 commented 4 years ago

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 commented 4 years ago

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

SchnWalter commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

Ok, from your comment https://github.com/angular/angular-cli/issues/17764#issuecomment-633515912 I am trying another approach, adding critical path css to the index.html and setting "extractCss": false,.

alan-agius4 commented 4 years ago

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 https://github.com/angular/angular-cli/issues/11395 which I think it make sense to continue tracking over there.

alan-agius4 commented 4 years ago

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

naveedahmed1 commented 4 years ago

Thanks @alan-agius4 . I thinks its a different feature request but I wont mind if this could be merged and tracked with https://github.com/angular/angular-cli/issues/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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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

naveedahmed1 commented 4 years ago

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

angular-automatic-lock-bot[bot] commented 4 years ago

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.