Problem/Motivation
The once feature is used in almost all core behaviors. The once feature is implemented as a jQuery plugin, making it impossible to remove jQuery from core scripts.
Proposed resolution
Implement a version of the once feature that does not depend on jQuery
Remaining tasks
Write the codeBenchmarks to validate the improvement:benchmarkFill in the change noticeCreate a change notice for the element.matches polyfillHave jsdoc for all functionsMake the new once feature available as a standalone library that follows the requirements outlined in #3176918: [policy, no patch] Publishing / Maintaining JS libraries produced by Drupal that do not have a dependency on Drupal
Dependency Evaluation
https://www.drupal.org/project/once is considered Drupal Core, there are no runtime dependencies. For dev dependencies see #3199444: Dependency evaluation
User interface changes
None
API changes
- Create a new
core/oncelibrary - Introduce the
onceglobal variable to eslint config - A new library with 4 functions:
once,once.filter(equivalent to jQuery.fn.findOnce),once.remove,once.find(new function not previously possible with the jQuery implementation) - When once is called on elements it add a new
data-onceattribute to each element that holds all the once ids called
Release notes snippet
The once library has been added to replace the functionality provided by for jQuery .once function. The change record for the once library details how to use the new functionality.
Before
# mymodule.libraries.yml
myfeature:
js:
js/myfeature.js: {}
dependencies:
- core/drupal
- core/jquery
- core/jquery.once
# js/myfeature.js
(function ($, Drupal) {
Drupal.behaviors.myfeature = {
attach(context) {
const $elements = $(context).find('.myfeature').once('myfeature');
}
};
}(jQuery, Drupal));
After (removing jQuery dependency)
# mymodule.libraries.yml
myfeature:
js:
js/myfeature.js: {}
dependencies:
- core/drupal
- core/once
# js/myfeature.js
(function (Drupal, once) {
Drupal.behaviors.myfeature = {
attach(context) {
const elements = once('myfeature', '.myfeature', context);
}
};
}(Drupal, once));
Release notes snippet
This adds the core/once library, a standalone library that offers the same benefits as core/jquery.once but without the jQuery dependency.
Original report by droplet
>Inspired from http://eleks.github.io/js2js/. Now I totally rewritten jQuery.once into Plain JavaScript Code. Aiming to provide a modern pattern widely used everywhere, not just the Drupal way.
Features / Changes:
- Remove dependency on jQuery.
- Use HTML5 data-* attribute (not the jQuery.data()). It's better for debugging and work with other scripts. eg. You now able to query it directly: document.querySelectorAll(["data-drupal-once"]))
- Performance improvements
- Fully pass jQuery.once testcases
- Light weight & More easier to adopt other libs. eg. Converting to jQuery Chaining way: https://github.com/KayLeung/dropletOnce/blob/master/jquery.droplet.once.js
Performance testing result:
https://docs.google.com/spreadsheets/d/1KXVKZS-HJhbQFgUYUmzPzMpJpEMOPXyG...
** Noticeable performance diff on iPhones.
Testing Repo:
https://github.com/KayLeung/dropletOnce
The reason I do not like jQuery.Once 2.0:
- jQuery adding overhead. Including libs size and execution time ( I'm doubt the performance improves between jQuery Once 1.0 .class way and Version 2.0 jQuery.data on modern browsers )
- No standard identify for themer (.once-class)
- No visual debugging info for themer ( You can't see the Onced elements in devtools. You must use JS to loop over to see if that elements if Onced or not. This is not friendly for HTML/CSS themers)
- One way synced only. jQuery.Once 2.0 use jQuery.data to track Onced elements. It will not synced back to HTML DOM.
- Not a modern way. Popular frameworks use standard HTML5 data-* attribute. Not the jQuery.data()
| Comment | File | Size | Author |
|---|---|---|---|
| #171 | core-once-2402103-171.patch | 26.19 KB | nod_ |
| #169 | interdiff-154-169.txt | 42.77 KB | nod_ |
| #169 | core-once-2402103-169.patch | 26.27 KB | nod_ |
| #154 | core-once-2402103-154.patch | 39.07 KB | nod_ |
| #149 | interdiff_147_149.txt | 1.11 KB | anmolgoyal74 |
Issue fork drupal-2402103
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:
- 2402103-rewrite-jquery.once-remove
changes, plain diff MR !22
Comments
Comment #1
droplet commentedComment #2
droplet commentedAdding testcase and making it 100% pass.
Comment #3
droplet commentedComment #4
droplet commentedComment #5
droplet commentedComment #8
nod_A patch maybe? It looks good. Wanted to use data attributes during the rewrite of jquery.once.
Comment #13
avpadernoComment #15
bnjmnmThis is a simple test that verifies once() is working properly. For the coverage to fully confirm that the once() replacement is working It would also need to cover once's
findOnce()andremoveOnce()functions.Another patch is attached that is an attempt at replacing jquery once with droplet once. This exposed several things things that are needed before this can work:
removeOnce()functionfindOnce(), so consequently the shim does not have an equivalent.Comment #16
nod_Comment #17
nod_with the findOnce code.
Simplified the code a bit, sorry @droplet, removed the comments to make it more readable. Everything seems to be working.
once file:
once jquery file
( edit ) messed up the once.remove function in the patch, use the one from the comment instead.
Comment #18
nod_went ahead and fixed date.es6.js which had some old school once processing. put the .es6 files to make it work on IE11 (so I can test the date polyfill)
Comment #19
droplet commentedIf that's not an issue, I'm okay to drop my copyright and move it into Misc folder.
A better name than dropletOnce :)
Comment #20
jibranPatch looks great. I'm so excited to see this change happening in core.
I think we should add a test for
once()just with Vanilla JS.This can be a nightwatch test now.
Comment #21
shaalComment #22
catchBumping to major since this blocks a lot of the work in #3052002: [meta] Replace JQuery with vanilla Javascript in core.
Comment #23
catchThere's a patch here :)
Comment #24
larowlanFor my reference: is there a reason we use hasAttribute/setAttribute/removeAttribute instead of interacting directly with element.dataset ? According to https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/da... that's supported in all the browsers we need.
I'm fixing the tests now.
Comment #25
larowlanFixes the tests and adds a test-case for Vanilla use of the once library
I'm not a fan of nightwatch, so didn't touch that.
More of a jest fan myself, shameless plug: https://www.npmjs.com/package/twig-testing-library
Comment #27
larowlanComment #29
larowlan@flxa and I spent some time debugging the fails
The issue is that droplet.once expects a NodeList but sometimes its a jquery object, and sometimes we receive the whole dom document,
flxa is working on making sure we do some casting there, we'll probably have to add a deprecation notice too.
Comment #30
flxa commentedSeems like the console error is popping up because one of the "elements" is actually the full #document. This filters that out and normalises the jQuery objects into arrays for `once`, `once.remove` and `once.find`.
Comment #32
nod_I'd rather not have anything related to jQuery in the new once script, if we need to call something specific it needs to be done in the jquery.droplet.once script. the once lib should take an array-like object or an single element (todo) as input and always returns either an array or a single element as output.
Also a general clean-up is necessary, renaming files, adding comments, finding the right place to put the script because here it's in the assets/vendor directory but the code doesn't come from a github repo like the original jQuery.once script does.
Didn't use dataset because at the time it wasn't supported by all browsers we needed but you're right that we can update that too.
Comment #33
larowlanYeah we came to the same conclusion
Taking those bits on
Comment #34
larowlanHow would this work in the single element scenario if the element was already executed
e.g.
once(document.getElementById('some-id'), 'some-once').appendChild(document.createElement('div'))In the scenario where this runs a second time, the return can't be chained.
This still to be done (if its worthwhile).
Here's the switch to a file in misc and removal of the droplet naming 😿 and license stuff.
Seeing some tests passing locally.
Interdiff is from #25
Comment #35
droplet commentedWhen I code it, no reason and no choice :)
But actually it makes diff
-
setAttributeis still more performant thandatasetas I known. (not noticeable and able to skip I bet, that's around 2000 calls max in Drupal only)-
setAttributeis friendly for PHP developer (and JS beginners) at this case. (more predictable, without thinking)Comment #36
nod_can we avoid multiple returns?
need to update eslint config to add the "once" variable to the global scope
was missing before too but we should probably check for spaces and other unconventional characters in the id. Also like droplet said, this part is less clear than with setAttribute
find returns an array, so we don't need to use the array prototype
once returns an array so we can call forEach directly on the return.
Comment #38
larowlanHere's a patch that
$(document).once(), off-canvas dialog uses$(window).once(). This was the source of some of the test-fails, as these objects don't support dataset or hasAttribute/setAttribute/removeAttribute. I realise there's no easy way to debug that set (e.g. via inspector) but there isn't now in those scenarios either. Manually tested off-canvas, node preview and media library and all seem to be working now.Comment #39
larowlanFYI support for
Setas used here is good in all the browser we're interested in - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global...Comment #41
droplet commentedI'd say off node is an inappropriate usage. shift them to HTML or body, can't?
One reason I shift it to data-attr instead of a plain object (which is a better performance) is to provide themeable features via CSS. (And able to once via PHP)
Even no code standard that forcing to do it but off canvas should do Once here and missing detach. (2 bugs)
Same here, should registered on the container. and `.node-preview-container` should be named`.node-preview` also (2 bugs)
Comment #42
droplet commentedIn other word,
the Once elements that removed from the node, anything related to that Once should be disappeared.
Comment #43
larowlanYes, but we have to maintain BC. We can fix core calls - but we can't know what things are done in contrib and custom code that will break if we force it to only use node elements. So I think we have to keep supporting that, but we need to trigger a deprecation error to force people to use it on elements instead. I think here is the place to do that too.
Comment #44
droplet commentedI preferred to remove it and no BC. (this is a bug more than a feature)
thoughts?
Comment #45
nod_the non element set has a little problem, we only track the element, not the id it is called with:
JS in Drupal has always been very dom-based. Because for what we do with it, the DOM is fast enough, especially these days.
One of the concern when we move to jquery once instead of using the
processedclass names was that information about what was processed and what was not lived outside the dom in a jquery-specific part. Having a data attribute with this information brings us back to having all the data in the DOM, I would still not tell people to use it for styling because that is not the goal (add that class in the js that call the once function), the goal is to expose the information about what has been processed in an easily accessible way*.About the window/document issue: both should be replaced with
document.documentElementbecause we're not doing SPA in drupal, the window == the document == the page. For SPA, just don't use window/document in the once function, easy. The deprecation can be added to the jquery extention only.Thinking about it the initial call to attachbehaviors is wrong. Everything uses jquery so we don't see it but instead of
Drupal.attachBehaviors(document, drupalSettings);it should beDrupal.attachBehaviors(document.documentElement, drupalSettings);because that's what it is that's for another issue though.Haven't looked at the test failures. Ran eslint, updated the patch with the aliasing method, remove the Set part, started to add the jsdoc, deprecation notice need some work. interdiff is a bit rubbish because of the move but essentially we're back on the previous version of once.js, and the jquery.drupal.once file has a new method.
* I'll try to get all that in a single data-once attribute because I'm not a fan of having too many data-once attributes.
Comment #47
nod_Patch that puts everything in data-once attribute. Can still use css with the
~=attribute selector for exemple:[data-once~="contextualToolbar-init"].Also, how do we go about deprecation warning in the testbot?
Comment #49
larowlanWe fix the usages in core :)
All of those 38 fails are deprecation errors, so if we fix the usages in core to not use window or document this should be green 🎉
This is looking nice @nod_
Comment #50
droplet commentedregex missing `+`
- what if we take `checkId` away and let the error happening. This is the only place in Core JS doing the args checking I think.
And should we check it or generate a valid ID from it?
And should we do further limitation? for example `/^[a-z0-9-]+$/` only?
- Should we make Once `elements` accept `.selectors` also?
- On the `once.remove`. Using regex replace is simpler
- `once`, I preferred to code like below, hehe
Comment #51
nod_We do have arguments checking in core, see Drupal.ajax :)
We should keep checkId. the function shouldn't change the id to a "safe" subset or generate one, if it's too forgiving it'll be a pain when we want to deprecate or fix a bug somewhere. I took the regex from a DOMTokenList polyfill, it follows the same rules as css class names like this.
we should fail on empty ids, you're right.
I don't want to allow selectors because then we'd need to allow passing a
contextelement, and then it's just a shortcut for context.qSA and doesn't add anything. I'd rather keep this thing specialized and if we need some syntactic sugar on top we can put that in Drupa.whatever.Removing with the regex leaves some extra spaces so if we add/remove a lot, we'll have a lot of extra spaces. and running a
replace(wsRE, ' ')would work, but it doesn't feel right.I prefer your code too there, though I would go with splitting an array again. Was on the fence mixing regular dom methods and browser specific implementation of reading/writting attributes.
To get around the deprecations warning, because I don't really want to touch all those modules and files we can do an argument checking in the once methods and let the aliasing on the jquery side. meanging we support using window/document when you go through jQuery but not when you use the vanilla lib. Would that be ok?
Comment #52
droplet commentedHmm...
Acutally I wonder if `wsRE` is correct or not also.
Then, can we remove `values.includes(dataId)`. `once.find` does the job already.
Technically, a loop to remove is required since it puts the value into a single once-data.
may perform a simple benchmark between the last few approaches and take the balance.
Comment #53
nod_was removing the .includes() in a new patch :)
Added a little something to prevent duplicate values that would 'break' the remove function. babel add some polyfills to the script but still lighter than jquery :p
Created a stub for the change notice to have an URL to point to.
Changed a bit the patch to deprecate the existing lib instead of removing it right away. Got rid of the deprecation notice in the jquery interface because, well either we use jQuery or we don't and when we update the code to not use jQuery we'll see that using once on window/document will not work. Safer than trying to update the code of other modules in this patch. we can deprecate the jQuery interface in 9.2.x once the rest of core doesn't rely on it.
Trying to get that in rather quickly so that claro and especially olivero can use it before getting in core.
Comment #54
nod_just moved the declaration of drupal.once in the right alphabetical place in core.libraries.yml
Comment #55
droplet commentedGood idea to use Set... BUT the bad news is IE11 doesn't really support Set.
in IE 11:
Comment #56
nod_IE11... there was another issue with the .matches method, added a polyfill for it. Now it works on IE9+.
Comment #57
nod_forgot the polyfill in the patch
Comment #58
droplet commentedno strong demand but if we want to standardize it a bit and no extra function call outside the loop:
I will drop the Javascript destructuring if extra polyfill matters/required. It's a private method, passing extra args each time won't hurt.
EDIT: maybe exporting the token function also, somebody may want to re-use it.
Comment #59
nod_I don't think we'll handle more than 20 items in this token list, iterating over it 2,3, or 4 time doesn't matter much compared to using the dom for filtering and storing the data.
What's left I think is:
Then we're good to go no?
Comment #60
droplet commentedI benchmarked it on another day, much faster (on large dataset).
module page has many divs `admin/modules`
I'm leaving now. My code is better than my English. I will go back soon if no one reviews it.
Comment #61
nod_Ok I see it now, I'll update the patch. Removed the foreach for updating the attributes so we go over the elements only once, not twice like before.
I'm making a benchmark on jsperf to try out a few things and to find the threshold when the perf impact of using the dom becomes an issue vs jquery.
Comment #62
droplet commented@nod_
sorry if there's misunderstood. the remaining work is doc, so I can't help as usual :)
the engine behind jsperf
https://benchmarkjs.com/
BTW, on the benchmark, looping once vs 5 in #60 is not linear growth.
Comment #63
nod_once seems better than the jQuery once surprisingly:
https://www.measurethat.net/Benchmarks/Show/8844/1/drupal-once-variables...
I'll see if I have time to find out how many elements we use it on average.
Saw that it's not linear, we can tweak the parameters of the test in the url above to find how it behave in various situations:
Comment #64
droplet commentedgenerally, each Once has no cache in global, even a re-call is no cache at all. So if we're going to find out the real env, it should not cached also.
Comment #65
droplet commentedit may help you to make a decision:
https://github.com/GoogleChrome/lighthouse/blob/master/lighthouse-core/a...
on my PC takes 14ms for 3000 divs
it's 286 Once in total in DrupalCore. The bad case, we called them all at once and 10 times, we still keep it close to 60fps I think..
(damn, my old iPhone 8 Plus is twice faster than my PC (AMD 3900x) on #63 test)
Comment #66
nod_Updated the test to query the elements in each loop :)
Thanks for the lighthouse link, never had the curiosity to look into it so it's educational. And yeah the iphone JS performance is ridiculous, so much better than any phone by far as well as most desktop.
Comment #67
nod_Comment #68
nod_Comment #69
nod_Comment #70
nod_Could use some help with review of the change notice https://www.drupal.org/node/3158256
also the library is called core/drupal.once but it's not in the Drupal namespace and I don't think it should be, might change it to core/once to reflect the actual implementation.
Comment #71
nod_renamed the libs
core/onceandcore/once.jquery. The name works this way because it's the jquery interface of the once library, not the jquery extension once. Updated the IS and change notice.Also I changed the weight of the once.jquery file so that is it added after the jquery.once file, That way if both libraries are on the page everything uses the new library.
Comment #72
nod_Now we only need the jsdoc comments for the functions.
Comment #74
nod_sorry my bad had some test code leftover.
Comment #75
droplet commentedWhy styling with Once data-attr is bad? Do you suggest the below usage or other thoughts? (I've read comment #45 already)
Enforce the code quality with the code is the best thing.
Acutally we should code like this: (GOOD)
But Drupal CORE code standard/current usages are: (BAD, Extremely BAD)
This is wrong:
So some concepts like decouple can't apply to here.
data-attr is act as a state class on BEM
** This is about the Styling only. js-prefix for JS selectors is another story. (Also many bad js-prefix-selector usages)
If we do above and the `.processed` still left. Is it correct or expected?
Comment #76
nod_so for the styling, flashback from 6 years ago #2348321-26: Upgrade to jQuery Once 2.x, we could argue that it's a code quality issue in a specific project… But we don't want to make it easy for people to do the bad thing.
In core not all the once calls are followed by an addClass, so I would think there is often a different way of doing the styling. What would it simplify to use data attributes for styling? the JS side or the CSS side?
For the code I wouldn't use jquery for class changes but yes essentially if a class is needed I would do:
For the styling I don't have a clear idea of how it should look like, just that is shouldn't be tied directly to the data-once attribute.
for the remove we can add that to the detach behavior to do:
Comment #77
nod_first pass at the jsdoc. Need help from good english writers :)
Comment #78
nod_Follow-up #3160052: Initial argument passed to Drupal behaviors is incorrect, looks scarier than it is.
Comment #79
droplet commented#76
Technically, scripts needed extra HTML class (especially, on the component top wrapper) should not use Once. Fully decoupled.
But if you tried to make it depends on Once (, that helping to reduce the repeated code), that's no reason not to reuse it. Once & Script is a couple now.
Comment #80
nod_That make sense, I removed the sentence about not using data-once for styling from the change record.
Comment #81
bnjmnmThis provides some jsdoc revisions as requested in ##7 I did a little bit of function and variable renaming that I thought would help readability.
There was one bit I was tempted to change but didn't:
I wasn't sure what the intent was having this as a variable since this isn't an overridable class. It seems like it would be easier for a contributor to understand if the attribute name is used directly instead of
attrName, but this seemed intentional enough that I didn't want to change it.Comment #82
nod_Thank you! names are better like this agreed.
Yes in a variable is intentional, I'm not loosing hope that we'll minify core JS at some point, then it'll help… trivially :p.
Makes it easier to change it, like you did :)
Comment #83
nod_Commenting the interdiff:
It's a detail but array-like is on purpose, the following works and will be treated like an array (this is how a jQuery object is built btw):
this is not accurate. The file is the core/once.jquery lib, it's a wrapper of core/once for jQuery
Looks much better now, updated the change notice with the new attribute name. Good to go for me :)
Comment #84
nod_Comment #85
droplet commentedelement is better IMO.
checkElement can be removed also. We need not check everything unless the top layer passing mixed types unpredictably (exception) or a security issue.
Comment #86
droplet commentedBTW,
haha, I don't want over-engineering but.... I think we can swap the args. It's more close to the jQuery.once
and we able to set a default value for `elements` in `once.find`...
The Once ID is equal/very smiliar to HTML ID, I think.
If we promoted named params.. that's also a good chosen (It's being popular now)
Comment #87
nod_I'd like to keep the check element, it doesn't cost much to do the check and we make sure that people sending unexpected things have a very precise information about what's wrong. I'd keep it more for DX than correctness.
In what situation would you use
once('id')without specifying the elements? If it's because of a behavior you only want run once per page, I would extend the behaviors to support this, not extend this simple library to help with this. If it's for behaviors I wouldNamed params, not a fan here, it does remove the order issue but it kinda makes things longer:
About the arguments order, no special opinion the two parameters are required (and I don't think we should default to 'html') so either way is ok with me. If we want to go with consistency, the theme functions are called with
theme('themefunc', options), not really the same but similar in some ways.In any case, to me this is good enough either way (parameter order, documentation, etc.) and I'd rather wrap up this one and move on to removing jquery dependency from ajax.
Comment #88
droplet commentedthat's once.find, not once
** params order only worth to change if we make the change to once.find
Comment #89
nod_Sorry didn't read it right.
Thinking about it, I like the param order change. Makes it easier to see which "once" was called, only need to pay attention to the beginning of the line when reading the code, the messy "query" part can be ignored more easily.
in datejs for example (and
document, should becontexthere but anyway):I'll probably regret this patch but here we go. The issue is that find is actually filter. So I went ahead and renamed find in filter, and created a new find function that does something more in line with what users would expect. There is no mapping in jQuery to the new function because it doesn't make much sense, nobody expect it and we don't want to encourage people using jQuery. The changes to date.js are mainly indentation, we can call forEach directly on the return of
once().I'll update the CR if we agree on those changes.
Comment #90
droplet commentedTrue.
It's time for end game I think :)
Thanks ALL.
Comment #91
nod_updated the CR. Who's up for RTBC?
Comment #92
nod_updated the IS & some more tweaks to the CR.
And droplet should be credited first in the commit.
Comment #93
nod_I feel this is ready.
We need to get things moving and this patch is needed for things to fix in the olivero issue.
Comment #94
bnjmnmThe new deprecation requires test coverage.
I'm still in the process of reviewing the full patch but mentioning this now as it may be actionable before I'm able to otherwise +1 #93
Comment #95
bnjmnmTagging with needs release note.
Since this is now part of core as opposed to a third party library with its own test coverage, the functions that will be called from other JS (find, filter, and remove) should get test coverage as well as the jQuery shimmed version.
Would also be good for tests to that confirms the proper conversion of illegal spaces in once ids and how the jquery shim aliases window and document.
I see examples of test-only JS with and without docs for the behaviors. Since there's additional work needed anyway, this and jqueryOnceTest should get the usual behavior docs.
Especially since the $type arg for the form has a default value, the test should add an assertion that confirms the library chosen by dataprovider is actually loading. Something as simple as jquery-once-test and vanilla-once-test adding a file-specific DOM element that the test can check for.
Comment #96
bnjmnmThis addresses #95.2 and #95.3
#95.1 (testing the individual methods in the new once() library) is still needed. I'm wondering if this needs to be done in a Nightwatch test or if it can added as another test in FunctionalJavascript classes we've already added.
Comment #97
avpadernoComment #99
nod_forked and renamed droplet repo to keep the history and make some update to the code and dependencies. I think library tests should be sorted out on the library not in core #3176918: [policy, no patch] Publishing / Maintaining JS libraries produced by Drupal that do not have a dependency on Drupal.
Comment #101
bnjmnmIf we're treating this like an external library (and it seems like that is necessary as it needs test coverage ill-suited for Drupal tests), the external repo should probably get tagged with a release and the library defintion changed, including once.js moved to assets/vendor.
Based on reviewing https://raw.githubusercontent.com/theodoreb/once/main/test/test.js it looks like some of the tests are commented out and test functionality that isn't otherwise tested. Before this would pass a dependency evaluation, those would need to be addressed + coverage for any other functions not yet covered. That is something I may be able to assist with if @nod_ can confirm the library will be continuing with that approach to testing.
Comment #102
nod_Updated the gtihub repo. Release made. It's following everything we outlined in #3176918: [policy, no patch] Publishing / Maintaining JS libraries produced by Drupal that do not have a dependency on Drupal. There is a npm package: https://www.npmjs.com/package/once-dom (until we get the whole @drupal thing going).
Automated tests took something like 5 clicks to setup once "npm test" was configured and it runs test on both chrome and firefox. Code coverage is 100%.
And we have an officially published API documentation :) https://github.com/theodoreb/once/blob/main/API.md
Comment #103
nod_not sure we need the drupal tests, lib is tested.
Moved the once lib to assets/vendor. Pointed to my repo, we can change to the drupal/once repo when it exists. had to reroll for the date.es6.js file, the interdiff is in the libraries.yml, the rest is updating the once lib.
I realized that the compiled version of once will not work on IE11. I'll update that, we can fix later if there is nothing else.
Comment #104
nod_lib compatible with ie-11 now, good to go for me.
Comment #105
nod_small update so that the version number is in the once library file.
updated the change notice with the proper attribute name and added a link to the generated documentation.
Comment #106
bnjmnmAdded a release notes snippet.
I reviewed the tests in the repo and confirmed they make the tests written in this issue redundant so I've removed those. The only additional change I made was updating the Drupal version in the deprecation.
As a result most of my changes have either been removed or now live in the library itself https://github.com/theodoreb/once, which means I'm 99% comfortable RTBC-ing this (assuming something in this patch didn't mess up tests).
If @nod_ can confirm he's OK with my test removal and deprecation message change I'd consider this a sufficient co-review and RTBC. I also ran this by committer @lauriii who confirmed this would be fine.
Comment #107
droplet commentedI bet we will remove once.jquery soon. A deprecated comment is needed.
Comment #108
nod_I'm good with removing the tests :)
One side effect here is that if a project or contrib module uses
core/jquery.onceas a dependency, the resulting script loading will be:Meaning that if one script depend on core/once.jquery, everyone uses it regardless of what they declared in their dependencies. I'm confident that won't break their code, but it does behaves unexpectedly, and it becomes very hard to use the old jQuery-once script.
So maybe to be safe we should only change olivero/date.js and not even use the new once jquery interface.
So unfortunately I think the safe thing to do is to not expose the once.jquery library (that way we don't have to deprecate it) and just update all the core code to use the vanillajs lib in a follow up, we can still get the lib in and update date.js in this patch.
Comment #109
droplet commented#108
Good questions & points.
hmm. I think we have to rename the $.once to a new namespace and remove seamless updates. Let them opt-in with quick bulk SEARCH & REPLACE.
Comment #110
droplet commentedBut my comment #109 creates another issue. The Drupal's jQuery binding have to find the old jquery.once data (https://github.com/RobLoach/jquery-once/blob/master/jquery.once.js#L174)
Comment #111
nod_Safe subset of the patch that can be committed. no more once.jquery library, only add core/once and update the date script.
I think we should use jscodeshift and a custom codemod to update the js code automatically for core/contrib/etc.
Comment #112
nod_Comment #114
nod_Ok so deprecation needs to happen in a followup.
Comment #115
nod_updated the Change notice an opened #3183149: Deprecate jquery.once and use the new once lib
Comment #116
nod_Comment #117
nod_It's taking some back-and forth to find the right configuration for shipping the library files.
I added the unminifed source to make it possible for IDE to pickup the documentation and provide autocompletion for the once library. what's loading is still the minified file.
now i'm done
Comment #118
bnjmnm#117 looks very good. My only concern is that the only actual use of the new library is something that would be completely ignored by tests since it requires a condition that won't be the case in our tests. Although I'm quite confident this new library works fine within core, I think it would be good to refactor $().once to once() for an asset that is used in existing test coverage. Most of this would obviously happen in #3183149: Deprecate jquery.once and use the new once lib, but I like the practice of providing at least one common-use change with updates like this. This would be a test-confirmed assurance that such a change does not result in some unexpected conflict with Drupal.
This also needs a dependency evaluation and issue summary update since the approach slightly changed. I'll take care of those.
Comment #119
bnjmnmComment #120
nod_how about every call updated #3183149-4: Deprecate jquery.once and use the new once lib :D
like droplet suggested might need to do the querySelectorAll inside the once call. It's a lot of noise to have that everywhere. Or better have a separate function that we can use as shortcut across core, or even
Comment #121
nod_there is a test for claro autocomplete, so I changed that one.
Comment #122
bnjmnmThe test in #121 serves as a nice confirmation that there isn't a mysterious breaking change going on.
The
querySelectorAllchange mentioned in #120 is probably more appropriate for #3183149: Deprecate jquery.once and use the new once lib, so I'll go ahead and RTBC. I am +1 on the noise reduction, though.Much of this patch has moved to the library or #3183149: Deprecate jquery.once and use the new once lib. The result is this becomes much easier to review, and a more manageable scope overall, so this is good!
Comment #123
nod_Some improvements to the library https://github.com/theodoreb/once/pull/3 (I should just do everything droplet tells me from the start :p)
Doesn't change anything for this issue, it'll be useful mainly the deprecation issue, but no reason to hold up this patch on that.
Comment #124
lauriiiHaven't reviewed the patch yet but since we're adding a new dependency, we need sign-off from release managers.
Comment #125
nod_Update with latest version, makes calls to once shorter and more readable.
Comment #126
nod_small upkeep fixing some inconsistencies in the lib and make a smaller minified file.
Comment #127
droplet commentedDoes `context` matter? When I looking at the #3183149: Deprecate jquery.once and use the new once lib .......
Sometimes, I don't quite sure why a script needs `context` and Once at the same time.
In Drupal, `context` can be anything (from different contri-modules) and run again on your scoped code. This is not controllable. [right??]
** `context` only helps for faster query in Drupal
Once is mainly rewritten for Drupal. If I'm right, we should remove `context` fron Once.
Or still better API?? so you can call:
Comment #128
nod_I don't think it's a problem to keep the possibility of using a context, it's an optional parameter. In that example I'd do:
Allowing a jQuery object as a context makes us run into the issue I show at the bottom of #3183149-23: Deprecate jquery.once and use the new once lib so I'd rather not do that.
( edit ) having the context will help with the transition. We can always deprecate it later
Comment #129
droplet commentedOh! I didn't noticed that before. Not a new request BTW.
Yeah, that's what I think. But the codemods still possible to do the transition, right?
Just out of my curiosity. Anyone know that WHEN the `context` is matter?
If that matter, I wonder how many of our code doesn't pass `context` to Once.remove (intentionally)
Just thinking about remove this pattern from core code or new patch:
`$context.find('target')`
Comment #130
nod_Update of the lib.
I'm expecting to go back to 1.0.0 when the lib moves away from my github fork, so I'm not too worried about having that many changes to the version at this point. It'll be a different npm package when we move that away from my fork.
Comment #131
nod_And about the context i think we need to keep it. It's useful for ajax forms to know what to serialize/unserialize because context will be the part of the form that is targeted.
Also I think that the targeted context might not always be attached to the DOM, meaning that document.querySelectorAll() can't find it and we need to be able to do context.querySelectorAll to be able to target elements in a detached dom node.
Comment #132
xjmThanks everyone for your work on modernizing this dependency!
I've discussed the dependency addition proposed here with @catch, @gabesullice, @bnjmnm, @Dries, and @lauriii. This issue raises some interesting governance and maintenance questions.
#3176918: [policy, no patch] Publishing / Maintaining JS libraries produced by Drupal that do not have a dependency on Drupal is definitely relevant to this issue. The library does not need to depend on Drupal, and it would benefit from being in a separate repository for many of the same reasons the proposed Drupal JS client libraries would:
Based on that, I think Once should be treated as a independent Drupal Core JavaScript package as described in #3176918: [policy, no patch] Publishing / Maintaining JS libraries produced by Drupal that do not have a dependency on Drupal (even given that its equivalent was an independent project maintained by individual Drupalists on GitHub).
However, the current policy draft is not finalized, and some important decisions about governance and the code origin are still not settled. So, I think it would be best to make this an independent package once we've settled that policy.
That said, neither @catch nor I want to see progress here blocked on the final decisions of the JavaScript package work. (As @catch pointed out, the point of #3176918: [policy, no patch] Publishing / Maintaining JS libraries produced by Drupal that do not have a dependency on Drupal is to accelerate Drupal JavaScript development, not to bog it down.) Given that, and given that this is going to be a production dependency of a production version of core, our proposed compromise is:
For now, just add Once as a core library, rather than an external dependency. I reached out to @bnjmnm about how the test suite could work if the library were in core. Ben confirmed that they could be converted to Nightwatch tests, and is willing to implement this conversion if we go ahead with this as a core library.
File a followup issue to make it one of the new packages covered by #3176918: [policy, no patch] Publishing / Maintaining JS libraries produced by Drupal that do not have a dependency on Drupal once we've established those policies, so that it can benefit from those same decisions and features (hopefully soon!).
Sorry that this means more work, as well as the overhead of core tests and queue, etc. It should be a temporary situation until #3176918: [policy, no patch] Publishing / Maintaining JS libraries produced by Drupal that do not have a dependency on Drupal is resolved and we can move the project to its own repo and publish the library on npm.
I'm leaving the RM review tag on because we'll want to review it again if we go ahead and implement the compromise.
Comment #133
nod_Thanks for the update. Given that, for now I would keep it as an external library and revisit the "core" status of the lib once the policy is agreed upon. The cost is way too high for what it is in this situation. We could name this library "core/once-dom" to map with the npm package name and keep the "core/once" namespace open for the "real thing".
Putting it in core will take time to figure out how to integrate with core tests, but there is also the build step to worry about. I'm using rollup and buble because they're very easy to use and there is no need for endless configuration, but that means we'd have to do a dependency audit for those and it's not going to be fun.
On my end I already spend a good amount of time on this to get the lib + updating all the core code, given that we have 2 green patches that put the lib and update all core code, it's going to be hard to justify spending more time on this to my employer, especially if it's for redoing the same work with tools that are not suited for the task. So I'm happy to reroll things, agree to keep the once lib up to date until it's under official core status, but I can't spend more company time on the compromise scenario and I won't spend personal time on this.
(also I'm much more worried about the second patch, so if there is anything that can help get it through happy to help. to that end I got the test coverage of all the JS functional tests if that can reassure people)
Comment #134
droplet commentedIf one of these people give a quick feedback to #133, we can save time on unused review. Don't be silent. We have a strong heart.
+1.
--
In fact, jQuery.once also has no tests and no complaints over these years. If jQuery.once needs a test, Once also needs a test. I vote for no tests here but my exp told me Drupal needs a test (, no matter external dependency or not). However, #133 or #106 or other strict tests. That's no standards to follow. Ideally, we should find a way to test each Once usage on every test.
Comment #135
viappidu commentedDoes a working example qualify as test? :)
Comment #136
nod_@viappidu something like this yes, it would be to make sure drupal load the library properly, that it can be accessed from "drupal-style" javascript, and that it does what is expected.
As a sidenote you can simplify the code like this
Comment #137
xjmJust to confirm, @bnjmnm is definitely working on the Nightwatch version of the test suite. It's understandable if @nod_ and @droplet can't put additional work in on that front at this time. When @bnjmnm posts that patch, we can compare the two approaches and discuss with Dries as needed.
Comment #138
nod_Comment #139
bnjmnmThis adds nightwatch equivalents of the tests in the library https://github.com/theodoreb/once/tree/v3.5.0/test. It is also moved to misc as it's not being treated as a vendor library at the moment (but hope to see soon 🙂)
Nightwatch isn't the ideal way to do these tests, nor is it ideal to rewrite existing+working tests for a different tool. I also think the work @droplet and @nod_ have done here is great and if getting it into core means adding some weird Nightwatch tests I'm happy do do them.
Comment #140
nod_We can use an arrow function so we don't have to add an eslint-disable comment.
Checked the tests, they work and cover the same thing as the lib, so the code itself is RTBC for me.
My main question if we go this way, how the switch to a "properly" managed library will look like?
We had issues with JS file paths before that prevented some cleanup #2484623: Move all JS in modules to a js/ folder. So are we going to keep the core/once namespace for it and just swap the lib (and file path) or are we going to deprecate core/once and introduce a new library (core/@drupal/once? to follow the @drupal/once npm package name it'll be under?) and ask people to update their code again in the next release?
Comment #141
bnjmnmVery good point in #140. I wonder if it would be possible to have this initial implementation be in core/assets/vendor as well. Here's something that came to mind that is odd, but as far as I can tell has fewer tradeoffs than switching js paths or creating two different libraries.
There may be glaring caveats I overlooked in this proposed approach, but perhaps some version of this could work?
Comment #142
xjmWe briefly discussed this issue on today's JS Menu Component initiative discussion with the DA and committers. Based on that discussion and feedback from nod_, I think we might want to attempt to adopt the "interim decision" for this. The MVP for standalone projects on d.o is:
Per nod_, we can do everything else the hacky way (running the build steps locally, running tests with not-the-bestest-tools) so long as it's on npm as we intend, and then fix those things later. From a release management perspective, I'd love to have the initial commit be under the official Drupal namespace on npm as a stable initial release for us to commit it to core (so we don't have to change that later).
I'll reach out to @hestenet and ask about how long those initial steps might take. If it's months and months, we should maybe go ahead with @bnjmnm's patch, but if it's "We can have that for you in two weeks" then it might be worth using Once as the test case. Inaugurating a new tag for the feedback.
Comment #143
droplet commentedpublish to npmjs.com isn't a must if that takes years. Any Git URL (even .gz) still works perfectly with npm/yarn. But a good portal is meaningful for marketing if we trying to get non-Drupal developers.
Patch #139 has a bug on test. will post soon when we have the first step.
and Once `context` has a potential bug:
https://developers.google.com/web/updates/2013/03/What-s-the-CSS-scope-p...
Comment #144
nod_We might need to wait after the holidays for DA feedback (for a timeline on adding a JS project type to Drupal.org, as well as some gitlab CI access and setup for secure publication to the @drupal namespace on npm). It might not be in 2020 but it won't take years either. Maybe you haven't seen the news yet droplet #3187489: Add @justafish and @nod_ as core committers to MAINTAINERS.txt, so that should help keeping things moving :D
The potential issue with context doesn't feel like a big deal in the context of once, also people have been using qSA for years so they probably already know when it trips them up. In core there are a few places where we could see an issue, when there are descendant selectors such as :
And that's it, the rest of the selectors in once don't have a hierarchy "condition". It's selecting based on the class, data attribute, tag name, or id.
Comment #145
nod_Small update: DA is taking actions around this topic as a priority, if all goes well we'll have everything this month. That will unlock a lot of things, this issue included.
Comment #146
nod_DA just released the new project type #2474007: Add a “General” project type to Drupal.org on d.o. The Gitlab part is also on the way, so good news all around :)
I went ahead and created: https://www.drupal.org/project/once I'll push the repo tomorrow as it's getting late here.
Comment #147
nod_The once lib is now on NPM :D. We have a new project type and a pipeline that published code to npm under the @drupal namespace automatically, removing the tag for DA review.
updating the patch with latest code. For making use of the npm package itself in core see #3185289: Use package.json and rollup to manage third party JS libraries
updating the change notice with links and mention of the npm package.
Comment #148
catchRetitleing and removing the 'Needs release manager review' tag now that it's decided exactly where the new library lives.
Comment #149
anmolgoyal74 commentedFixed CI issues.
Comment #150
droplet commentedthe patch is similar to the previous, only needs to change issue status mostly.
I'm okay to do a full REVIEW if no more hidden issues that need someone to approve
Comment #151
nod_I'd say go for it, I don't see outstanding problems or concerns.
We'll need to have some integration tests to make sure the once lib works as intended for drupal core but we can do that in the follow-up issue where we replace everything in core, this one is long enough already.
Comment #152
nod_@droplet confirmed it with the other committers, no hidden issue that needs approval.
Comment #153
droplet commentedOnly one thing. It's not really a bug but other polyfill have `{ weight: -20 }`. We should keep them the same, right?
not a bug either. and no written rules for it. But we keep all `minified` the last one in this file. :p
Comment #154
nod_Nice find, fixed.
Comment #155
droplet commentedComment #156
justafishLooks great! +1 🎉
Comment #157
alexpottComment #158
alexpottAdded a release note. This will probably be a highlight too.
Comment #159
xjmLet's tag the 1.0.0 release of https://www.npmjs.com/package/@drupal/once before we commit this to HEAD. We need to add a stable version semver contract before core adds the code -- otherwise, it adds a release blocker for the release of 9.2, which isn't something we allow for minors.
If we run into any issues in #3183149: Deprecate jquery.once and use the new once lib that require public API changes to the library, we still have until the 9.2.0-beta1 commit deadline to release a 2.0.0 version of once.js, and 1.x can be EOLed in that situation. (The restrictions around version support etc. only kick in when the library ships in a stable version of core, which will be 9.2.0 in this case.)
Comment #160
alexpott#159 means we're blocked here on #3195360: Stable 1.0 version
Comment #161
nod_Thanks for the details on version support. I'll start the dependency evaluation so that we can have a stable version by monday hopefully, the code that we bring in to core doesn't have dependencies, the evaluation is for dev dependencies.
I'm not anticipating any API change (or any code change) at this point, the patch in #3183149: Deprecate jquery.once and use the new once lib is already complete and green. I'd be surprised to find an edge case we didn't already run into.
Comment #162
nod_built the 1.0.0 version locally, it'll be the same as the one we'll publish later. I'll update the patch again if necessary
Comment #163
droplet commentedCan you/we do it? haha
well this patch revealed a bug. it pointed to parent folders. so the non-minified js file in this patch is useless.
Comment #164
nod_non minified is for IDE integration, that way there is documentation when using the once lib.
But agreed that it's not 'clean' as far as the source mapping is concerned.
Comment #165
nod_source map issue has been fixed, only need a release now :)
Comment #166
bnjmnmI'm concerned this doesn't have any integration tests. The coverage of the library itself is excellent, but I think there should be some minimal coverage to confirm it is properly integrated with Drupal. I ran tests on a version of the #162 that removed the usage of once() from datepicker and Claro autocomplete, and the tests passed without error, but both would be scenarios that caused multiple instances of behavior that should only occur once. https://www.drupal.org/pift-ci-job/1991107. An abbreviated form of the core tests that were present as recently as #105 would work fine.
Comment #167
droplet commentedIMO, we need a global LOAD test for all libs in `core/core.libraries.yml`
And then, we need not test the 3rd party itself. `Once` is just another `jQuery`, that's no diff. We need not do extra for it.
and all these new-type libs are protected (, I believed). It's very difficult to remove a method from it.
This is my comment #134, we never have strict tests for many JS code & Once. In fact, we really have 1 or 2 wrong Once usages in the old day.
Technically, this is bad tests in modules, not missing tests of Once. (A simple dedicated test for Once in CORE won't prevent bugs happened)
Comment #168
nod_Integration testing is going to happen, I wanted to do that in the next issue where we deprecate the jquery.once lib because this issue is long enough as it is.
Comment #169
nod_We have a 1.0.0 release of @drupal/once :D
interdiff against #154 as it was the last RTBC. The diff in size is because the map file doesn't include the source inline.
To test you can edit any node with the claro admin theme, and do a
once.find();in the console to see the couple of fields that have been marked as processed (the author field is always there).Comment #170
droplet commentedComment #171
nod_reroll after #3113649: Remove drupal.tabbingmanager's jQueryUI dependency (confilict in .estlintrc.json file)
Comment #174
alexpottCreditting @catch, @gabesullice, @bnjmnm, @Dries, @lauriii, @xjm for #132
Comment #175
alexpottI fixed #169 / #171 locally since the conflict was in core/.eslintrc.json and I run the custom commands locally prior to committing anyway.
I noticed that this patch had test coverage but that was then removed. I checked to ensure the once library has test coverage and it does - https://git.drupalcode.org/project/once/-/tree/1.0.x/test
Committed 5bce5ee and pushed to 9.2.x. Thanks!
Comment #177
nod_That one makes me very happy, thank you everyone :)
Comment #178
larowlanCongratulations folks
Comment #179
nod_#3183149: Deprecate jquery.once and use the new once lib is complete, green, and in need of review, then we can say goodbye to jquery.once :)
Comment #181
xjmThis issue is a critical part of D10 development.