Ensure consistent ordering when calculating library asset order

Created on 13 August 2024, 3 months ago
Updated 13 September 2024, 2 months ago

Problem/Motivation

This issue has probably been around for a while, but getting a setup where the error happens is a bit tricky and it's very hard to understand what is actually going on. I'll start by giving some background info on how Drupal's asset system works when aggregation is turned on. This is based on my understanding have looked into this a few times and needed to fix issues with assets management in Drupal in the past. If you are well versed with asset management, feel free to skip this.

Asset url generation

The first part of Drupals asset management with aggregation turned on, is the process to generate the script / link tags to include on the page which are grouped into header and footer for scripts to be placed in the header and footer section respectively. The way this works is

1. Get a list of libraries to include on the page (this list is collected from the render array where every template rendering will include a set of libraries needed. This means that the order of this list is influenced by the render process.
2. Group libraries into header and footer section
3. For each section group the scripts into groups
4. Each group is represented by a script / link tag. If preprocess is enabled for a group a magic url is generated with various information included, which can be used to generated the script file on request. the idea is that one on JS file is generated per request to protect against attacks where user would force Drupal to generate many script files per request. The information included in the script is:

  1. Minimum representation of libraries to include
  2. Minimum representation of libraries to exclude
  3. theme
  4. language
  5. delta (the group number from above)

Asset generation from url

When Drupal is requested for a script or a CSS file, if the file is not present on disk it will be generated, this process is managed with the information listed above:

  1. Minimum representation of libraries to include
  2. Minimum representation of libraries to exclude
  3. theme
  4. language
  5. delta (the group number from above)

The way this works is that pretty much the same thing happens as before. From the minimum representation of libraries to include / exclude, the full list of libraries to load is generated. This is then ordered by section and then by group.

Steps to reproduce (the logic error)

The problem we have right now, is that the order of scripts from the page render and from the minimum representation can be different. This can happen if you have css only libraries with javascript dependencies.

A way to reproduce this:

Have 4 libraries (using header scripts is the simplest solution to avoid the many assets on the page to conflict testing from on themes, modules etc).

prep_dis_a, prep_dis_b (preprocess disabled)
prep_en_a, prep_en_b (preprocess enabled)

Then one javascript library (js_dep) with the following dependencies (order is important):

  1. prep_dis_a,
  2. prep_en_a
  3. prep_dis_b
  4. prep_en_a

And one css library (css_dep) with the following dependency:

  1. prep_dis_b

If you on a page include the libraries in this order:

  1. js_dep
  2. css_dep

Then everything will work as expected and what you would see in the header scrip section is

  1. link to js file for prep_dis_a
  2. aggregated js with delta 1 (file should include prep_en_a script)
  3. link to js file for prep_dis_b
  4. aggregated js with delta 3 (file should include prep_en_b script)

If you reverse the order and include libraries like this

  1. css_dep
  2. js_dep

what you will see in the header section will be

  1. link to js file for prep_dis_b
  2. link to js file for prep_dis_a
  3. aggregated js with delta 2 (file should include prep_en_a and prep_en_b script)

The aggregated script will not work however. The reason is that the libraries to load (minimal version) will only be the js_dep library as css only dependencies are not included in the javascript dependency calculation. This means that the order of scripts will be different when Drupal calculates the groups based on the JS only libraries and will give a different set of groups which conflicts with the set of groups generated during page render (where CSS is included).

Proposed resolution

AssetResolver::get*Assets() receives the list of libraries in the order they were added on the page. By getting the minimal list of libraries, for only the asset type we want, and basing everything else on that normalized list, we ensure consistent ordering of files as they're output to the page (whether aggregated or not).

There is one invalid library definition in vertical-tabs.js which weights itself before its own dependencies, this magically works in some circumstances now, but not with the change, so we remove the weight. Otherwise the change is transparent to core test coverage.

Remaining tasks

Figure out a solution

User interface changes

None

Introduced terminology

API changes

Data model changes

Release notes snippet

πŸ› Bug report
Status

RTBC

Version

11.0 πŸ”₯

Component
Asset libraryΒ  β†’

Last updated about 6 hours ago

No maintainer
Created by

πŸ‡©πŸ‡°Denmark googletorp

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @googletorp
  • Status changed to Needs review 3 months ago
  • πŸ‡©πŸ‡°Denmark googletorp

    I did a simple approach which is to ensure that the libraries that needs to be processed is sorted. This means that the order of the libraries doesn't influence the specific groups that are created, which in turn ensures as consistent behaviour.

    I'm not sure if and how we should write tests for this, as the whole cycle is pretty complicated and not sure how we could write tests to actually capture this.

  • Pipeline finished with Failed
    3 months ago
    Total: 596s
    #253793
  • Status changed to Needs work 3 months ago
  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    MR should be pointed to 11.x as latest development branch.

    As far as tests go, if this is a major bug do believe we need someway to see that this is a problem and is being solved.

    But appears to have test failures.

  • Merge request !9217Create test module to showcase issue β†’ (Open) created by googletorp
  • Pipeline finished with Failed
    3 months ago
    Total: 426s
    #254749
  • Pipeline finished with Failed
    3 months ago
    Total: 188s
    #254766
  • Pipeline finished with Failed
    3 months ago
    Total: 137s
    #254800
  • Pipeline finished with Failed
    3 months ago
    Total: 3663s
    #254806
  • πŸ‡§πŸ‡ͺBelgium swentel

    We've been hit by this one as well. In our case, it happens while using gin admin theme, which also has two JS files with preprocess set to FALSE, and only under a specific case being a webmaster role which has less permissions than say user 1 (so hard to reproduce as well, as I'm not totally known with the internal system)

    The JS aggregation URL triggers following error: Symfony\Component\HttpKernel\Exception\BadRequestHttpException: Invalid filename. in Drupal\system\Controller\AssetControllerBase->getGroup() (line 225)

    The MR in #4 fixes it for us, and the page now renders fine, so plus one from me at least.

    It's close to being critical, because in our case the webmasters, while they don't get a blank screen, they couldn't edit their content as for instance CKeditor isn't loaded at all or use the media browser to select images.

  • πŸ‡§πŸ‡ͺBelgium swentel

    Actually, I spoke to fast, while node edit page is now fine with the patch, admin/content is now broken, so it looks like the sort isn't solving everything.

  • πŸ‡¬πŸ‡§United Kingdom catch

    Adding πŸ› Aggregated URL hashes for assets can mismatch due to different order of library assets Active as a related issue. We definitely have an issue with the current ordering not being reliable enough, whether it's fixable without the before/after issue or not I'm less sure about, but this is a slightly different approach from the ones I've tried (and failed) previously.

  • Pipeline finished with Failed
    3 months ago
    Total: 555s
    #255042
  • πŸ‡©πŸ‡°Denmark googletorp

    I spent quite some time trying to figure out what is going on. I also saw the issue with gin theme and had some node forms which didn't work depending on which additional libraries were pulled in via the field_group module.

    Originally I thought the problem was the ordering of libraries, but upon further investigation it seems the root issue is that the order of all libraries vs the ordering of only js assets isn't necessarily the same, which then produces the error @swentel mentions in #11.

    I closed the MR from #4 since it doesn't necessarily fix things and made a new patch in !9219 which I think actually solves the issue.

    I don't know how to do a an actual test case for this - I made a test module which has two pages, one failing and one before the fix where both are working after the fix. For some reason some functional JS unit tests are failing in the MR which introduces the test module used to demonstrate the issue, which seems really strange to me, as the introduction of a test module shouldn't make test fails, so wonder if something else is going on.

    I'll try to see if I can come up with a way of testing this, but would be great if some one could take a look at things mentioned and provide feedback on the suggested fix.

  • Status changed to Needs review 3 months ago
  • Pipeline finished with Failed
    3 months ago
    Total: 606s
    #255148
  • πŸ‡¬πŸ‡§United Kingdom catch

    Left one comment on the MR - currently it's repeating logic done earlier to remove non-js libraries from the list. Is that duplication intentional? Could we remove the unset in the second foreach maybe?

    The AjaxPageState test is a real failure.

    My attempt to fix this issue was in πŸ› Aggregated URL hashes for assets can mismatch due to different order of library assets Active and was clearly incomplete. If we can get this MR to green with a test that fail successfully, it might be worth looking to see whether we can revert the fix from there (but also possible that we actually need both changes).

  • πŸ‡¬πŸ‡§United Kingdom catch

    Pushed a commit to fix the AjaxPageState failure.

  • πŸ‡¬πŸ‡§United Kingdom catch

    functional JS unit tests are failing in the MR which introduces the test module used to demonstrate the issue

    If this is ckeditor5 image tests, it's a random but frequent test failure in HEAD and unrelated.

  • Pipeline finished with Canceled
    3 months ago
    Total: 91s
    #257180
  • Pipeline finished with Success
    3 months ago
    Total: 621s
    #257182
  • Pipeline finished with Failed
    3 months ago
    Total: 183s
    #257211
  • Pipeline finished with Failed
    3 months ago
    Total: 156s
    #257213
  • Pipeline finished with Failed
    3 months ago
    Total: 595s
    #257216
  • Pipeline finished with Failed
    3 months ago
    Total: 693s
    #257225
  • Pipeline finished with Canceled
    3 months ago
    Total: 202s
    #257235
  • Pipeline finished with Success
    3 months ago
    #257237
  • πŸ‡©πŸ‡°Denmark googletorp

    I have done a couple of things now, to summarize what we have.

    1. Improved existing test cases in AssetResolverTest and correct a wrong assertion (different libraries, same timestamps should have 2 / 4, not 2/3). This was a result of the way libraryDiscovery was mocked
    2. Create a failing unit test case where a CSS on library influence the contents of JS groups
    3. Adjusted fix to ensure that new test case is now green (needed to both do sorting and re-calculation of dependencies from minimal dependencies to get a working solution

    With regards to the comment in the PR, we need to double unset (explained in detail in the PR). The root issue is caused by CSS dependency changing the order or dependencies which can result in different groups. The reason why this fails is that when Drupal generates the aggregated it does so from the minimal JS only libraries.

    The test cases for the fix is now all green (we got lucky with the ckeditor test case which was failing before), so I think this is ready now.

  • Status changed to RTBC 3 months ago
  • πŸ‡―πŸ‡΅Japan orakili

    We faced the same issue and the changes from the MR seem to have fixed the problem.

    Attaching the a patch for Drupal 10.3.2 backported from the MR as well since this version was also affected.

  • Status changed to Needs work 3 months ago
  • πŸ‡¬πŸ‡§United Kingdom catch

    Needs a rebase for merge conflicts in the test..

  • πŸ‡¬πŸ‡§United Kingdom catch

    Re-titling to make it clearer what the issue is.

  • Status changed to Needs review 2 months ago
  • πŸ‡¬πŸ‡§United Kingdom catch

    Did a couple of things here:
    1. Rebased to resolve the merge conflicts. The unit test/mocking fix implemented here was similar to the one in HEAD, but I prefer the use of withCallback() here so stuck with that from this MR.

    2. Factored out the library loading/filtering logic to a protected method and calling that from getCssAssets() and getJsAssets() to reduce code duplication.

    3. Both before and after #2, and also in similar attempts elsewhere, MediaStandardTest fails on vertical-tabs.js being loaded before form.js - this is because vertical-tabs.js specifies a negative weight and form.js doesn't, and weight overrides dependencies even though it shouldn't.

    I tried changing the ordering logic and then stopped again. Then looked back to see why vertical-tabs.js is weighted before announce.js, and... I cannot find any reason. It was added back in #1996238: Replace hook_library_info() by *.libraries.yml file β†’ which was a huge MR, and didn't have a weight before that.

    Removing the unnecessary weight I get a green MR.

  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    1 nitpicky comment but can't apply since it's a committer MR.

  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    May want a 2nd thumbs up before marking. But could issue summary be updated also. Solution still seems to be TBD.

  • πŸ‡¬πŸ‡§United Kingdom catch
  • πŸ‡¬πŸ‡§United Kingdom catch

    Updated the issue summary.

  • Pipeline finished with Failed
    2 months ago
    Total: 418s
    #279345
  • Status changed to Needs work 2 months ago
  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    Rebased as I can't re-run tests and javascript tests appear to be consistently failing.

  • πŸ‡¬πŸ‡§United Kingdom catch

    I think I can reproduce the bug manually, it silently doesn't do the thing the test is checking for (maintaining vertical tabs state when ckeditor5 buttons are moved around), which is probably something to do with the vertical tabs weight change, but there's no error at all and I'm not clear what even provides that functionality in core.

  • Status changed to Needs review 2 months ago
  • πŸ‡¬πŸ‡§United Kingdom catch

    Apart from one random test failure, that results in a green run again.

  • Status changed to RTBC 2 months ago
  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    Will go out on a limb and say this one is ready.

    Marking since it will go through 2 committer eyes too.

  • Status changed to Needs work 2 months ago
  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    I've added couple of comments to the MR that need addressing.

  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    I think we need a CR here that notes that libraries need to ensure they list the vertical tabs as a dependency if it is one so it's not missing like it was for ckedtior.

    Also think the CR should note the potential for the JS order to change if a contrib or custom extension adds a library with an impossible weight (i.e. trying to come before it's own dependencies).

    I asked @catch if we could produce a warning if we determined that a library had an impossible weight and he pointed out he'd rather do πŸ“Œ Replace custom weights with dependencies in library declarations; introduce "before" and "after" for conditional ordering Needs review and deprecate weights altogether.

  • Pipeline finished with Failed
    2 months ago
    Total: 396s
    #280113
  • Status changed to Needs review 2 months ago
  • πŸ‡¬πŸ‡§United Kingdom catch

    Added the change record.

  • Status changed to Needs work 2 months ago
  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    Unfortauntely testJsAssetsOrder failed in the last run and that's definitely related.

  • πŸ‡¬πŸ‡§United Kingdom catch

    @alexpott I think you were looking at https://git.drupalcode.org/project/drupal/-/merge_requests/9219 which is tests only - going to hide that MR.

  • πŸ‡¬πŸ‡§United Kingdom catch

    catch β†’ changed the visibility of the branch 3467860-11x-test to hidden.

  • Pipeline finished with Success
    2 months ago
    Total: 542s
    #280415
  • Status changed to Needs review 2 months ago
  • πŸ‡¬πŸ‡§United Kingdom catch

    #36 was looking at the wrong MR, but I had failed to push a commit fixing the ckeditor5 dependency from local and now that's pushed.

  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    Oops yeah I must have been. Sorry.

  • Status changed to RTBC 2 months ago
  • πŸ‡¬πŸ‡§United Kingdom catch

    Moving this back to RTBC since only trivial changes and a CR since #33.

  • πŸ‡¬πŸ‡§United Kingdom catch

    This doesn't only affect aggregate assets, it also ensures the correct order when aggregation is off, re-titling.

  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    Committed and pushed 0c9e4a4b8e to 11.x and 4c3bada24c to 10.4.x. Thanks!

    • alexpott β†’ committed 4c3bada2 on 10.4.x
      Issue #3467860 by googletorp, catch, smustgrave, alexpott, swentel:...
    • alexpott β†’ committed 0c9e4a4b on 11.x
      Issue #3467860 by googletorp, catch, smustgrave, alexpott, swentel:...
  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    Tagging after discussion with @xjm, @catch and @longwave

  • πŸ‡¬πŸ‡§United Kingdom catch
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • Automatically closed - issue fixed for 2 weeks with no activity.

  • πŸ‡ΊπŸ‡ΈUnited States thefancywizard

    Rerolled the patch from #21 against 10.3.6.

  • πŸ‡ΊπŸ‡ΈUnited States thefancywizard

    Added the changes needed to support vertical tabs.

  • πŸ‡¨πŸ‡­Switzerland berdir Switzerland

    Views exposed filters in entity browser dialog/iframe are somehow no longer working in 10.4.x when testing on our install profile, git bisect points to this issue.

    I guess there might be also an incorrect or missing library dependency, but there are no JS errors or so, pressing the submit button just reloads the page without applying the filter. maybe something related to jquery.form or views JS?

    entity_browser tests on D11 are broken atm, but I did manage to at least fix previous major and set up a job for 10.4 that is also failing on various tests: https://git.drupalcode.org/project/entity_browser/-/jobs/3221250

  • πŸ‡¬πŸ‡§United Kingdom catch

    I don't know if it's what's causing the issue, but the dependencies do seem to be incorrect:

    entity_browser.common depends on drupalSettings but doesn't declare it. Other libraries depend on entity_browser.common then separately depend on drupalSettings.

    Couple of other libraries declare dependencies on indirect dependencies of libraries they depend on.

    Something like this (completely untested) diff:

    diff --git a/entity_browser.libraries.yml b/entity_browser.libraries.yml
    index bf6d05d..21e512e 100644
    --- a/entity_browser.libraries.yml
    +++ b/entity_browser.libraries.yml
    @@ -2,9 +2,10 @@ common:
       js:
         js/entity_browser.common.js: {}
       dependencies:
    -    - core/drupal.dialog.ajax
    +    - core/drupalSettings
         - core/drupal
         - core/jquery
    +    - core/drupal.dialog.ajax
     
     pager:
       css:
    @@ -28,10 +29,7 @@ iframe:
       js:
         js/entity_browser.iframe.js: {}
       dependencies:
    -    - core/drupalSettings
         - core/once
    -    - core/drupal
    -    - core/jquery
         - entity_browser/common
     
     iframe_selection:
    @@ -47,8 +45,6 @@ entity_reference:
         - entity_browser/common
         - entity_browser/entity_list
         - core/sortable
    -    - core/drupal
    -    - core/jquery
     
     file_browser:
       css:
    
Production build 0.71.5 2024