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:
- Minimum representation of libraries to include
- Minimum representation of libraries to exclude
- theme
- language
- 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:
- Minimum representation of libraries to include
- Minimum representation of libraries to exclude
- theme
- language
- 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):
- prep_dis_a,
- prep_en_a
- prep_dis_b
- prep_en_a
And one css library (css_dep) with the following dependency:
- prep_dis_b
If you on a page include the libraries in this order:
- js_dep
- css_dep
Then everything will work as expected and what you would see in the header scrip section is
- link to js file for prep_dis_a
- aggregated js with delta 1 (file should include prep_en_a script)
- link to js file for prep_dis_b
- aggregated js with delta 3 (file should include prep_en_b script)
If you reverse the order and include libraries like this
- css_dep
- js_dep
what you will see in the header section will be
- link to js file for prep_dis_b
- link to js file for prep_dis_a
- 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.
| Comment | File | Size | Author |
|---|---|---|---|
| #51 | 3467860-51.patch | 7.73 KB | thefancywizard |
| #50 | 3467860-50.patch | 6.76 KB | thefancywizard |
| #21 | 3467860-21.patch | 9.15 KB | orakili |
Issue fork drupal-3467860
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
googletorp commentedComment #3
googletorp commentedComment #5
googletorp commentedI 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.
Comment #6
smustgrave commentedMR 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.
Comment #7
googletorp commentedComment #11
swentel commentedWe'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.
Comment #12
swentel commentedActually, 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.
Comment #13
swentel commentedProbably related, but not sure #1945262: Introduce "before" and "after" for conditional ordering in library definitions
Comment #14
catchAdding #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.
Comment #15
googletorp commentedI 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.
Comment #16
googletorp commentedComment #17
catchLeft 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).
Comment #18
catchPushed a commit to fix the AjaxPageState failure.
Comment #19
catchIf this is ckeditor5 image tests, it's a random but frequent test failure in HEAD and unrelated.
Comment #20
googletorp commentedI 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
Comment #21
orakili commentedWe 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.
Comment #22
catchNeeds a rebase for merge conflicts in the test.
I think this might provide a solution to #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 too.
Comment #23
catchRe-titling to make it clearer what the issue is.
Comment #24
catchDid 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.
Comment #25
smustgrave commented1 nitpicky comment but can't apply since it's a committer MR.
Comment #26
smustgrave commentedMay want a 2nd thumbs up before marking. But could issue summary be updated also. Solution still seems to be TBD.
Comment #27
catchComment #28
catchUpdated the issue summary.
Comment #29
smustgrave commentedRebased as I can't re-run tests and javascript tests appear to be consistently failing.
Comment #30
catchI 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.
Comment #31
catchApart from one random test failure, that results in a green run again.
Comment #32
smustgrave commentedWill go out on a limb and say this one is ready.
Marking since it will go through 2 committer eyes too.
Comment #33
alexpottI've added couple of comments to the MR that need addressing.
Comment #34
alexpottAfter 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.
Comment #35
catchAdded the change record.
Comment #36
alexpottUnfortauntely testJsAssetsOrder failed in the last run and that's definitely related.
Comment #37
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.
Comment #39
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.
Comment #40
alexpottOops yeah I must have been. Sorry.
Comment #41
catchMoving this back to RTBC since only trivial changes and a CR since #33.
Comment #42
catchThis doesn't only affect aggregate assets, it also ensures the correct order when aggregation is off, re-titling.
Comment #43
alexpottCommitted and pushed 0c9e4a4b8e to 11.x and 4c3bada24c to 10.4.x. Thanks!
Comment #46
alexpottTagging after discussion with @xjm, @catch and @longwave
Comment #47
catchComment #48
wim leersWow, nice!
This is a solid step forwards, next up is #1945262: Introduce "before" and "after" for conditional ordering in library definitions.
Comment #50
thefancywizard commentedRerolled 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.
Comment #51
thefancywizard commentedAdded the changes needed to support vertical tabs.
Comment #52
berdirViews 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
Comment #53
catchI 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:
Comment #54
quietone commentedThis was committed to 10.4.x so updating version.
Comment #55
tim.plunkettFrom the CR:
more like custom, contrib, or core ;)
#3493182: Block visibility settings have summary duplicated in the title
Comment #56
catchComment #57
miiimoooYikes, 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?
Comment #58
ljenn3451 commentedAs @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:
Comment #59
mmenavas commentedI 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.
Comment #60
natefollmer commentedThis 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.
Comment #61
alina.basarabeanu commentedThis 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.
Comment #62
bwaindwain commentedI'm pretty sure this has caused visual problems in Claro tabledrag. See https://www.drupal.org/project/drupal/issues/3506870
Comment #63
amavi commentedHi :)
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.
Comment #64
ccarnnia commented++
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 ...
Comment #65
simonjoe commentedOur 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
Comment #67
xjmBelatedly adding credits per #46.