-
Notifications
You must be signed in to change notification settings - Fork 12k
[RFC] Eliminate Render Blocking Requests #18730
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
Comments
Small note: I think using a faux media type for async loading CSS will download the CSS file anyway on some browsers. Read more: https://bugs.chromium.org/p/chromium/issues/detail?id=977573 |
does this also means in dev. mode CSS could be reloaded and injected with out the need to reload the application? because that would be a great boost. |
@fabianrios, this is already possible when using HMR. |
I would vote for opt-out. If this would compromise the dev experience, maybe a clear option on the |
@alan-agius4 not completely, it happens for the global CSS files but the ones inside the components don't get injected with out reloading. |
You need to configure your application to handle components changes: #17324 (comment) Let’s however keep on the RFC topic please. |
Remove unused CSS https://web.dev/unused-css-rules/ |
@alan-agius4 that answer was very very helpful, sorry to deviate the topic. |
Agree with @adnanebrahimi. The easiest way to remove unused CSS in Angular apps is to use For such a huge savings in file-size, its really strange that something similar isn't supported out of the box by Angular. |
In order to understand what is being proposed, it is important to define "Critical CSS". I haven't done testing, but I would guess that critters and critical have different methods of defining the Critical CSS that they inline. I would imagine the same thing for Penthouse. Further, based on my understanding of how it is calculated would mean that each route in the app could have distinct Critical CSS.
|
Hi all, thanks for the excellent feedback.
@adnanebrahimi & @elliotleelewis, unused CSS removal is a very interesting topic and is on the radar. However, it gets tricky if your application content is dynamic where the parts of the content gets retrieved from an API/Database or classes are interpolated example: While unused CSS is definitely useful, it doesn't help reducing or removing render blocking requests. It's important to remember that a 0Kb CSS file still impacts performance negatively, because of the time spent for a connection to be estimated and the file to be downloaded all while rendering is blocked.
@aaronfrost, indeed that each tools has a different methods of detect Critical CSS. In Critters terms, it's the entire document structure that is rendered. From their readme
When it comes to Critical CSS per route, this is what's being proposed for SSR and pre-rendering. For SSR, critical CSS will be extracted and inlined during runtime (Given a more lightweight and non Webpack plugin of Critters is available.), while for pre-rendering this shouldn't impact much the build time due to the parallel nature of the build.
@kevinfarrugia, I think you meant that the CSS file will not be downloaded when using an invalid media. While the article from That being said, It does look like using |
The Scully team is going to build a post-render plugin that does this for those who care. We will provide it as a built in plugin so that you don't have to add it separately to your package.json. Puppeteer has this info ALREADY available for each page. So we can essentially get this info for free. Soon Scully is going to add Universal pre-rendering as well. We will be interested to see what is decided here to see how we can add that support into Scully-universal as well. |
We're using penthouse/critical to generate critical CSS for each route, then inject this CSS in the universal server at the moment. It's quite slow and sometimes just doesn't work. One really performant solution I tried is to include the critical generation into the app itself (i.e. the script that penthouse injects to find selectors that apply) and send the generated page back to puppeteer, then navigate to a new route from within the same app. Essentially this prevents having to load the whole app for each page you want to render critical CSS for. Unfortunately it doesn't work since the nghost attributes are different on client and server. This one was easy to fix, but they are also different between page loads. If you could give components a unique ID at AOT compile that would make this approach possible. |
@kamshak, that was one of the reasons why we are proposing to use Critters which doesn't use Puppeteer or similar headless browsers solutions. In general App-Shell and Universal serve different purposes and typically when using Universal the use-case where you want to use an App-Shell is together with Service Workers. This is because the Universal response will always override the contents of the |
This would be a great addition to Angular CLI.
I vote for opt-out.
yes. Is there any metric collected by Angular CLI Analytics that can help to make a more informed decision about threshold?
yes |
A vote for opt-out here. If the feature is added we can choose to adopt it, or not :) I wrote about relying on global (1 level specific) styles for all elements within your application. This keeps component styles small and also at single levels of specificity for easy override... and more. If the CSS blocks I mention are broken out to load as their own files and only strategically loaded on pages which require them then CSS weight and FOUC could be even further optimized and component weight reduced also.
Consider a prefetching strategy for broken up global stylesAlso, combining the proposed
|
I think It should be an opt-out option to ease the way for those who are starting with the framework and who do not understand the subject very well since this functionality if it comes by default, would speed up the app built time and solve this problem in bundle sizes and who have more knowledge and who need to disable this option for some reason, allow an option to disable this behavior would be fine. I think that considering a threshold on each specific project should be analyzed and based on the average of the applications that are usually made with Angular, to say a measure of this threshold would be totally unwise, so I agree with @felipeplets that this information who's better than the analytics collected by Angular and Google itself to make this decision. But yes, I think bundle budgets should be added to both new and existing applications. Come on, this is highly cool. |
This concludes the 2 week commenting period for our RFC. I'd like to thank everyone for their time reading through this and providing valuable feedback. We at the Angular team really appreciate this. To wrap this up, the main takeaways are:
|
Authors: Alan Agius (@alan-agius4)
Status: Closed
Closing Date: 2020-09-22
Summary
We’re proposing to eliminate render blocking requests loading by:
At the moment there is no easy or streamlined way to identify, extract or inline critical CSS in Angular Universal applications. We’re proposing to offer an out-of-the-box solution with little or no configuration needed.
Motivation
CSS files are render-blocking because the browser must download and parse these files before starting to render the page. This makes CSS files a bottleneck when they are large or when having poor or limited network connectivity. Each of these files will result in a penalty on the Performance Score of your application #17966.
We can reduce this render-blocking time and at the same time improve the first contentful paint (FCP) by extracting and inlining the critical CSS and loading the CSS files asynchronously.
Proposal
Load CSS files asynchronously
In most cases JavaScript bundles will take a longer time to download, parse than for Angular to bootstrap and start rendering the first component thus the chance of having Flash of unstyled content (FOUC) is relatively low.
We are proposing to introduce an experimental async CSS loading use the “media” technique which can be opted-in/out via an option angular.json
Before
After
CSS files budgets
CSS files are great for code-sharing, but other than that they are a bottleneck to achieving great performance. Two of the main reasons for this is that they are render blocking and might contain dead-rules.
Since a CSS file is not strictly associated with the components loaded on the page, a number of CSS declarations that get downloaded and parsed will remain unused by the view that was rendered.
Having render blocking and/or dead-rules will cause performance score penalties in Lighthouse.
We propose to add two new bundle budgets allStyle and anyStyle:
Inline Google Fonts and Icons
We are proposing to introduce an experimental optimization for fonts which can be opted-in/out via an option in angular.json.
During build time we will parse the index.html, download the content of stylesheets originating from
https://fonts.googleapis.com/…
and inline their content.This eliminates the extra round trip that the browser has to make to fetch the font declarations, which improves LCP, reduces FOUC, and also unlike other approaches doesn't prohibit Angular applications from taking full advantage of
font-display: optional
.Before
After
In case the application needs to support Internet Explorer 11 which will be determined via the browserslist configuration, the
woff1
definition of the font will also be inlined.Extract Critical CSS
The most generic which requires zero to no configuration from the developer is to inline critical CSS as a post-rendering step using existing projects such as: penthouse, critters and critical.
The above mentioned tools take different approaches to extract critical CSS. The approach that Critters uses is the best fit for our use cases. The main reason for this is that Critters doesn’t use a headless browser but uses a JavaScript DOM (JSDOM) to render the page content which makes it faster compared to the other tools and hence it makes it valuable to be used as a build time and runtime option. The main trade-off of this is that it doesn't predict the viewport and inlines all the CSS declarations used by the document.
SSR
In Angular Universal SSR we cannot use Critters directly because this is a Webpack plugin and rendering of Angular Universal pages for both dynamic and static applications happens outside of the Webpack build. Therefore, a more decoupled version of Critters core functionality would be needed.
We’ll run Critters during runtime. When a request hits the server and the Angular SSR page is rendered, we will run Critters as a post-rendering phase to extract the critical CSS and inline in the final HTML response.
App-Shell & Pre-rendering
As a post-rendering phase during build time, we’ll run Critters to extract the critical CSS of the rendered page and inline the contents in the HTML document.
CSR
In Angular CSR, we cannot extract and inline CSS because we are unable to run it in a Node.Js environment, However, it is common for CSP’s to have a custom loading experience outside of Angular context defined in the index.html file. Therefore, we are proposing to extract and inline CSS for this use case to reduce the risk of FOUC even further.
Alternatives
Below are some alternatives that we have considered but deemed less useful / less feasible compared to the main proposal.
Annotating critical CSS
Annotating critical CSS with a comment and tools such as postcss-critical-split will extract these into a separate file which can later be inlined.
The drawback of this is that It will be up to the developer to determine which CSS declarations are critical or not. Developers will also not be able to annotate critical styles which are not part of the application such as when depending on a vendored UI framework library such as Material, Bootstrap etc...
Hence this approach is more complex, has a bigger learning curve and is error prone.
Using headless browsers based extractors
Penthouse is a critical CSS extractor and can do so for non SSR’d applications. This is because under the hood it uses puppeteer to generate the critical CSS.
The main drawback of this is that this approach will be different from what’s proposed for Angular Universal and is slower.
Include CSS files in app root component
Another approach would be to include the global styles in the app root component.
The main drawback of this is that the entire contents of styles.css will be inlined in the HTML page when using Angular Universal or App shell.
DNS-Prefetch and Preconnect Hints
Add dns-prefetch and preconnect hints for for
https://fonts.gstatic.com
to initiate DNS resolution and a connection.Additional Resources
Open Questions
The text was updated successfully, but these errors were encountered: