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

<a href="https://www.drupal.org/node/3473558">Assets are now ordered more strictly by dependencies</a> instead of relying on the order that libraries were attached to the page. This is a bug fix that resolves various issues with asset handling, however if a library specifies an individual asset weight that conflicts with its dependencies (i.e. the dependent file would be added to the page before the files it depends on), there may be side-effects in some situations. In these cases, module or theme authors should review their library dependencies and any custom weights.

Issue fork drupal-3467860

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

googletorp created an issue. See original summary.

googletorp’s picture

Issue summary: View changes
googletorp’s picture

Issue summary: View changes

googletorp’s picture

Status: Active » Needs review

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.

smustgrave’s picture

Version: 10.3.x-dev » 11.x-dev
Status: Needs review » Needs work
Issue tags: +Needs Review Queue Initiative

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.

googletorp’s picture

Issue summary: View changes

swentel’s picture

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.

swentel’s picture

Actually, I spoke to fast, while node edit page is now fine with the patch, admin/content (order of css and JS completely) is now broken, so it looks like the sort isn't solving everything.

swentel’s picture

catch’s picture

Adding #3397713: [Performance regression] Starting with Drupal 10.1, some sites hit PHP for every page view due to aggregated asset URL hash mismatch from different order of asset items 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.

googletorp’s picture

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.

googletorp’s picture

Status: Needs work » Needs review
catch’s picture

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 #3397713: [Performance regression] Starting with Drupal 10.1, some sites hit PHP for every page view due to aggregated asset URL hash mismatch from different order of asset items 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).

catch’s picture

Pushed a commit to fix the AjaxPageState failure.

catch’s picture

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.

googletorp’s picture

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.

Also I'm haven't done much Drupal core contribution with gitlab and MRs so unsure who should revolve the question in the MR. Please let me know if I should do anything

orakili’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new9.15 KB

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.

catch’s picture

Status: Reviewed & tested by the community » Needs work
catch’s picture

Title: Logic error in Drupal's lazy load for asset aggregation » Ensure consistent library ordering between page an asset requests when calculating asset aggregates

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

catch’s picture

Status: Needs work » Needs review

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, and there is no obvious override or interdependency between the two files, so it appears to just be bogus.

Removing the unnecessary weight I get a green MR.

smustgrave’s picture

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

smustgrave’s picture

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

catch’s picture

Issue summary: View changes
catch’s picture

Updated the issue summary.

smustgrave’s picture

Status: Needs review » Needs work

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

catch’s picture

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). No error, just doesn't do it.

After some grepping, I discovered this functionality is implemented by ckeditor5 js itself, and that library does not declare a dependency on vertical tabs, even though it does in fact depend on vertical tabs. After adding the dependency that test passes, see how it looks on a full run. The problem was that the ckeditor5 js looks for vertical tabs markup which isn't there unless it runs after vertical-tabs.js.

catch’s picture

Status: Needs work » Needs review

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

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

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

Marking since it will go through 2 committer eyes too.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

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

alexpott’s picture

Issue tags: +Needs change record

After discussion with @catch, 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 #1945262: Introduce "before" and "after" for conditional ordering in library definitions and deprecate weights altogether.

catch’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record

Added the change record.

alexpott’s picture

Status: Needs review » Needs work

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

catch’s picture

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

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

catch’s picture

Status: Needs work » Needs review

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

alexpott’s picture

Oops yeah I must have been. Sorry.

catch’s picture

Status: Needs review » Reviewed & tested by the community

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

catch’s picture

Title: Ensure consistent library ordering between page an asset requests when calculating asset aggregates » Ensure consistent ordering when calculating library asset order

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

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

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:...
alexpott’s picture

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

catch’s picture

Issue summary: View changes
wim leers’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

thefancywizard’s picture

StatusFileSize
new6.76 KB

Rerolled the patch from #21 against 10.3.6. Edited to note - this patch causes a regression with vertical tabs and should not be used, support for vertical tabs was added to the patch in #51.

thefancywizard’s picture

StatusFileSize
new7.73 KB

Added the changes needed to support vertical tabs.

berdir’s picture

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

