Problem/Motivation

The current method of adding CSS and JavaScript through libraries assumes that all CSS and JavaScript is reusable, which isn't always the case. Especially CSS is for the most time very dependent on the markup it was first built for. If the markup changes, the CSS should be changed and vice versa.

This creates backwards compatibility issues in core, since themes could override just markup or CSS. If markup or CSS is modified individually and core parts get changed over time, the implementation in the theme is prone to bugs.

Proposed resolution

Allow attaching CSS and JavaScript directly in templates. When themes override the template, they also have to take ownership over the CSS and JavaScript that was attached in the template since it doesn't exist in the library system.

Remaining tasks

User interface changes

-

API changes

New API to attach CSS and JavaScript in templates.

Example:

{{ attach_css('style.css') }}
{{ attach_javascript('javascript.js', ['core/jquery']) }}

Data model changes

Release notes snippet

Issue fork drupal-3050386

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

lauriii created an issue. See original summary.

lauriii’s picture

davidhernandez’s picture

catch’s picture

Our current aggregation strategy is based on libraries, which was a major improvement from the Drupal 7 method and has unblocked a lot of front end performance issues like #956186: Allow AJAX to use GET requests and #1014086: Stampedes and cold cache performance issues with css/js aggregation. Moving back to inclusion of individual files would complicate this a lot.

wim leers’s picture

#4++

It's not clear to me what problem is solved by not using asset libraries. Can you give a concrete example?

lauriii’s picture

It's not clear to me what problem is solved by not using asset libraries.

The root issue we are trying to solve is that we currently provide backwards compatibility for our frontend code (markup and CSS specifically) for a single major release. This isn't good enough since this doesn't go along with the promise that upgrades between major releases will be always easy.

We had some ideas about how we could potentially deprecate HTML classes but that's as far as we could go. The problem is that the backwards compatibility goes further than just classes. For example, in frontend changing the type of an element creates a BC break. How do we deprecate the old element and add a new one? Deprecations would have to happen on the level of the whole template and CSS file which wouldn't be maintainable. This would be similar to if we would be only able to deprecate whole PHP classes.

The competing idea to deprecations was to remove the capability of partially overriding frontend components. This means that if a theme wants to provide custom design for a button, they will have to maintain both input--button.html.twig and button.css. It is unfortunate that this solution adds some maintenance burden to people maintaining themes, but as a return, their themes will continue to work in the next major release.

Can you give a concrete example?

The issue queue has lots of examples of this! Let's look at one (this is specific to Classy, but this is a fairly simple one to understand so I'm using it): #3010558: Unnecessary <strong> element in Umami's form-element template may produce invalid markup.

Some templates we currently have in Classy, wrap form labels as a whole with a <strong> element, which goes against W3C recommendations. The issue proposes to replace the <strong> element with a new HTML class that changes the font-weight to bold.

Now, this might sound a simple task, but we have to take into account that someone could have overridden form-element.html.twig and/or form.css in their theme.

  • form-element.html.twig is overridden without overriding form.css: there's a risk of breaking the design since they could have removed the strong element from the template so that labels are not rendered with font-weight bold.
  • form.css is overridden without overriding form-element.html.twig: their design will be likely broken since they wouldn't receive the newly added CSS rule and the label would not be rendered with font-weight bold.
  • form-element.html.twig and form.css have been overrided: there's no problem, theme has full ownership of this element since neither our change to the CSS or the template will affect their rendered output.

I hope this brings some clarity to what the problem we are trying to solve is. We are also still open to alternative ideas ✌️

wim leers’s picture

Thanks, that helps a lot! I now see a concrete use case, and see the ways it could break.

But as far as I can tell, there's no requirement to have a theme override only a CSS file; we could just as well demand that the theme overrides the entire asset library associated with that template. That way, we can continue to rely just on asset libraries, and not go back to the world of both asset libraries and individual assets.

I suspect this is less appealing because this further increases the amount of work for a theme developer?

davidhernandez’s picture

Looks like we're having convo in two places.

we could just as well demand that the theme overrides the entire asset library associated with that template.

If we can do that, like have the library only callable by the theme that defines it, that would serve the need; however, I worry that would get confusing. The nice part of attaching an asset directly, like css/my-style.css is it would be obvious to anyone, even the inexperienced, what they'd have to do.

