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.

CommentFileSizeAuthor
#21 9294.patch6 KBwoldtwerk

Issue fork drupal-3046636

Command icon 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

manafire created an issue. See original summary.

dawitti’s picture

I 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?

Version: 8.6.10 » 8.6.x-dev

Core issues are now filed against the dev versions where changes will be made. Document the specific release you are using in your issue comment. More information about choosing a version.

Version: 8.6.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Branches prior to 8.8.x are not supported, and Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev
pmichelazzo’s picture

The 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.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

jonmcl’s picture

Very 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:

function modulename_css_alter(&$css, \Drupal\Core\Asset\AttachedAssetsInterface $assets) {

  foreach($assets->getLibraries() as $name) {
    [$module, $id] = explode('/', $name);
    if ($module && $id) {
      $library = \Drupal::service('library.discovery')->getLibraryByName($module, $id);
      foreach($library['css'] ?? [] as $definition) {
        if (isset($definition['group']) && $path = $definition['data']) {
          if (isset($css[$path])) {
            $css[$path]['group'] = $definition['group'];
          }
        }
      }
    }
  }
}

It is probably not very efficient, but should only run when needed and then the alterations are cached.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

jwilson3’s picture

Agree 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:

 css:
    base:
      css/core/assets/vendor/normalize-css/normalize.css: { weight: -20 }

core.libraries.yml:

internal.jquery_ui:
  css:
    component:
      assets/vendor/jquery.ui/themes/base/core.css: { weight: -11.8 }
    theme:
      assets/vendor/jquery.ui/themes/base/theme.css: { weight: -11.8 }

system.libraries.yml:

base:
  css:
    component:
      css/components/ajax-progress.module.css: { weight: -10 }

One would expect normalize.css to be the first CSS file in the HTML, but in fact, it shows up last.

Expected result:

/core/themes/stable9/css/core/assets/vendor/normalize-css/normalize.css
/core/assets/vendor/jquery.ui/themes/base/core.css
/core/themes/stable9/css/system/components/ajax-progress.module.css
/core/assets/vendor/jquery.ui/themes/base/theme.css

Actual result:

/core/assets/vendor/jquery.ui/themes/base/core.css
/core/themes/stable9/css/system/components/ajax-progress.module.css
/core/assets/vendor/jquery.ui/themes/base/theme.css
/core/themes/stable9/css/core/assets/vendor/normalize-css/normalize.css
jwilson3’s picture

woldtwerk made their first commit to this issue’s fork.

woldtwerk’s picture

I 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

vensires’s picture

I 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.

vinmayiswamy made their first commit to this issue’s fork.

arisen’s picture

Status: Active » Needs work

I 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.

woldtwerk’s picture

Issue tags: +Bug Smash Initiative
woldtwerk’s picture

StatusFileSize
new6 KB

11.2 patch.
dunno why the mr diff doesn't work :(

catch’s picture

I think this is a duplicate of the much older #1924522: Remove separate CSS_AGGREGATE_THEME aggregate file

jwilson3’s picture

From 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).

catch’s picture

For 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.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

mlncn’s picture

Sitting 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 base layer comes before layout layer.

catch’s picture

That 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.

mlncn’s picture

(Had briefly edited the above to go from base.a_module for a module named a_module to base.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/RNoobYj

jwilson3’s picture

I like this idea a lot.

My experience with layers is limited, but I see a few potential gotchas:

  1. Unlayered CSS outranks all layered normal CSS. If core maps SMACSS groups to layers while contrib, theme, or vendor CSS remains unlayered, that legacy CSS may unexpectedly override the layered cascade regardless of group/weight intent. As a contrived example: if Drupal’s reset.css is placed in a layer, but contrib’s normalize.css remains unlayered, normalize.css may win even when Drupal’s layer order intends reset.css to come first.
  2. Layer assignment probably needs to be explicit opt-in metadata in *.libraries.yml, e.g. per-file layer: component. Automatically wrapping existing assets in @layer seems risky, especially for files containing @import, @charset, or existing/arbitrary @layer rules.
  3. Layer naming needs a policy. First-declared layer order wins, and later matching names merge into the existing layer. Generic names like component, theme, or utilities could accidentally merge contrib/vendor CSS into core’s taxonomy. Core-owned layers probably needs namespacing eg drupal.reset, drupal.component etc, and contrib/vendor layers may need separate namespacing. Undoubtedly, there needs to be a modifiable layer registration system.
  4. There is still the migration problem: fixing the current SMACSS ordering issue while preserving legacy module/theme cascade assumptions may be difficult or impossible to do perfectly.

Possible migration path:

  • Define a core layer order and namespace, with a way for themes/modules to extend or alter it.
  • Add optional layer: metadata per CSS asset in *.libraries.yml. Presence of layer: would override the SMACSS-derived default.
  • Define whether non-core layer names can be arbitrary, namespaced, or must be registered so order is deterministic.
  • Move core CSS into layers.
  • Leave unannotated legacy CSS unlayered, falling back to existing SMACSS/group/weight behavior until modules, themes, and vendor libraries opt in.
  • Document this very clearly and expect some contrib/vendor-library rough edges during migration.
catch’s picture

I'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?

jwilson3’s picture

if aggregation is off the only way I know that will work is to use:

<style>@layer drupal.reset { @import url('/path/to/reset.css'); }</style>
<style>@layer drupal.component { @import url('/path/to/component.css'); }</style>
<style>@layer drupal.theme { @import url('/path/to/theme.css'); }</style>

Instead of :

<link rel="stylesheet" src="/path/to/reset.css">
<link rel="stylesheet" src="/path/to/component.css">
<link rel="stylesheet" src="/path/to/theme.css">

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.

jwilson3’s picture

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?

You 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:

  1. libraries/modules/base-themes with arbitrary layer in css files that don't (yet) follow our conventions,
  2. libraries/modules/base-themes with no layers at all in the css.

My thinking was that scenario #2 was where libraries.yml would come into play, utilizing the wrapper approach in my previous comment #31.

catch’s picture

#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.