catch’s picture

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:
quietone’s picture

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

This was committed to 10.4.x so updating version.

tim.plunkett’s picture

From the CR:

If custom or contrib JavaScript is implicitly depending on vertical-tabs.js being loaded very early, this might result in regressions

more like custom, contrib, or core ;)
#3493182: Block visibility settings have summary duplicated in the title

catch’s picture

miiimooo’s picture

Yikes, while I can see the reason for this change, it should come with a big warning sign as it changes the order of assets like CSS and JS files, also the ones loaded in custom themes and modules. Especially CSS specificity which is based among others by order this will break most sites in subtle ways. I just tested this with a bigger site and I'm not sure I'll be able to identify all problems and upgrade to 10.4.

If I'm not alone in this, maybe there could be a backwards compatibility patch or setting that allows site maintainers non-breaking upgrades?

ljenn3451’s picture

As @miiimooo predicted, this broke several of our sites that had multiple custom subthemes. Per https://www.drupal.org/project/drupal/issues/1945262, I added weight to the libraries of my custom themes and it fixed the obvious issues, but we are continuing to monitor for other issues.

example fix in subtheme.libraries.yml:

global-styling:
  css:
    theme:
      css/style.css: { weight: 10 }
mmenavas’s picture

I found more side effects that affect Drupal core. I created https://www.drupal.org/project/drupal/issues/3498100 to report duplicate summaries (similar to https://www.drupal.org/project/drupal/issues/3493182) on vertical tabs in Media, Content Block, and Taxonomy Term add/edit forms. I also provided a patch but I'm not sure the solution I proposed is the best.

natefollmer’s picture

This has changed the way some of our custom modules interact with contrib modules. We're using DataTables with Views and wrote custom jQuery to manipulate some data on page load. Even after adding datatables as a dependency to our custom module the code no longer functions as it did on 10.3.10 (downgraded to confirm). I'm currently debugging to see if I can add any helpful information, but I'd be grateful if anybody has any debugging tips.

Edit: I fixed my original issue, but I don't like how I had to do it. I added a weight of -1 to our custom js and it's now working as it did with 10.3.10.

alina.basarabeanu’s picture

This change broke many of our styling and javascript.
Most of them I was able to fix with a weight but on some pages, the aggregated files do not contain the required dependencies.
For example: Our theme depends on the following
dependencies:
- core/jquery
- core/drupal
- core/drupalSettings
- core/drupal.ajax
- core/drupal.dialog.ajax
- core/views.ajax

But the Drupal.behaviors.ViewsAjaxView is missing from the aggregated files.

bwaindwain’s picture

I'm pretty sure this has caused visual problems in Claro tabledrag. See https://www.drupal.org/project/drupal/issues/3506870

amavi’s picture

Hi :)

I am not dev., I have a problem with 10.2, 10.3 or 10.4.

10.1.x is oki, but I am bloqued, if I try to update up all my Java and CSS seem bloqed on Edge and Opera (menu no work, Accordeon not deploy, CSS diseapear), seem oki with Chrome if I not use the 2 option "Agregated" in performane menu.

Thx.

ccarnnia’s picture

++
on 10.4.6 with aggregate(js,css) turned on the number of `script` tags go down from 84 to 7 !
colorbox js is a quick example of a mission library with aggregate turned on .
same with search autocomplete.
the easiest thing to notice is scripts relevant to admin toolbar in Adminimal theme (the drop down under droplet in top left of toolbar that list actions such as `clear cache` disappears.
still looking ...

simonjoe’s picture

Our 10.4.6 update caused a custom module using Datatables to stop attaching our custom JS file and the Datatable JS file. We had to update our library.yml file to include weights. Same fix mentioned in #58. We have other JS that handled the upgrade just fine.

datatables:
version: VERSION
js:
js/datatables.js:
weight: -20
dependencies:
- core/jquery

jquery-table-search:
version: VERSION
css:
component:
css/commerce_reports_table.css: {}
js:
js/commerce_reports_table_search.js:
weight: -10
dependencies:
- core/jquery
- commerce_reports/datatables

xjm credited longwave.

xjm’s picture

Belatedly adding credits per #46.