I suspect this is less appealing because this further increases the amount of work for a theme developer?

It's not just work. Creating a library isn't a lot of work, and I'd challenge anyone saying so, but it does create two issue. One, it requires a fair amount of knowledge of how theming works in Drupal to get used to. Two, it requires foreknowledge. The libraries have to be setup before anything can work. Working with assets directly can make setting up things like a component system simpler, because you can have templates and assets bundled without dealing with the libraries. (which are independent from the component) The effect is to simplify the mental model.

Moving back to inclusion of individual files would complicate this a lot.

Duly noted. I think it would be acceptable to put some restrictions on it. We've often gone out of our way to add more flexibility than needed in some places. Less flexibility, but consistent and reliable is a good tradeoff. I'm not sure though what that would be.

lauriii’s picture

Discussed this with @Wim Leers to get a better understanding of the concerns, and the required changes to the proposed solution to address those. The main concern is that allowing attaching individual assets rather than libraries makes calculating required assets for a page more expensive. This becomes a concern especially on AJAX requests where the client informs the server about libraries that have been already loaded on the page. When using individual assets, the request sizes could increase dramatically — this is a problem that used to plague Drupal 7 and was fixed in Drupal 8, in #2368797: Optimize ajaxPageState to keep Drupal 8 sites fast on high-latency networks, prevent CSS/JS aggregation from taking down sites and use HTTP GET for AJAX requests.

To avoid this problem, Wim proposed to redesign the Twig functions (attach_css() + attach_js()) from the issue summary so that all assets would be loaded with a single function call (e.g. attach_assets()). During Twig compilation, we generate one (immutable) asset library per Twig function call. If multiple calls exist in a single file, multiple libraries will be generated. They would get auto-generated names such as THEMENAME/component--COMPONENTNAME--index. The Twig function call will get compiled into a call for the existing attach_library() Twig function with the dynamically generated library name.
The new function would have parameters for an array of CSS files, an array of JavaScript files and an array of libraries these files depend on.

Example code:
{{ attach_assets([‘style.css’], [‘javascript.js’], [‘core/jquery’]) }}

Since internally these function calls lead into automatically generated libraries, we have to extend the libraries API to allow the creation of non-extendable, and non-overridable libraries — in other words: immutable asset libraries. The only way to override an automatically generated library is to override the template containing the attach_assets call. This also means that it’s not a problem that the automatically generated library name is not known by the themer, because it’s purely an underlying implementation/code execution detail: the overarching goal is to specifically only allow different assets to be loaded by overriding the entire component (= template + CSS + JS).

In other words: we continue to use all existing infrastructure for themes/templates and asset libraries, but rather than requiring themers/front-end developers to *learn* those underlying abstractions, we simply automatically use them under the hood.

There are still is some front-end performance risks related to #2368797: Optimize ajaxPageState to keep Drupal 8 sites fast on high-latency networks, prevent CSS/JS aggregation from taking down sites and use HTTP GET for AJAX requests though: if there are dozens of components on a page, and each has some CSS and/or JS associated with it, then there will be dozens of asset libraries listed in AJAX page state. The reason this isn’t a problem already in Drupal 8 is the use of dependencies: only top-level asset libraries need to be sent from the client to the server; their dependencies do not need to be sent. If we can figure out a way to have dependencies between components so that those dependencies can also be applied to their auto-generated asset libraries, then this problem would disappear too.

wim leers’s picture

wim leers’s picture

MathieuSpil’s picture

Discussed this with @lauriii at Drupal Dev Days.
Seeing this from an end-user perspective: the only actual difference would be that next to attach_library, we would now have a more granular approach by saying attach_css and attach_js.

This more granular approach sounds like a really valuable thing, as that implies that
1) We have better naming of what we are actually doing as a themer not sure what a library contains.
2) We move closer in our naming to the fundaments of the web (HTML/CSS/JS). When writing HTML (in twig) we attach CSS and JS. (makes it super readable)
3) This opens up the possibilities of progressive rendering: Drupal Core in the future could decide to allow progressive rendering by inlining JS and CSS directly as little files in the HTML output. Which would be a massive push forward in way how we think about theming.
4) With compony.io I am making a push towards seeing the theming-layer as a mere collection of components that are self-sufficient. Having a clear separation between library / css / js in terms of assets would make everything a whole less complicated, as that would mean lower the amount of work that needs to happen to attach an asset to a component.
5) Not a lot of themers dares to unset libraries, because it's hard to be sure what exactly you are unsetting. Having this more fine grained control would mean more flexibility and better readability.

wim leers’s picture

This came up in #3064854: Allow Twig templates to use front matter for metadata support, and actually an alternative implementation proposal was made there. See the issue summary and #3064854-35: Allow Twig templates to use front matter for metadata support.

markhalliwell’s picture

I kind of have to agree with #4. This kind of defeats the whole point of moving everything over to libraries.

That being said, this issue (at its core) is basically discussing the concept of components; which more or less duplicates the related issues I'm attaching now.

I'm not entirely sure this is something that can or should be done at this time (in 8.x).

At the very least, I think any "component" based issue really needs to be postponed on something like #2869859: [PP-1] Refactor theme hooks/registry into plugin managers.

We really need to focus on modernizing the core APIs of the theme system first before we attempt to shift theming paradigms as a whole.

wim leers’s picture

I agree with #4, but #9 addressed those concerns.

markhalliwell’s picture

Not really, it merely skirts around the issue by essentially hacking in dynamic libraries; something templates were never really designed to do.

Yes, we have the attach_library() Twig function, but that only attaches existing libraries that are already defined elsewhere.

You asked me to create a POC in #3064854-43: Allow Twig templates to use front matter for metadata support.

This issue would require, at a minimum, the inclusion of #3075427: Create TemplateDiscovery for plugin managers to use if we were to go this route.

It would then need to actually create some sort of plugin manager to actually collect discovered templates.

All this to mean that this issue is essentially akin to introducing the concept of components in core (which we already have issues for).

I'm not saying that this isn't needed or desired, but the theme system will need some serious overhaul for this to all work properly.

Regardless, this issue doesn't entirely seem like the biggest priority right now.

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

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

andypost’s picture

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

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now 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: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

andypost’s picture

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

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

mherchel’s picture

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

I want to give a huge +1 to this from a core developer perspective

If themers had the ability to attach CSS and JS without creating a library, core developers would have much more flexability to modify markup and styling, which would enable more rapid innovation within the front-end of Drupal. This is extremely important now that Drupal 10 will not be supporting legacy browsers.

Forcing core themes to use the library system sets an expectation that the library will be forever available and not change. This negatively affects our ability to improve the markup, styling, and resilience of the styles.

An example of this is Drupal’s tabs. Themes should not have to create their own styles for this (unless they want to simply updating the colors and spacing), however the tabs’ default markup is inadequate (which includes the non-BEM CSS classes).

I want to give a huge +1 to this from an general development perspective

As a front-end developer who works with clients on a daily basis, having to create a single one-use library for each and every component I develop, creates a very negative development experience. I should not have to jump around to multiple places within my codebase just to get a single component working (example: template directory, the CSS directory, and to the libraries file). While this doesn’t 100% solve this issue, the ability to not create a library takes a huge step in fixing this.

Implementation Details

  • Does the `style.css` in the example reference the same directory that the template exists in? Should we do something like`{{ attach_css('@olivero/css/styles.css') }}`? Ideally we could do both.
  • I think the ability to declare dependencies within these functions would be useful
  • I’d personally prefer `attach_js` over `attach_javascript`. Less typing!
effulgentsia’s picture

As I understand it, this is a required step of #2659890: [Policy] [Plan] Drupal 9 and 10 markup and CSS backwards compatibility (and is listed in that summary as such), so setting this issue's parent to that one, and promoting this to Major accordingly.

The main concern is that allowing attaching individual assets rather than libraries makes calculating required assets for a page more expensive.

Yes, I agree with this concern.

To avoid this problem, Wim proposed to redesign the Twig functions (attach_css() + attach_js()) from the issue summary so that all assets would be loaded with a single function call (e.g. attach_assets()).

+1. Not sure if there's agreement here on that or not, but if/when there is, can we update this issue's summary accordingly, especially the example code in the API changes section?

