Split from #2391025: Add support for inline JS/CSS with #attached don't bother reading any comments till after comment #20
Problem/Motivation
Adding inline CSS is cumbersome and isn't supported by the asset library system via #attached. In order to avoid the flash of unstyled content, it is recommended to attach all ABF (above the fold) styling as inline styling in the head tag. With our current library system this is difficult/near impossible. It is easy for a theme to add inline css into the twig templates, but a module cannot depend on the order of the css included externally. This makes for inconsistent css rule specificity.
Proposed resolution
Allow for all aggregated/cached css to be added inline in the head. This is the recommended strategy for Mobile First Development and avoiding the flash of unstyled content.
Remaining tasks
TBD
User interface changes
TBD
API changes
TBD
Data model changes
TBD
| Comment | File | Size | Author |
|---|---|---|---|
| #44 | core-allow-css-inline-with-libraries_2891967_43.patch | 3.85 KB | chriskinch |
| #42 | core-allow-css-inline-with-libraries_2891967_42.patch | 3.64 KB | frob |
Comments
Comment #2
andypostbtw In case of inline it should go always first, so no ordering needed
Comment #3
hass commentedComment #4
frob@hass, if this is a duplicate, please post what issue this is a duplicate of. This issue is about getting all aggregated CSS listed inline in the head as a best-practice for mobile first.
Comment #5
frob@andypost, can you elaborate? Why not put the inline CSS after the included external css files.
Comment #6
hass commented@frob: no. This issue is not about adding all css as inline.
This is about allowing conditional /dynamic inline css code if a module/theme may need it for rendering reasons. Currently only possible without #attached dependecies and incorrect ordering inside head.
This is part of #2391025: Add support for inline JS/CSS with #attached
Comment #7
frob@hass, I created the issue, I wrote the IS. I know what this issue is about. Please stop closing it.
From the Issue Summary:
Comment #8
droplet commentedWhat are we going to support or not support?
I'd like to hear 2 feedback:
1. What if Performance doesn't matter
2. What if Performance does matter
Here's a possible pattern:
Comment #9
frob@droplet, I believe #8 is a crosspost from #2391025: Add support for inline JS/CSS with #attached. There isn't really a pattern to discuss and we should ignore any JS issues here.
This issue is a feature request for streamlining the ability to get all the aggregated CSS into the head. @andypost recommends that it should always go first. However in the related issue they brought up wanting the inline CSS to overwrite the external CSS.
I really don't think order matters so much. I would expect that putting the inline CSS first would make it so JS parsing won't block the rendering of the page. But I haven't looked into if that is an issue or not.
Comment #10
hass commentedFrob: you linked to this case here and said is is the inline part. What you are looking for is a totally different thing. Totally unrelated to the other issue that just tries to allow developers to add some important selectors to head and not the full css. As the title is highjacking the other issue, i change the title, too.
Comment #11
frob@hass, putting the CSS inline in the head as a strategy was brought up in the other issue. It is related. I linked to this issue from that one for people who think we are discussing flash of unstyled content.
The title should really reflect the flash of unstyled content, as that is what this is supposed to fix.
Comment #12
hass commentedYou are highjacking the other case. Please stay on topic. This named topic is handled in the other case. You are permanently changing your comments. If you want to change all css files to inline code you may better create a contrib module. If you want to add inline css we fix this in the other case. Please do not change the status again or change the title to something different.
Comment #13
frob@hass, I have no idea what you are talking about. Please stop closing this issue. It is a legitimate feature request.
Comment #14
droplet commentedI also don't quite understand this issue.
"aggregated CSS into the head." and "#attach" are 2 different concepts to me.
Can I say this issue is going to get this thing workable via API:
A:
OR
B: (@hass and @droplet wanted. This is similiar to #2391025: Add support for inline JS/CSS with #attached I think )
Comment #15
hass commentedAh, that is a good topic for the case here - "aggregate CSS into the head as inline". At least this is what frob explained.
#attached is different and A and B is handled in #2391025: Add support for inline JS/CSS with #attached.
Comment #16
frobThe thought is just that all CSS is CSS. The only way CSS can really be inline in the body is to use the style attribute. I don't think anyone really wants that (but I could be mistaken). The use of #attached to add CSS in render arrays in the same way it is done for all other CSS. The reason for specifying #attached is #attached is how programmatic CSS is added through the render array. This is the primary way CSS is should be added by modules. Themes already have access to the html.twig and can put inline CSS in easily. The point of this issue is to put themes and modules on level ground with regard to inline CSS inclusion.
A and B are not handled in #2391025: Add support for inline JS/CSS with #attached. Not even close. If I was trying to hijack that issue then I would have continued to remove CSS from the title and IS of that issue.
@hass, If you still don't understand what this issue is about then please just leave it alone.
Comment #17
hass commentedFrob: you really have not understood the case #2391025: Add support for inline JS/CSS with #attached. This is the ground api work to be able to implement A and B. Maybe you need to read the case again.
If you talk about converting the following code from:
To this non-cachable code that bloat the webpages (let's name ist C):
Than you may need this case here.
But A and B are covered in the other case and allow a developer to stop the flash of content. If you think not, you are clearly wrong and have not understood the other issue. Close the case here if you want to implement A and B here.
The flash of content will be fixed by adding small pieces of css like body width or background colors as inline css only. This does not mean every piece of css. All this requires case #2391025: Add support for inline JS/CSS with #attached.
Comment #18
frobNo @hass, #2391025: Add support for inline JS/CSS with #attached only allows for CSS to be added inline in the head with respect to the order of CSS libraries. That has the byproduct of allowing a developer to put CSS inline in the head that respects the order of the CSS libraries dependencies. Any developer that does this would still have to alter every library and attached CSS to be put inline in the head.
I am not even sure CSS that is attached as a css file can be made to be inline CSS. That is what I am requesting here. Make it so all CSS regardless of how it was initially attached to the renderer, inline or file based, to be made to be put inline in head with full support of the library api. Basically, all CSS regardless of how it was initially included or attached should be able to be put into the head inline without having to duplicate the CSS.
Comment #19
droplet commentedSo @frob wanted C in #17.
That meant Comment #2 until my this comment are wasting others time to read. We should open a new issue or note it in IS.
Usually, we talked about "Critical CSS" (Few KB of above fold CSS) rather than inlined ALL CSS ( big CSS like 300KB)
If we talked about "Critical CSS", I don't think Drupal is possible to do so. We have to run nodejs to capture the "Critical CSS" and adding via #attached
Comment #20
droplet commentedI think it's a better title:
Comment #21
frob@droplet, this issue is about what has always been in the issue summary. If hass wants to read the issue summary instead of vindictively closing this issue over and over again, then he can stop wasting everyone's time.
I have updated the IS to direct people away from reading the comments. Though at this point I doubt anyone would want to get involved in this.
Comment #22
hass commentedThere is no simple checkbox in core, but it can be done easily in contrib already.
Please see https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Asset%21C... . If you create a new asset that combines all css files into inline (it is simply what core already does) except creating a combined css file. You just do not add a file, you make it inline. This requires #2391025: Add support for inline JS/CSS with #attached very first. You can already do this dirty with html_head.
However... nobody want to add 300kb into the body of a webpage as it disables all caching features of webbrowsers and only makes your website ultra slow on mobile as you always need to transfer the webcontent (~50kb) and this ~300kb inline css with every request. Do you really like to slow down your website? I guess not.
Guess you only like to prevent the flash effect... and this may require just 500 byte inline code or less.
Comment #23
frob@hass, I think you're right. This issue might depend on #2391025: Add support for inline JS/CSS with #attached depending on how that issue is resolved. But this expands on that issue.
I am not advocating for a single checkbox that forces all css into the head. As you said:
But making this a core feature, maybe one that is supported with an alter_hook or a yaml file. Maybe we can work through some possible APIs.
For this hypothetical let's consider the navbar module. It is in core and its placement is such that it will always be above the fold. This module could flag it's css as being inline. But, (this is hypothetical) it depends on a css library from another module (I am not sure that is true). That module is marked as a css dependency and is thus added inline as well. This could be done in library definition in the yaml or as a part of #attached inline.
or
As a part of building a theme, we know that we will be using a responsive menu module (any of them should work for this example). We would like the css that the module provides to be placed inline in the head, in the info.yml we should be able to register it as a dependency and have it automatically placed in the head.
Comment #24
hass commentedOMG. Please spend some time with core api. This is already possible with library api. Nothing extra need to be implemented. Search for library override.
Navbar depends on underscore, backbone, modernizr. And again with #2391025: Add support for inline JS/CSS with #attached the developer of navbar has the ability to add all navbar css inline if he really want. No need for any extra case.
And again a theme can use library override or #2391025: Add support for inline JS/CSS with #attached to add inline js into head.
Why is it so difficult to understand that we already have all these api's??? We only need #2391025: Add support for inline JS/CSS with #attached. Please close these useless case yourself now.
Comment #25
frobDid that and found: https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Asset%21L...
Which doesn't really help. I will give you the benifit of the doubt and expect that you meant to tell me to search for library extend. Which does a similar thing: https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Asset%21L...
But, I don't see anywhere documented that allows one library definition of an inline css library to set another css library as a dependency and thus automatically setting that dependency as also being inline. If one exists please let me know because I would love to see it. We can ignore the order of the css files for now as that will be taken care of by #2391025: Add support for inline JS/CSS with #attached when that has a working patch.
Comment #26
frobExpanded the title a bit to be clear to include the library CSS too.
Comment #27
hass commentedSee #2642122: Overriding already overridden libraries-override requires knowledge of previous libraries-overrides how library override works. It will work the same way for inline once inline is back.
No idea what you are trying here. In #23 you said all inline is not the idea. Now you changed title to do it again. It looks clearly that you are not aware what core already can or what this case is about.
Comment #28
frobSome or all would be up to the user via configuration or #attached. Again, if core can do this then show me an example and change this from a feature request to a support request.
The main idea is to make it chain to dependencies automatically. If you are genuinely trying to figure out what this issue is about, can you please tone down the aggression? If you think the IS is unclear, then ask questions so we can clarify it.
Comment #29
droplet commented@frob
Is it what this issue try to do:
It will depend on #2391025: Add support for inline JS/CSS with #attached I think. We still do not what is the best strategy to add inline CSS/JS into HEAD. Introduce one or two random Twig tag in HEAD section will make the API more messed in the future.
Comment #30
hass commentedThis is the third time you change the goal of your case.
Dependencies are an existing feature of library api in Drupal Core 8. Example code:
Comment #31
frob@hass, that is js do it with css.
The Issue Summary is the same and it says what I have been trying to clarify for you through the comments. The intent has always been the same, if the IS is unclear, then let's fix that.
Comment #33
chriskinch commentedI got you. Does what @droplet says in #29.
Originally posed in https://www.drupal.org/node/2391025#comment-11829091 but there was still a lot of unresolved discussion about JS.
I think there is more agreement about CSS. (Current thread arguments considered).
What do you think?
Comment #34
chriskinch commentedComment #35
hass commentedYou are in the wrong case.
Comment #36
chriskinch commented@hass Could we potentially split the issues and have this one for CSS and https://www.drupal.org/node/2391025 for JS only? The JS discussion seems to be holding up what I think most agree is not an issue for CSS.
Comment #37
hass commentedThis is not needed. The code will be nearly the same - at least logic wise. If you can come up with the CSS part we can easily adapt the JS code.
Comment #38
frob@hass, I have not reviewed it, but it looks like they have a patch. Maybe they have come up with the CSS part that you can easily addapt to the JS code part. Take a look!
Comment #39
chriskinch commentedThe patch #33 does what @droplet mentions if you want to review? Not sure if it's what we need but it's being doing the trick for me.
Comment #40
dsnopekI can't comment to the overall functionality, but I took a peek at the patch and noticed this:
Error message is no longer correct - it appears both 'file' and 'inline' are allowed
Comment #41
hass commentedComment #42
frobHere is a patch that fixes that Exception. However, I still need to actually test the patch. I am in the middle of other stuff right now. It would be easier to review if we had a few test cases.
Comment #44
chriskinch commentedI've rerolled Frobs patch from #42 (with the exception fix) for Drupal 8.4.x. Should apply now.
Will try to find some time to work on some test cases as well as adding support for programmatic #attach
Comment #47
gappleAs a site builder trying to implement Content Security Policy and not allow any inline scripts, I don't want an arbitrary module to be able to break the expectation that all CSS is included via
linktags. There's also the risk that functionality would break if I were to disable the inlining of CSS, if the module author wrote with the expectation that certain CSS was always going to be inlined.I also don't think that inlining as implemented here has any benefit to rendering performance and time to first paint.
My understanding is that the browser will pause rendering the page when any stylesheet is encountered until the CSS is parsed (which requires downloading first for external assets). The portion of CSS still included via
linktag in the head of the document will continue to block the first render of the page until it's downloaded and parsed, eliminating the benefit of extracting a portion of the CSS from that file.The intent of inlining is to reduce the number of external assets loaded so that there is less overhead due to multiple requests, but with CSS aggregation the number of requests is already minimal. Inlining even risks increasing the number of aggregated files if an inlined portion of CSS is ordered between two external sheets that could have otherwise been aggregated (though Drupal's aggregation could probably be made smarter to prevent this).
Two ways rendering performance and time to first paint could be improved:
attach_library()(https://jakearchibald.com/2016/link-in-body/#a-simpler-better-way)For HTTP 1.1 the overhead of additional requests versus just including the CSS in aggregated files in the document head would probably vary per site, but this practice would be beneficial over HTTP/2 due to the reduced connection overhead.
This would risk content reflow if the styles would affect elements already rendered on page, but could be beneficial for interactive elements (e.g. the part of menus not visible on page load, modal content...)
Comment #48
hass commented@gapple: Inlining CSS is often / mostly a mobile first optimization. If you do not like it you do not need to use it, but others may care about their strongly growing mobile users.
Comment #49
gappleI understand inlining CSS as a mobile optimization. However, without moving external stylesheets from the head of the page, inlining has no benefit since first paint is blocked until all CSS assets are downloaded and parsed.
Bytes transferred is the same whether the CSS is inline or external (on the first request, at least), so unless all CSS is inlined the number of requests isn't reduced and connection overhead will be the same.
Comment #50
hass commentedMaybe you read https://en.m.wikipedia.org/wiki/Flash_of_unstyled_content first and some other websites that write about this topic. It is never about bytes. It is about beeing able to show/read content before all external css is fully loaded.
Comment #51
gappleThe Wikipedia article is very limited in information relevant to Flash of Unstyled Content. The references it includes are in regards to Internet Explorer 5, Safari as of 2006, and "with the advent of jQuery".
Looking deeper, Firefox treats FOUC as a browser bug and it looks like Chrome does the same.
A much more detailed walk through of browser rendering in Firefox and Safari from 2011: https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/#The_m...
Here is a much newer article on the browser rendering flow: https://bitsofco.de/understanding-the-critical-rendering-path/
And info on Chrome's rendering process, and how loading stylesheets block rendering: https://developers.google.com/web/fundamentals/performance/critical-rend...
The browser will pause rendering until the last stylesheet specified in the page head is downloaded and parsed, so inlining some of the CSS offers no improvement if there are still stylesheets linked in the head of the document.
The rendering performance and time to first paint of the following examples will be equivalent. The pages both require two HTTP requests, and transfer roughly equal bytes of data.
Comment #52
hass commentedThis was only a starting point for You as you seem to have no idea what flash of unstyled content is. There is the definition what is means.
Comment #53
frob@grapple, are you trying to say that the FOUC doesn't exist? I can find numerous articles from the past year about this problem. It dates back to 2001 and is very common. None of what you suggest has any affect on FOUC.
The two prevailing solutions is to use some hacky JS that hides and then shows all the content when the page loads or load necessary (above the fold) CSS inline in the head. The JS option is a bad solution (IMO) because it requires js in-order-to work at all and if done incorrectly, then js could be required to view the site. CSS is the tool that should be used for this.
Here is a good overview of the solution. https://www.i3dthemes.com/how-stop-flash-unstyled-content-on-your-web-pa...
Comment #55
drup16 commentedHave you tried Critical CSS contrib module? This helps to inline CSS and can also load other CSS files (including aggregated ones) using
preloadmethod.In our implementation we did the following. You have to play with it as every site is different.
above the foldCritical CSSmodule to pick up and inline.<body>or other element.Comment #57
gapple@frob in the article you linked it is addressing FOUC occurring because of the use of loadCSS, not basic browser behaviour. Its recommendation to use a block of inline styles for critical CSS is no different than if the same critical CSS was included through a file in regards to the browser's render process (other than the additional network request on first page load).
loadCSS even indicates that it is only intended for CSS not critical to the initial render of the page, and is intended to improve perceived performance because CSS is by default render blocking.
Comment #65
catch@gapple is correct, unless you're doing something tricky with the rest of the CSS files, just making some inline does nothing. Making the entire contents inline is likely to be counter-productive, you wouldn't want everthing inline all the time because then you can't make use of browser caching.
https://www.drupal.org/project/critical_css is handling what this issue wants to do, so this is really a feature request about bringing that functionality into core.
Either way this should be postponed on #2391025: Add support for inline JS/CSS with #attached.
Comment #66
catchActually this is needs more info, between the contrib module and also #2989324: Allow CSS to be added at end of page by rendering assets with placeholders, not clear what's being asked for here that's not covered by other issues.
Comment #67
quietone commentedIt has been two years sine more information was asked for so progress could be made here. Since that information has not been supplied I am closing this issue.
If there is work to do here, then either re-open the issue or open a new issue and reference this one. If the choice is to use this issue then add a comment change make sure to change the issue status to 'Active'.