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
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()