During Twig compilation, we generate one (immutable) asset library per [attach_assets()] call...They would get auto-generated names such as THEMENAME/component--COMPONENTNAME--index.

Does the generated library name need to be named with the theme and component names? If Twig extension functions have ready access to that info, then great, but if not, then would it be easier to instead name the library as a hash of the asset names?

This issue would require, at a minimum, the inclusion of #3075427: Create TemplateDiscovery for plugin managers to use if we were to go this route. It would then need to actually create some sort of plugin manager to actually collect discovered templates.

It looks like #3075427: Create TemplateDiscovery for plugin managers to use has some great use cases in that issue, but I don't understand why it's a requirement for this one. Can you elaborate on that?

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

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now 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.

e0ipso’s picture

I am not sure if this the direction we want to go, but one of the goals of CL Components is precisely to simplify maintenance of CSS, JS, and assets.

As @lauriii points out we would benefit from communicating that when you override X you are taking over of X CSS, JS, and assets as well. It does not make sense to have that burden perched on the framework.

The solution we arrived at in CL Components is not to allow inclusion of CSS and JS assets directly in the templates, but quite the opposite. Developers associate CSS and JS to Twig templates by adding them to the component folder. The documentation shows this (abbreviated for more focus on the current issue):

[PROJECT_ROOT]/...
    |- ... organize your components as you see fit
        |- my-component-machine-name
            |- my-component-machine-name.twig (required)
            |- assets
                |- css
                    |- some-styles.scss
                    |- some-more-styles.css
                |- js
                    |- some-javascript.js
                    |- other-javascript.js
                |- img
                    |- img1.png

Then the module creates a library dynamically cl_components/a324dlkjaef30 with the JS and CSS. The library gets attached automatically whenever anyone includes my-component-machine-name.twig (using a custom Twig loader). This way, the front-end developer never has to worry about aggregation, backwards compatibility, ...

Now if someone wants to take over my-component-machine-name the implementation forces them to copy the whole folder, which implies ownership of JS and CSS.

I am not sure weather or not this helps in this issue, but I thought to share an alternative point of view on the matter.

e0ipso’s picture

Additionally (and a bit off topic, hence the separate comment), all components need to have a metadata.json file. Which includes (among other more relevant bits) the status of the component. This would also help with version upgrades and BC, because the site would know if you are rendering a component that has been marked as "status": "DEPRECATED" by upstream.

geek-merlin’s picture

#26 sounds VERY convincing to me.

markconroy’s picture

Issue summary: View changes
StatusFileSize
new49.93 KB

This sounds REALLY interesting, and is how we organise assets in our custom themes at Annertech (either via using Storybook or our own base theme). Here's an example of our 'accordion' component directory:

accordion component structure

What I love about this is that I don't need to go searching for CSS in one directory, JS in another, etc.

However ... sorry about this, but I'm not convinced yet that for _Drupal Core_ we should take the same approach. If we have patterns in Drupal core for a modal for example, and then we decide there is something we'd like to add for accessibility (a newly release aria attribute for example), anyone who simply wanted to extend the twig template will not get this new update since they will also have taken over the CSS and JS and HTML for that component.

An approach like this for Drupal Core will - I think (happy to be corrected here) - mean a lot more maintenance for developers if they have to take control of a lot more than they intended.

---

Edit: I don't have a problem with all the parts of a component (HTML, CSS, JS, etc) being in the same directory for Drupal Core (I love that idea), my only concern is with the idea that "you take ownership of the full component if you take ownership of any part of it".

hawkeye.twolf’s picture

I don't have a problem with all the parts of a component (HTML, CSS, JS, etc) being in the same directory for Drupal Core (I love that idea), my only concern is with the idea that "you take ownership of the full component if you take ownership of any part of it".

If we have the metadata.json file as described in #27, how about supporting a "parent" or "extends" key to extend another component? Like, whenever the child component gets used, it automatically includes the assets from the "parent" component.

jonathanshaw’s picture

@markconroy

my only concern is with the idea that "you take ownership of the full component if you take ownership of any part of it".

The arguments for doing this are documented in #6. How do you respond to them?

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

bnjmnm’s picture

Version: 9.5.x-dev » 10.1.x-dev
bnjmnm’s picture

StatusFileSize
new2.53 KB

