The majority of the patch was made thanks to a custom codemod, automated replacement are:
| Before |
After |
$('body')
.once('detailsAria')
.on('click.detailsAria', function () {});
|
$(once('detailsAria', 'body'))
.on('click.detailsAria', function () {});
|
const $collapsibleDetails = $(context)
.find('details')
.once('collapse')
.addClass('collapse-processed');
|
const $collapsibleDetails = $(once('collapse', 'details', context))
.addClass('collapse-processed');
|
const $progress = $('[data-drupal-progress]').once('batch');
|
const $progress = $(once('batch', '[data-drupal-progress]'));
|
$(context)
.find('th.select-all')
.closest('table')
.once('table-select')
.each(Drupal.tableSelect);
|
$(once('table-select', $(context)
.find('th.select-all')
.closest('table')))
.each(Drupal.tableSelect);
|
const $timezone = $(context).find('.timezone-detect').once('timezone');
|
const $timezone = $(once('timezone', '.timezone-detect', context));
|
$(this).removeOnce('big-pipe');
|
$(once.remove('big-pipe', $(this)));
|
const $configurationForm = $(context)
.find('.ckeditor-toolbar-configuration')
.findOnce('ckeditor-configuration');
|
const $configurationForm = $(
once.filter('ckeditor-configuration', '.ckeditor-toolbar-configuration', context)
);
|
$('<div class="color-placeholder"></div>').once('color').prependTo(form);
|
$(once('color', $('<div class="color-placeholder"></div>'))).prependTo(form);
|
$('.color-preview')
.once('color')
.append(`<div id="gradient-${i}"></div>`);
|
$(once('color', '.color-preview'))
.append(`<div id="gradient-${i}"></div>`);
|
if ($('body').once('contextualToolbar-init').length) {
initContextualToolbar(context);
}
|
if ($(once('contextualToolbar-init', 'body')).length) {
initContextualToolbar(context);
}
|
$context
.find('#filters-status-wrapper input.form-checkbox')
.once('filter-editor-status')
.each(function () {});
|
$(once(
'filter-editor-status',
'#filters-status-wrapper input.form-checkbox',
context
))
.each(function () {});
|
editors = $(context).find('[data-editor-for]').findOnce('editor');
editors = $(context).find('[data-editor-for]').removeOnce('editor');
|
editors = $(once.filter('editor', '[data-editor-for]', context));
editors = $(once.remove('editor', '[data-editor-for]', context));
|
$context
.find(selector)
.removeOnce('fileValidate')
.off('change.fileValidate', Drupal.file.validateExtension);
|
$(once.remove('fileValidate', $context
.find(selector)))
.off('change.fileValidate', Drupal.file.validateExtension);
|
$(context)
.find('.js-filter-guidelines')
.once('filter-guidelines')
.find(':header')
.hide()
.closest('.js-filter-wrapper')
.find('select.js-filter-list')
.on('change.filterGuidelines', updateFilterGuidelines)
// Need to trigger the namespaced event to avoid triggering formUpdated
// when initializing the select.
.trigger('change.filterGuidelines');
|
$(once('filter-guidelines', '.js-filter-guidelines', context))
.find(':header')
.hide()
.closest('.js-filter-wrapper')
.find('select.js-filter-list')
.on('change.filterGuidelines', updateFilterGuidelines)
// Need to trigger the namespaced event to avoid triggering formUpdated
// when initializing the select.
.trigger('change.filterGuidelines');
|
// Keep the jQuery find because of sizzle selector.
$(context)
.find('table .bundle-settings .translatable :input')
.once('translation-entity-admin-hide')
.each(function () {});
|
// Keep the jQuery find because of sizzle selector.
$(once('translation-entity-admin-hide', $(context)
.find('table .bundle-settings .translatable :input')))
.each(function () {});
|
$('.js-click-to-select-trigger', context)
.once('media-library-click-to-select')
.on('click', (event) => {});
|
$(
once('media-library-click-to-select', '.js-click-to-select-trigger', context)
)
.on('click', (event) => {});
|
const $view = $(
'.js-media-library-view[data-view-display-id="page"]',
context,
).once('media-library-select-all');
|
const $view = $(once(
'media-library-select-all',
'.js-media-library-view[data-view-display-id="page"]',
context
));
|
$('.js-media-library-item-weight', context)
.once('media-library-toggle')
.parent()
.hide();
|
$(once('media-library-toggle', '.js-media-library-item-weight', context))
.parent()
.hide();
|
$('body').removeOnce('copy-field-values').off('value:copy');
|
$(once.remove('copy-field-values', 'body')).off('value:copy');
|
$(`#${ids.join(', #')}`)
.removeOnce('copy-field-values')
.off('blur');
|
$(once.remove('copy-field-values', `#${ids.join(', #')}`))
.off('blur');
|
this.$el
.find(`#toolbar-link-${id}`)
.once('toolbar-subtrees')
.after(subtrees[id]);
|
$(once('toolbar-subtrees', this.$el
.find(`#toolbar-link-${id}`)))
.after(subtrees[id]);
|
initTableDrag($(context).find(`#${base}`).once('tabledrag'), base);
|
initTableDrag($(once('tabledrag', `#${base}`, context)), base);
|
$('table')
.findOnce('tabledrag')
.trigger('columnschange', !!displayWeight);
|
$(once.filter('tabledrag', 'table'))
.trigger('columnschange', !!displayWeight);
|
const $forms = (contextIsForm ? $context : $context.find('form')).once('form-updated');
|
// Replace
const $forms = $(once('form-updated', contextIsForm ? $context : $context.find('form')));
|
const $source = $context
.find(sourceId)
.addClass('machine-name-source')
.once('machine-name');
|
const $source = $(once('machine-name', $context
.find(sourceId)
.addClass('machine-name-source')));
|
// Don't replace.
_.once(() => {});
CKEDITOR.once('instanceReady', (e) => {});
|
// Don't replace.
_.once(() => {});
CKEDITOR.once('instanceReady', (e) => {});
|
Comments
Comment #2
nod_Tests failures needs to go to 0 :)
Comment #4
nod_Let's try this.
The patch is not clean, it includes the parent patch and the codemod I thrown in there to do the update. We should put the codemods in a better place and make it easy for contrib to use (like the upgrader module).
There are a few uses that can't be automatically updated.
There are optimisations we can do for certain usual forms like
$(context).find('selector').once('toto').each(callback);let's just see how much it's breaking things.Comment #5
nod_Comment #7
nod_Mainly deprecation, a couple of actual regressions
Comment #8
droplet commentedYou're my hero.
Codemods should kick to Github (as your own project? or something else). You always need a faster patch & newer version for your older Drupal version. And that's more possible to accept other libs' codemods.. (eg. Zepto)
Comment #9
nod_Yeah I was planning on putting something over on GitHub to start with. Kinda short on time these days :p
Ideally we'd have other people do
npx drupal-upgradeand that makes the magic.Comment #10
nod_no more deprecations, let's see what we broke.
Comment #12
nod_that went well :) small workaround in the interdiff until I update the lib: https://github.com/theodoreb/once/pull/3
Comment #14
nod_update after the change to once.filter interdiff not so useful but it's there in case.
Comment #16
nod_subltly broken things fix on the node preview.
still an issue with the media widget. There is a strange gray background around media fields, no idea where it comes from. Haven't looked closely at the media JS.
Comment #18
droplet commentedI didn't perform any real test but i think error around this line (missing $menu on patched version):
https://git.drupalcode.org/project/drupal/-/blob/9.2.x/core/modules/medi...
that can't pass `context` to $.fn.find BTW
https://api.jquery.com/find/
also, query `a` is a bad practice, should assign `.class` to links (I will file the issue soon when I have time to perform a UI test. Or anyone can help?)
Comment #19
droplet commentedAcutally what I afraid is the below style became a bad example for contributors.
Don't:
$(once('toolbar', '#toolbar-administration', context)).eachDo:
once('toolbar', '#toolbar-administration', context).forEachCustom ESLint rule will work. Just not sure how much the community can handle...
#3178115: [policy, no patch] Maintenance of JS in core
#3176918: [policy, no patch] Publishing / Maintaining JS libraries produced by Drupal that do not have a dependency on Drupal
Comment #20
nod_Agreed, was going to replace them manually later on. Started with the bigpipe one in the last interdiff. Wanted to have green tests before I change things manually too much.
Comment #21
nod_thanks for the find, let's try this.
Comment #22
nod_Looking good!
Comment #23
nod_Did a first manual pass at fixing obviously inefficient replacements.
I didn't touch the
$(once()).on()because we don't have anything better to replace it with. Didn't mess too much with the selectors either because sometimes it's not sure that the jQuery object is a single elementI'd rather deal with that later on.
Comment #25
nod_forgot to replace a "this"
Comment #27
nod_last this missing.
Checked the loading perfs of before/after the patch. Thanks to the benchmark we already know once is faster than the jquery version, it holds up in real life even with quickedit doing a lot of once calls. I don't have formal numbers, its just slightly faster.
Comment #28
nod_Updating the lib after some hardening on parameter validation.
Comment #29
nod_Patch without the once lib. Should be green once parent patch is in (remove all the vendor/assets files from this patch)
Had to reroll for #3083051: Refactor tabledrag when core issues are resolved
other than that, no changes.
I still have the jscodeshift deps in this patch, we probably won't keep it on commit though.
Comment #31
nod_Moving to a merge request for easier review/feedback.
Comment #32
nod_Comment #33
nod_Comment #34
nod_Tagging because we talked about having integration tests for the once library. Leaving as need review because there is still a lot of code to go through independently of the tests
Comment #35
droplet commentedI'm thinking of...
Comment #41
nod_HOW TO REVIEW
There are 2 branches:
Merge request from
fork:3183149-manual-adjustments → fork:3183149-auto-replace(for the diff between auto fix and manual changes)Merge request from
fork:3183149-manual-adjustments → core:9.2.x(for core commit)The human code is on the branch 3183149-manual-adjustments There is a merge request created between 2 branches in the fork to facilitate discussion on what additional changes are needed on top of automated replacements: https://git.drupalcode.org/issue/drupal-3183149/-/merge_requests/1/diffs
Comment #42
droplet commentedComment #45
nod_Comment #46
nod_taken care of #42 as part of this issue
Comment #47
nod_Comment #48
droplet commentedTips for review:
- You could turn off `Show whitespace changes`
- non-ES6 file is easier for review. (better diff view)
Comment #49
droplet commentedI don't know if I can do it or not. I submitted reviews on gitlab directly.
https://git.drupalcode.org/project/drupal/-/merge_requests/391
Comment #54
nod_Comment #55
nod_Any takers for a review? let me know if you need time to talk it through directly or need more details about something.
Comment #57
nod_Comment #58
nod_Comment #61
nod_Comment #62
nod_Comment #63
nod_Opened #3207782: Add BC layer between @drupal/once and jQuery.once to address BC concerns I wouldn't postpone this one, we already tried a few things for BC in the different once issues, there are several solutions that work, just a matter of agreeing on one.
Comment #64
nod_going at it step by steps. Addressing #3208051: Use @drupal/once for the block module first to add some integration testing for the once library.
Comment #65
droplet commentedthis patch ready to go I think..
files without forEach, 101% safe to go
file with forEach, 99% safe to go.
Comment #66
nod_I feel it's ready too, as you may have guessed it's not a technical issue that is holding this patch up. It's just too big for someone that hasn't followed it from the start, so after talking with other core committer we decided to split a few pieces of "reviewable" size to let others get a sens of what happened here.
I'll spend time with justafish to go through the changes, and I'll answer questions until the rest of the core committers are comfortable with committing this patch. Large JS changes are still somewhat scary because we don't have many tests and most people are not familiar with the whole codebase, so it's an opportunity to get more people onboard :)
I know it's not going as fast as we'd like but looking back at the last 6 months, there's been some pretty significant changes already (on a personal note, I'll soon be able to contribute to core full-time so that could help). We're getting there, slowly but surely, going slowly here will make it easier for the other big patches that are sleeping in the queue :D
Comment #67
droplet commented@nod_
You've already done a great job!
Everywhere (, not just Drupal) suggested not send large patches, I can understand it. But sometimes, to split issues and reviewed by the SAME person or people who don't really understand code. We just making ourselves feel good.
If we have snapshot tests, we may able to perform `Drupal.attachBehaviors(document, drupalSettings);` twice and compare the DOM. It should be no changes for the second call.
(only worked for CORE & good usages BTW)
I'm a bad guy as usual. I always suggested not to teach reviewers how to review a patch. Because we will lead them into our path (into the trap). We need blind and smoke tests. However, the below hints may help you to get a quick start:
This patch is big but to review it is easier than you think.
Do:
- Understanding of how the jQuery.fn.each & [].forEach works first. (IMPORTANT)
- turn off `Show whitespace changes` on Gitlab or your editors
- review non-ES6 file (less diff)
- review non-forEach first (almost zero code logic changes)
- drop notes for unclear points and go back (don't block your flow)
Comment #68
chi commentedGiven that Drupal 10 is dropping support for IE11 will we eventually switch to event listeners with once option and deprecate once.js?
Comment #69
nod_It's not strictly the same, once uses are not always about event listeners.
In your example they don't do the same thing, with once you bind an event listener once but it triggers every time you click on the element, with the code using the once option from the event listener, the event can be bound multiple times and will only trigger once.
So I don't see us retiring the once library as long as we rely on behaviors to initialize JS code.
Comment #70
chi commented@node_ you are right, the last example does a whole different thing.
I wonder if it's possible to migrate from behaviours to event based JS initialization. That would make custom JS code less verbose and less Drupalized.
Something like this.
Comment #71
nod_have a look at #2367655: Control the list of behaviors run by Drupal.attachBehaviors and #1446166: Use JS events instead of Drupal.behaviors to see previous discussions. This is starting to become off topic though, so let's stick with the once thing here and discuss this in a separate issue, thanks.
Comment #73
nod_updated to 9.3.x
Comment #74
mglaman@nod_ would the next step be identifying all of the child issues that need to be created, like #3208051: Use @drupal/once for the block module?
Maybe we could have a list in issue summary so folks to create them? I missed the block issue at a quick glance.
Comment #75
nod_the block issue was a spin-off to make onboarding easier on this patch. The plan is not to create one issue per module otherwise it'll never end. This patch has been green for 7 months already so just trying to make it happen ASAP :)
Tests are in #3207782: Add BC layer between @drupal/once and jQuery.once
Comment #76
nod_Let me know if you want me to walk through the patch. Currently I need someone who'll be able to RTBC this so happy to spend time with folks to talk it through.
Comment #77
jptarantoI'm about 70% through. I've been going through each .es6 file change. There's a huge amount here and so far so good.
Dare I ask how much of this can/has been tested?
Comment #78
nod_Well what you see in the IS, I wrote the codemod to automate the change, and each transformation has been written carefully to not replace things that shouldn't be replaced.
As far as manual changes go, I did test every change to check that the result was similar to previously (that's how I caught the problems with
thisin the view and ajax files).There is also the fact that a bad patch does break some tests so there is at least some form of coverage. A few months ago I ran the Drupal tests for the JS coverage, the result is here: https://lonely-fifth.surge.sh/index.html it's surprisingly good, more than I expected. We added some more tests lately so coverage is even better.
Comment #79
jptarantoOk! Look I've gone through each change line by line basically. Again, It's a mammoth effort.
Visually the code looks fine and it's a 1:1 replacement across the board, which is the right approach.
+ It terrifies me how embedded jQuery is in absolutely everything.
Comment #80
jptarantoComment #81
droplet commentedFinally, we've seen another review :) @jptaranto!!!
I haven't reviewed the latest changes since my last comment. All other files were fine at that point. Hope it will give a little bit more faith to maintainers also. Cheers.
Comment #82
nod_Addressed lauriii comments.
Comment #83
droplet commentedI go back to review the changes since my last review.
Code Logic looks fine to me. Other formatting or docs issues are not my first priority.
Comment #84
lauriiiCommitted 8a40e42 and pushed to 9.3.x. Thanks!
Comment #86
nod_credit from #3208051: Use @drupal/once for the block module
Comment #88
cilefen commentedComment #90
chetansonawaneComment #91
chetansonawane