My theme uses Bootstrap and I noticed I cannot change the library load order to appear before any module CSS libraries, no matter the group I specified (base, component, theme, etc.) - even if specified manually (`group: -200`). I tracked this down to core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php:165:
$options['group'] = $extension_type == 'theme' ? CSS_AGGREGATE_THEME : CSS_AGGREGATE_DEFAULT;
This essentially forces any theme CSS libraries into the CSS_AGGREGATE_THEME group (100) and overwrites/ignores any specified SMACCS group settings, e.g. 'base' (my_theme.libraries.yml):
bootstrap:
css:
base:
libraries/bootstrap/bootstrap.min.css: {}
And also overwrites setting group specified like this:
bootstrap:
css:
base:
libraries/bootstrap/bootstrap.min.css: { group: -200 }
So this doesn't function according to 'group' property described in the docs: Adding Stylesheets CSS and Javascript JS to a Drupal Theme which specifies the "rarely used" group setting causes assets to be aggregated per group - because it's overwritten in the case of themes and modules.
Even if my module (e.g. Faculty Cards) libraries file specifies 'css: theme' grouping, it still appears in the HTML loaded before the theme bootstrap library (which specifies 'base'):
faculty-cards:
css:
theme:
css/faculty-cards.css: {}
The line in LibraryDiscoveryParser means that module CSS libraries are always loaded before theme CSS libraries regardless of SMACCS category (base, layout, component, theme, etc.) specified in respective files. Which means that greater CSS specificity or even worse !important needs to be used in module CSS files to override a theme that uses Bootstrap.
If I comment out the affected line in LibraryDiscoveryParser, ordering is as I would expect and group settings are respected.
| Comment | File | Size | Author |
|---|---|---|---|
| #21 | 9294.patch | 6 KB | woldtwerk |
Issue fork drupal-3046636
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
dawitti commentedI can confirm no matter what weight or group you set in a theme library module css is always loaded before theme css.
This essentially makes it impossible to follow established performance best practices like loading web fonts before any other css.
I'm aware of the workaround of adding web fonts in a module - but there should be a clean and easy way to handle that.
Am I not missing anything here?
Comment #7
pmichelazzoThe problem still remain open and I'm facing the same situation reported here. No matter which weight you set, the theme always override the module's library.
Comment #10
jonmcl commentedVery frustrating to find out that using the "theme" category on a module's library, with a group value that is higher than CSS_AGGREGATE_THEME (100), does not allow the module's CSS to come after the theme's CSS in the DOM. Impossible to do simple CSS refinements when the module wants to do them.
I am glad I am not alone in this :)
For what it may be worth, here is my implementation of hook_css_alter that re-applies the group property from the library data:
It is probably not very efficient, but should only run when needed and then the alterations are cached.
Comment #12
jwilson3Agree this is fairly frustrating and more than slightly nonsensical that there is no way to define a "base" SMACSS in a theme and make it appear first before higher SMACSS groups defined by core or contrib modules.
Even stable9 theme in Drupal core suffers from this exact issue out of the box:
Given the following three examples...
stable9.libraries.yml:
core.libraries.yml:
system.libraries.yml:
One would expect normalize.css to be the first CSS file in the HTML, but in fact, it shows up last.
Expected result:
Actual result:
Comment #13
jwilson3I've mentioned this issue in docs in two places:
Comment #15
woldtwerk commentedI think we have 2 Issues here:
1: group key is overwritten by LibraryDiscoveryParser (commit)
2: we can't disable grouping. CssCollectionGrouper only checks for preprocess key. Thus you can't use preprocess, but opt out of grouping. (commit)
For 2 it's debatable if 0 or -1 would be preferred.
Let me know what you think
fork
patch
Comment #17
vensiresI am not yet 100% this issue is related but maybe the problem described in #3476650: Library gin_custom_css should depend on gin/gin for higher priority is something we should take into account here too.
Comment #19
arisenI face the same issue. I need to load certain libraries from the modules after the theme libraries. The logic in the LibraryDiscoveryParser completely overrides any group settings done in the library. I feel it should be conditional rather(only applied if not set already).
There is no way to work around this other writing a hook_css_alter() to set the group again for the required libraries.
Comment #20
woldtwerk commentedComment #21
woldtwerk commented11.2 patch.
dunno why the mr diff doesn't work :(
Comment #22
catchI think this is a duplicate of the much older #1924522: Remove separate CSS_AGGREGATE_THEME aggregate file
Comment #23
jwilson3From a cursory review of that issue and this one, they do fundamentally look to be duplicates. The only difference being the Problem/Motivation express two different symptoms of same underlying problem with the CSS_AGGREGATE_THEME group:
#3046636 motivation is the artificial number of aggregation files.
This issue's motivation is the artificial distinction preventing fine-grain control over cascade order of theme stylesheets (to be lower than module/system stylesheets).
Comment #24
catchFor background I ran into this due to #3565258: Support library-specific aggregates. That issue would benefit from less arbitrary CSS groups too. Also my comments from #3046636: LibraryDiscoveryParser overwrites theme CSS group ordering are outdated due to a lot of changes in aggregation logic since.
Comment #26
mlncn commentedSitting at MidCamp's hackathon/unconference and Frank M. Taylor is pitching Drupal providing a CSS Layer Manager, using native CSS layers: https://www.w3.org/TR/css-cascade-5/#layering
That would solve this problem, and provide much more control over when and how you override (or not). The SMACSS 'layers' like base, component (what we call "Module" i guess), theme would become CSS layers. Doing this, the order does not matter as long as everything gets put in a layer. If core maps its version of SMACSS groups to layers, we could be certain that all CSS is going into a CSS layer. Outside of core, people could be given the ability to move layers around and disable layers, much more easily than than the current matching-filename approach.
(Avoiding 'module' and 'theme' layers in these examples because that is just too confusing.)
base.a_module
base.a_theme
layout.a_module
layout.a_theme
Will not matter what order these are output, as Drupal would set @layer rules such that
baselayer comes beforelayoutlayer.Comment #27
catchThat sounds encouraging. Sounds like the main thing we'd need to do is define some layers in core CSS and actually use them in our own CSS?
Cross linking #1945262: Introduce "before" and "after" for conditional ordering in library definitions which is for getting rid of individual weights. After that issue and #3467488: [PP-1] Deprecate support for per-file weight in libraries API we'd still need to handle groups, but then this issue might mean we can remove groups as well, and that would allow us to significantly improve and simplify CSS aggregation in core.
Comment #28
mlncn commented(Had briefly edited the above to go from
base.a_modulefor a module nameda_moduletobase.module.a_module, so we have explicit sub-layers for modules and themes and then sub-sub-layer in the namespace of each module, but Drupal's layer manager could keep track of that— if a site administrator wanted to put a module after a theme, they should be able to do that (contrib functionality) and Drupal core will know which libraries come from a module and which from a theme, so could enforce the default ordering— as well as the handle the #1945262: Introduce "before" and "after" for conditional ordering in library definitions request, simply So libraries can slot their CSS into any SMACSS type layer (or define their own, but managing that is out of scope), but rather than a module sub-layer that loses out to a theme sub-layer in the same SMACSS layer, we keep everything flat in the same SMACSS layer and manage the weights by default.)So every library slots CSS into a SMACSS scope dot project name (for example,
component.example_project) and Drupal outputs in one place, once, before all of the CSS, the list of layers in the proper order.The fact that Drupal breaks up CSS so that it only shows up on certain pages still works fine, the layer.projectname would still be ordered correctly if it is used multiple times. Still, the full layer name would include the library name, such as
layer.project__library, to support re-ordering at the library level not only the project level. (In layers, dot-notation signifies nesting that cannot be re-ordered outside of that nesting.) https://codepen.io/paceaux/pen/RNoobYjComment #29
jwilson3I like this idea a lot.
My experience with layers is limited, but I see a few potential gotchas:
layer: component. Automatically wrapping existing assets in@layerseems risky, especially for files containing@import,@charset, or existing/arbitrary@layerrules.component,theme, orutilitiescould accidentally merge contrib/vendor CSS into core’s taxonomy. Core-owned layers probably needs namespacing egdrupal.reset,drupal.componentetc, and contrib/vendor layers may need separate namespacing. Undoubtedly, there needs to be a modifiable layer registration system.Possible migration path:
layer:metadata per CSS asset in *.libraries.yml. Presence oflayer:would override the SMACSS-derived default.Comment #30
catchI'm not sure how we'd support this in libraries.yml - we can wrap CSS when it's aggregated, but we also support switching aggregation off and in that case there is no CSS processing at all.
We'd need to make it so that aggregation still does preprocessing to make that work e.g. you get an 'aggregate' of one file each. That's doable but we've not had to do it before now.
But also why could we not define layers in a CSS file and use a convention rather than putting it in the libraries API as such?
Comment #31
jwilson3if aggregation is off the only way I know that will work is to use:
Instead of :
In this scenario, I think layer MUST be defined outside of the CSS file itself, eg in libraries.yml
Btw, one marginal side benefit we get from that is the ability to put more relevant (eg above-the-fold) components earlier in the DOM since we're no longer bound to
source order == cascade order. I haven't thought through how that will help real PROD scenarios yet where aggregation is turned on (but maybe layers == cascade, group == dom priority, weight == bridge to fine tune cascade within group?). This could pave the way for some level of next-gen poorman's critical css approach, dunno.Comment #32
jwilson3You totally could do that; but layer name discovery suddenly becomes harder (need to parse CSS instead of YML). Maybe the workaround is to introduce something in theme.info.yml to let users register the layers they're using (not as DRY as pulling straight from the CSS files or libraries.yml).
First party themes are easier to figure out. But thinking about the third party library CSS and legacy module CSS (things generally outside themers' control), it seems we'd need a standard approach to handle the following scenarios:
My thinking was that scenario #2 was where libraries.yml would come into play, utilizing the wrapper approach in my previous comment #31.
Comment #33
catch#31 we would probably need to add CSP support to but if that works it seems OK! Aggregation off is only really for development so we don't need it to look nice.
edit A: for external CSS we could add a layers opt-out flag to library definitions like layer: FALSE and skip the wrapping if that's set? Might be enough.
edit B: on poor man's critical CSS. Big pipe already renders non-placeholder CSS in the header and any other CSS via loadjs.
Big pipe non-js placeholders render the CSS link tags inline. #2989324: Allow CSS to be added at end of page by rendering assets with placeholders is open to explore whether we could or should do that more broadly. Also some discussion on #3499829: Support inlining critical CSS for faster core web vitals.
CSS layers would definitely help to make those approaches more reliable, but there are some unanswered questions about whether they're a good idea or not outside the Big Pipe approach which has more clearly defined trade-offs - e.g. recalculations, repaints etc. can all get worse, some browsers might block header CSS from rendering if they find another stylesheet added later in the page quick enough.
However where I think we can definitely see gains is #3563782: [PP-1] Use early hints for CSS combined with #3565258: Support library-specific aggregates and related issues and those issues would be directly benefited by this one.
On every request (except internal page cache), we can calculate all of the aggregates for non-placeholdered attachments and send early hints for those, then we can render placeholders and generate aggregates for any additional libraries after that. If we do that per-placeholder, we could also send early hints for those before the other placeholders finish rendering too (which might be all the CSS on the page, some placeholders won't add any).
Where this issue will help a lot is making grouping of CSS aggregates a lot easier (no more weights and category), so that we can have much, much higher hit rates and less duplication of CSS between different aggregates generated for different pages.
On slower pages (cold and slightly warm caches) there is the chance for most or all of the CSS to arrive at the browser before any HTML does. It won't make any difference for pages served from the internal page cache or a CDN because for those the HTML is served more or less instantly anyway - but the big thing for me is speeding up the slower requests, fast requests are already fast.