This patch adds a working add_css() that adds the asset via the library system. It requires the full asset path at the moment, but that (and an add_js) can be added if this seems to resemble the direction to go in.

The asset path is currently snuck into the library name. We'd need something a little slicker if we wanted to add options like weight, and that approach may weird some folks out in general.

Either way, here's 3k that demonstrates this can happen without moving the earth.

rootwork’s picture

Status: Active » Needs review
e0ipso’s picture

I am working on this now in the scope of CL Components.

#3293455: Allow altering components created elsewhere

wim leers’s picture

nod_’s picture

Pretty neat.

Few comments,

  1. Passing the path in the library name might get troublesome with regard to asset URL length if there are a lot of templates that uses that, even with #3303067: Compress aggregate URL query strings. So we'll need the slicker solution right away unfortunately.
  2. I'd use the names from the IS for the functions (attach_js/attach_css) so that it's not confused with the old/removed drupal_add_js/drupal_add_css methods.
nod_’s picture

cross post, used an old tab to write the message :o

nod_’s picture

e0ipso’s picture

StatusFileSize
new11.42 MB

This is the way I am proposing to do it in CL Components. See attached video (3min 38sec).

bnjmnm’s picture

Issue tags: +Needs tests
StatusFileSize
new6.8 KB
  • This adds (and actually requires) prefixing the asset path with @module, @theme or @core.
  • the attach_js and attach_css accept a second argument of a config object that works the same as the config object for an asset path in a .libraries.yml file
  • The library name length is no longer a concern as there isn't a bunch of extra data attached to it. During CSS/JS attach, we get all the necessary info by parsing the twig file directly.

Definitely needs tests. Hope to work on those next 😎

bnjmnm’s picture

StatusFileSize
new19.3 KB
  • Added basic tests
  • Changed service requests to dependency injection
  • Added docblocks
  • Other mix fixes

Testing needs to go further than the basic checks added here, but it's a good foundation for how to write them.

pooja saraah’s picture

StatusFileSize
new20.18 KB
new760 bytes

Fixed failed commands on #43
Attached interdiff patch

bnjmnm’s picture

StatusFileSize
new22.26 KB

This adds JS to testAttachToPageTemplate and adds the new dependencies for AssetResolver and TwigExtensions to their unit tests, which was the cause of the Commands Failed in #43 (which at least for me didn't happen when I ran the commit script locally).

This also fixes a JS attach issue that was exposed by adding this test.

This does not take #44 into account as that attempted to fix the check in #43 by adding arbitrary commands to a constructor vs the actual classes it required.

More tests are still needed including: subthemes, use on non-base templates, passing in options, and likely even more than that. Work will continue.

bnjmnm’s picture

Issue tags: -Needs tests
StatusFileSize
new28.07 KB
new9.18 KB

Test coverage expanded, including confirmation that the assets aggregate as if they were added as part of a traditional library.

