Problem/Motivation
HTML5 proposes/solidifies asynchronous loading of JavaScript files based on two attributes: defer and async. Drupal has supported defer since at least D6, and support for async is currently in the works (#1140356: Add async, onload property to script tags).
Current behavior (as of 8.6) is that scripts with the defer attribute do not ever get aggregated (even in the case where there are multiple scripts added through one library).
Proposed resolution
We should have a list of attributes that support aggregation.
JS files with these attributes should be aggregated together if they have the same attribute values and we should make sure the attributes are on the bundle.
This means that if a library defines 3 JS files with the defer attribute, we should create a single bundle with the defer attribute.
Remaining tasks
- Related issue: #1140356: Add async, onload property to script tags
- Related issue: #865536: drupal_add_js() is missing the 'browsers' option
- Patch, test, etc.
User interface changes
None
API changes
A new aggregated_js_attributes setting allows expanding the list of attributes that are aggregated separately:
// Default value.
$settings['aggregated_js_attributes'] = ['async', 'defer'];
// Example of adding a new attribute.
$settings['aggregated_js_attributes'] = ['async', 'defer', 'data-custom-attribute'];
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|
Issue fork drupal-1587536
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 #1
iamEAP commentedWrong patch. Disregard.
Comment #2
iamEAP commentedHere's the right one.
This is totally untested. Just posting to get the conversation rolling.
Comment #3
ericduran commentedWhy change this to md5? Lets keep this simple and just add the new item to the group_keys.
Comment #3.0
ericduran commentedFixing history related to defer attribute.
Comment #4
iamEAP commentedIn the existing code, if you came across a JS file that had async, the logic would say, "This set of group keys is different than the last one. We need a new bundle," and would proceed to create a new bundle. Then, if the next file didn't need async, but otherwise had all of the same group keys, it'd say, "This set of group keys is different than the last one, so we need a new bundle." Which would result in three separate bundles when only two were needed. We don't want this.
The md5 wrapped around the serialize (which is a technique I'm borrowing from Views, where they use it to generate Views Cache IDs based on a series of keys), just attempts to minimize the number of aggregates.
Comment #5
iamEAP commentedAlso, I guess I didn't realize drupal_group_js was an API addition to Drupal 8. Utilizing it here might make a backport very murky.
Comment #6
catchMarking this major now that #1140356: Add async, onload property to script tags is in.
Comment #7
ericduran commented@iamEAP drupal_group_js is an addition in D8 but there is currently a D7 backport that is RTBC which is holding up a couple of other drupal js related patch. The issue is #865536: drupal_add_js() is missing the 'browsers' option. Once that gets in all these changes become a lot easier to backport :)
Comment #8
iamEAP commentedForgot the browsers property for bundling.
Setting this back to needs review for more/wider discussion.
Comment #9
arnested commentedI tried the patch in #8 and it seems to group the async scripts in its own group (I have not made a thorough review though).
But what the patch did not do was to actually put the async attribute on the script tag of the aggregated script.
Attached patch adds the attributes (also for the defer attribute) on the aggregated script tags, but is otherwise the same patch as the one in #8.
Comment #10
iamEAP commentedNevermind. Disregard.
Comment #11
yurtboy commentedApplied cleanly
Comment #12
yurtboy commentedApplied cleanly
Comment #13
ericduran commentedDo we have test that test the different aggregation with the new properties?
Comment #13.0
xjmUpdated issue summary.
Comment #14
tim.plunkettI read the issue summary several times, and I don't see how this is a bug. If it is, can the summary clarify what is broken?
Comment #15
iamEAP commentedSuppose you add a JS file like so:
When JS aggregation is disabled, this shows up just fine with the defer attribute set properly. However, once you turn aggregation on, the script you added will almost definitely be bundled into an aggregate without the defer property set. As a result, code you were expecting to fire asynchronously now fires in line. The same is true for the async property.
In other words, your async/defer scripts don't behave as expected if you have JS aggregation enabled.
Comment #15.0
iamEAP commentedUpdated issue summary.
Comment #16
iamEAP commentedUpdated the summary with the above details.
Comment #17
tim.plunkettOkay, that sounds reasonable. Can you copy that into the issue summary?
As @ericduran points out, this should have automated tests.
Comment #17.0
tim.plunkettUpdating summary to describe the bug.
Comment #18
cweagansFixing tags per http://drupal.org/node/1517250
Comment #18.0
cweagansFurther clarification of the problem/motivation/bug.
Comment #19
nod_From what I see it's fixed, anyone can confirm?
Comment #20
mikeytown2 commentedMost likely fixed by #1664602-35: Allow attributes to be passed to drupal_add_[css|js] (SRI)
Comment #27
ivan berezhnov commentedComment #29
andypostComment #31
mherchelPlaying around with this in 8.6. The behavior I'm seeing is that if I set the defer attribute, the JS file will never be aggregated. It will retain the defer attribute.
Even if I have multiple JS files in one library with the same weight and defer attribute, the files will not be aggregated together.
Comment #32
mherchelComment #34
vacho commentedAt this time JS is pluggable, It is solved in this issue #352951 JS & CSS Preprocessing Pluggable so the method drupal_pre_render_scripts not exist and the patch is outside of context.
Comment #35
vacho commentedI found int this file /core/lib/Drupal/Core/Asset/JsCollectionGrouper.php:21
I am not sure about where set 'async' and 'defer' attributes.
Comment #36
drup16 commentedThis seems to apply if you are not using a module like advagg enabled. The module has a feature
Enable preprocess on all JS, which will do the following:Force all JavaScript to have the preprocess attribute be set to TRUE. All JavaScript files will be aggregated if enabled.Comment #38
grathbone commentedI've done a basically 1-for-1 re-roll of the changes onto the new files.
Comment #40
dhirendra.mishra commentedComment #41
dhirendra.mishra commentedComment #43
narendra.rajwar27Comment #44
narendra.rajwar27fix added for test case failure. Patch Applied in drupal 9.x branch.
Comment #45
narendra.rajwar27Comment #46
guptahemant commentedComment #47
guptahemant commentedComment #48
guptahemant commentedMarking this issue as needs work since defer and async attributes are not present on the aggregated JS file.
Comment #49
guptahemant commentedBelow is a fresh approach to resolve this issue.
First I did some research to understand how the aggregation process starts and i found that there is a method named
Drupal\Core\Asset\AssetResolver::getJsAssets()which is responsible for triggering the aggregation, and there itself a condition has been added to make sure that Js libraries with assets will not be aggregated. This was added as part of https://www.drupal.org/project/drupal/issues/1664602#comment-6683702.To resolve this issue:
Drupal\Core\Asset\JsCollectionGrouper::group()function so that it can create aggregation groups for defer and async attributes as well.This is an initial patch which is working good for normal page loads and will need more testing with Ajax behaviours. Also this patch tries to maintain the order of weight in which javascript files have been added as per the previous core implementation.
Please review.
Comment #51
jamesashok commentedPatch to include all JS attributes to be considered while aggregating, grouping for D8.
Comment #53
thallesThis line is broken the tests
Comment #54
thallesComment #55
ayushmishra206 commentedReverted the line causing test failure.
Comment #60
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #61
rishabh vishwakarma commentedAdding patch against 10.1.x-dev
Comment #62
smustgrave commentedThis issue is being reviewed by the kind folks in Slack, #needs-review-queue-initiative. We are working to keep the size of Needs Review queue [2700+ issues] to around 400 (1 month or less), following Review a patch or merge request as a guide.
This was previously tagged for tests and issue summary updates which still need to happen it seems.
Comment #64
bnjmnmComment #65
ambient.impactHere's a quick reroll of #55 against 10.1.x-dev that seems to work for me. #61 did not work for me on 10.1.x, so it might be out of date or missing the `AssetResolver` part of this.
Comment #66
ambient.impactWhoops, looks like the patch doesn't quite work. Hiding for now until I can figure out why.
Comment #67
ambient.impactSetting to 10.1.x-dev to create the issue fork against that.
Comment #68
ambient.impactRe-ordered the attributes in the issue title to alphabetical because it was bothering me.
Comment #69
smustgrave commentedJust fyi though the issue would have to go into 11.x first before 10.x
Comment #70
ambient.impact@smustgrave My bad. Switching back.
Comment #71
smustgrave commentedNo worries! If anyone here didn’t know but 11.x is the “main” branch. Where Drupal10 tags will be made off of. So no longer need to worry about 10.2.x, 10.3.x etc.
Comment #73
ambient.impactHere's a patch from the current draft merge request if anyone wants a snapshot. Feel free to build on this in the merge request - a test to verify that
asyncanddeferattributes are being aggregated (while other attributes aren't) seems like the next step.Comment #74
ambient.impact@smustgrave I noticed that message on issues but didn't realize that using 11.x for 10.x.x versions was was the plan. Thanks for the heads up.
Comment #75
catchI think we should support defer for both CSS and Javascript, so re-titling.
#2695871: Aggregation creates two extra aggregates when it encounters {media: screen} in a library declaration added some unit test coverage which I think could be used as the basis for coverage here.
Comment #76
catchAlso it would be good to actually use the API if we can - Umami has some footer-specific CSS files which look like good candidates. Could we apply it to all dialog CSS (like Claro's) or is it actually possible for dialogs to be part of initial paint? Any others?
Comment #77
catchNo wait, deferring CSS isn't part of the spec, you can only use it on script tags :(. There are techniques for it (like https://web.dev/defer-non-critical-css/) but that's not helpful here. Back as it was then, but still the test coverage can probably be cribbed from.
Comment #78
agoradesign commented@catch you were quicker.. just wanted to write something similar :)
Comment #79
ambient.impactHa, yeah, I was about to comment that I didn't know you could defer CSS with
deferattribute. 😂Comment #80
niklanFaced same problem after upgrade on Drupal 10.1.
This
Become this:
but
works as expected
P.s. oops, looks like you are here fixing attributes problem, mine with async inside code. For those who find this issue but the problem in 'async' inside code, I've created a separate issue #3374660: Update mck89/peast composer dependency to 1.15.2
Comment #81
prudloff commentedIt is also possible to use a
fetchpriorityattribute on CSS and JS. IMHO, assets with the same priority should be grouped together.Comment #82
ambient.impactLooks like my changes in the issue fork have some grouping problems with Drupal 10.1+'s aggregation system in that it seems to result in aggregates sometimes being generated without a file included on one page and then again with all the same contents plus the additional file. This has been biting us in #3414538: Turbo: Implement additive JavaScript aggregation to prevent multiple evaluation where we're trying to get Hotwire Turbo working with JS aggregation on but it's downloading and evaluating (nearly) the same JavaScript, and after a bunch of debugging, I finally realized it was due to us using theEdit: actually, this doesn't seem to be the case, but that I'm dumb and misunderstood a fundamental part of how aggregates are built and from what libraries; see #3411010: Document that aggregation of JavaScript files can cause redeclaration.deferattribute plus this issue fork, because removing the attribute and removing the patch instantly fixed the JS aggregates.Given that there are also other attributes that we may want to group by (e.g.
fetchpriorityas mentioned by #81) and that new attributes may come along later, I propose reworking this as a more generic approach: instead of hard-coding the attributes (i.e.async,defer, etc.), we add a settings.php setting which is an array of attribute names that can be grouped, with the default being['async', 'defer']- this will make it easy for sites to opt into aggregating with other attributes based on their needs and be more future proof.Comment #83
jv24I'm uncertain if this is an active issue still for some. In my case for D10.3, it is. So I've updated for the patch from #73 to match with the latest changes I'm seeing in core.
Comment #84
prudloff commentedI added a test.
But it seems I can't remove the draft status from the MR.
Comment #85
smustgrave commentedLeft a few small comments on the MR
But will need a CR for the settings change. and probably should include in API section too.
Comment #87
prudloff commentedI rebased the MR and added a change record: https://www.drupal.org/node/3526457
Comment #88
catchSites can do that in a site-specific services.yml so it shouldn't prevent that.
Comment #89
smustgrave commentedRan test-only feature https://git.drupalcode.org/issue/drupal-1587536/-/jobs/5897064 which shows the coverage.
Believe the rest of the feedback here has been addressed too.
Comment #90
catchThere's still an open discussion here about moving to a service parameter instead of using $settings.
Comment #91
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #94
anybody@catch in #90 you wrote
and set this back to NW from RTBC. Has this discussion come to a conclusion?
Just ran into this with defer, I'll try finishing this with @grevil then!
Comment #95
catch@anybody no I mean there was an open discussion in this issue, which should have been resolved before the issue was RTBC, since I marked it needs work, that discussion hasn't moved on unfortunately. Maybe 'needs review' would be better though.
Comment #96
catchAlso I'm not really sure the settings/container parameter is even necessary looking at this again.
What we could do instead is allow preprocess with those two attributes (hard coded), but add the attributes to the $group_keys in JsCollectionGrouper. Then if multiple async or defer files appear next to each other, they'd end up in the same aggregate, if they don't, they won't - but they'd still get minification etc.
Moving back to needs work for that.