phenaproxima’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
    @@ -2,6 +2,9 @@
    +use Symfony\Component\Process\Exception\LogicException;
    

    Surely this is not the LogicException you're looking for. 😎

  2. +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
    @@ -72,14 +94,30 @@ class AssetResolver implements AssetResolverInterface {
    +   * @param \Drupal\Core\Template\TwigEnvironment $twig
    +   *   The Twig environment.
    +   * @param \Drupal\Core\Theme\Registry $theme_registry
    +   *   The theme registry.
        */
    -  public function __construct(LibraryDiscoveryInterface $library_discovery, LibraryDependencyResolverInterface $library_dependency_resolver, ModuleHandlerInterface $module_handler, ThemeManagerInterface $theme_manager, LanguageManagerInterface $language_manager, CacheBackendInterface $cache) {
    +  public function __construct(LibraryDiscoveryInterface $library_discovery, LibraryDependencyResolverInterface $library_dependency_resolver, ModuleHandlerInterface $module_handler, ThemeManagerInterface $theme_manager, LanguageManagerInterface $language_manager, CacheBackendInterface $cache, TwigEnvironment $twig, Registry $theme_registry) {
    

    Shouldn't $twig and $theme_registry default to NULL? And do we have test coverage for the deprecation notices that will be raised if they're not passed?

  3. +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
    @@ -72,14 +94,30 @@ class AssetResolver implements AssetResolverInterface {
    +      @trigger_error('Calling AssetResolver::__construct() without the $twig argument is deprecated in drupal:10.1.0 and will be required in drupal:11.0.0.', E_USER_DEPRECATED);
    

    Pro-tip: I usually use __METHOD__ . '() in deprecation notices like this, so that the fully qualified class name is displayed without needing to type it all in.

  4. +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
    @@ -107,6 +145,106 @@ protected function getLibrariesToLoad(AttachedAssetsInterface $assets) {
    +  /**
    +   * Creates a definition for dynamically generated Twig libraries.
    +   * @param $name
    +   *   The library name.
    +   * @param $definition
    +   *   The library definition
    +   */
    +  protected function componentDefinition($name, &$definition) {
    

    Few minor things:

    • There needs to be an empty line between the one-line description and the param list.
    • Should $name and $definition be type hinted?
    • Should this method have a return type hint?
    • The name componentDefinition() seems a little value. Maybe createComponentDefinition() would be clearer.
    • Why does $definition need to be passed by reference? Is there any way to avoid that?
  5. +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
    @@ -107,6 +145,106 @@ protected function getLibrariesToLoad(AttachedAssetsInterface $assets) {
    +    if (str_starts_with($name, 'TWIG_ATTACH---')) {
    

    Depending on how often it's reused, it might make sense for TWIG_ATTACH--- to be a class constant.

  6. +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
    @@ -107,6 +145,106 @@ protected function getLibrariesToLoad(AttachedAssetsInterface $assets) {
    +      [$theme_hook, $file_type] = explode('>>', $name);
    

    This looks like $name is expected to be in a very specific format. Are we validating that in some way?

  7. +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
    @@ -107,6 +145,106 @@ protected function getLibrariesToLoad(AttachedAssetsInterface $assets) {
    +      assert(in_array($file_type, ['css', 'js']), 'Only JS and CSS files should be attached to a template');
    

    We should always use strict comparison with in_array(). Also, do we want to normalize $file_type here by calling strtolower()?

  8. +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
    @@ -107,6 +145,106 @@ protected function getLibrariesToLoad(AttachedAssetsInterface $assets) {
    +      $info = $this->themeRegistry->get()[$theme_hook];
    

    🤔 Looking at ThemeRegistry::get(), it seems that it expects a string to be passed, rather than returning an array like this. Should this be $this->themeRegistry->get($theme_hook)? If so, then the fact that this didn't fail tests points to some missing coverage.

  9. +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
    @@ -107,6 +145,106 @@ protected function getLibrariesToLoad(AttachedAssetsInterface $assets) {
    +      $template_file = $info['path'] . '/' . $info['template'] . '.html.twig';
    

    Are we sure the path and template keys will be set? What happens if they're not?

  10. +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
    @@ -107,6 +145,106 @@ protected function getLibrariesToLoad(AttachedAssetsInterface $assets) {
    +        if (str_contains($node_string, 'attach_' . $file_type)) {
    

    Would str_starts_with() make more sense here?

  11. +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
    @@ -107,6 +145,106 @@ protected function getLibrariesToLoad(AttachedAssetsInterface $assets) {
    +            if ($node instanceof PrintNode && $node->getNode('expr') instanceof FilterExpression) {
    +              $expr = $node->getNode('expr');
    +              $expr_node = $expr->getNode('node');
    +              if ($expr_node instanceof FunctionExpression && $expr_node->getAttribute('name') === 'attach_' . $file_type) {
    +                foreach ($expr_node->getNode('arguments')->getIterator() as $index => $arg) {
    +                  if ($arg instanceof ArrayExpression) {
    +                    $pairs = $arg->getKeyValuePairs();
    +                    foreach ($pairs as $pair) {
    +                      $value = $pair['value']->getAttribute('value');
    +                      if ($index === 0) {
    +                        $files_to_add[] = $value;
    +                        $options[$value] = [];
    +                      }
    +                      else {
    +                        $key = $pair['key']->getAttribute('value');
    +                        $options[end($files_to_add)][$key] = $value;
    +                      }
    +                    }
    +                  }
    +                }
    +              }
    

    OK, not gonna lie: this code is terrifying and I don't understand what it's doing, so I'm basically just taking it on faith that it works. I feel like abstracting this into its own method and dousing it generously with comments would be helpful for future maintenance.

    Also, this seems like quite a heavy operation. Are we caching this in any way?

  12. +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
    @@ -107,6 +145,106 @@ protected function getLibrariesToLoad(AttachedAssetsInterface $assets) {
    +  public function assetPath($path) {
    

    Why no type hints?

    Also, the name assetPath() seems a little vague in relation to what the doc comment says this method does. How about getAssetPath() or computeAssetPath()?

  13. +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
    @@ -107,6 +145,106 @@ protected function getLibrariesToLoad(AttachedAssetsInterface $assets) {
    +    if ($this->moduleHandler->moduleExists(substr($path_prefix, 1))) {
    

    Maybe we should assert that $path starts with @, since I could easily see that little quirk being missed.

  14. +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
    @@ -107,6 +145,106 @@ protected function getLibrariesToLoad(AttachedAssetsInterface $assets) {
    +      return str_replace($path_prefix, $base_themes[substr($path_prefix, 1)]->getPath(), $path);
    

    We're calling substr($path_prefix, 1) several times. It should probably be its own variable, which would also improve readability.

  15. +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
    @@ -107,6 +145,106 @@ protected function getLibrariesToLoad(AttachedAssetsInterface $assets) {
    +      throw new LogicException("Asset prefix $path_prefix does not correspond to an installed module, theme or subtheme.");
    

    Shouldn't "subtheme" be "base theme" in this context?

  16. +++ b/core/lib/Drupal/Core/Template/TwigExtension.php
    @@ -82,13 +90,21 @@ class TwigExtension extends AbstractExtension {
    +  public function __construct(RendererInterface $renderer, UrlGeneratorInterface $url_generator, ThemeManagerInterface $theme_manager, DateFormatterInterface $date_formatter, FileUrlGeneratorInterface $file_url_generator, Registry $theme_registry) {
    

    Same question here about whether $theme_registry should default to NULL (and related test coverage).

  17. +++ b/core/lib/Drupal/Core/Template/TwigExtension.php
    @@ -365,6 +383,80 @@ public function attachLibrary($library) {
    +  private function attachAsset(array $assets, string $type, $context) {
    

    Missing return type hint?

  18. +++ b/core/lib/Drupal/Core/Template/TwigExtension.php
    @@ -365,6 +383,80 @@ public function attachLibrary($library) {
    +      $registered_hooks = $this->themeRegistry->get();
    

    As before, I'm not sure ThemeRegistry::get() is meant to be called without any arguments.

  19. +++ b/core/lib/Drupal/Core/Template/TwigExtension.php
    @@ -365,6 +383,80 @@ public function attachLibrary($library) {
    +      // This is a temporary library with a crazy name that is later parsed to
    +      // add the asset.
    +      $theme_library_name = "$active_theme/TWIG_ATTACH---component--$theme_hook>>$type>>$index";
    

    This could use an @see pointing to the place where the library name is parsed.

  20. +++ b/core/lib/Drupal/Core/Template/TwigExtension.php
    @@ -365,6 +383,80 @@ public function attachLibrary($library) {
    +   *   Each CSS file path should be prefixed with one of the following
    +   *    - @THE-THEME-MACHINE-NAME if the file is in a theme
    +   *    - @THE-MODULE-MACHINE-NAME if the file is in a module
    +   *    - @core if the file is not in a theme or module.
    +   *    These prefixes will resolve to the full theme/module/core paths.
    

    I think this could use some examples.

    Also, we should be clear about whether the module or theme you reference in the asset name needs to be installed or not.

  21. +++ b/core/lib/Drupal/Core/Template/TwigExtension.php
    @@ -365,6 +383,80 @@ public function attachLibrary($library) {
    +   *   Each JavaScript file path should be prefixed with one of the following
    +   *    - @THE-THEME-MACHINE-NAME if the file is in a theme
    +   *    - @THE-MODULE-MACHINE-NAME if the file is in a module
    +   *    - @core if the file is not in a theme or module.
    +   *    These prefixes will resolve to the full theme/module/core paths.
    

    Same comments as for attachCss().

Version: 10.1.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, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

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.