Problem/Motivation
Drupal uses a new standalone library to provide the once functionality. This library is published on npmjs as the @drupal/once package. The package exposes a js module and a version compatible with browsers that don't support js modules (the "iife" version). This iife version exposes a global variable named "once", making window.once
the entrypoint of the functionality, this is the version drupal core uses since IE11 support is necessary.
Some third party scripts/libraries can already declare a window.once
that Drupal core ends up using instead of the intended @drupal/once
script, causing errors in almost all behaviors since most of them use once.
Steps to reproduce
Add one of the following in some library definition that gets loaded on the page:
https://unpkg.com/alpinejs@3.x.x/dist/module.cjs.js: { attributes: { defer: true }, type: external, process: false }
https://cdn.flowplayer.com/releases/native/stable/plugins/chromecast.min.js: { attributes: { defer: true }, type: external, process: false }
Here the defer means those files will be loaded right before the behaviors are executed and after the once library has been loaded (so window.once
will never point to the @drupal/once
script).
Proposed resolution
None yet.
Remaining tasks
Find a good solution
Write tests
Original report by mrweiner
Problem/Motivation
Upon upgrading to 9.3, we are seeing a number of console errors for Uncaught TypeError: once(...).forEach is not a function
from various modules including tour, toolbar, big_pipe, views, and contextual. One such example is ajax_view.js
.
9.3.x, line 70: https://git.drupalcode.org/project/drupal/-/blob/9.3.x/core/modules/view...
once('exposed-form', this.$exposed_form).forEach($.proxy(this.attachExposedFormAjax, this));
Doing some logging, it looks like the calls are not always broken. When they work, once(...)
appears to be an array. When they are broken, however, once(...)
looks to be the Window
, which is not iterable. I verified this in both ajax.js and big_pipe.js.
It seems that the culprit is that jquery.once.bc.js
sets once()
as a global via window.once
, and if another script is loaded on the page that also sets this global then subsequent invocations of once()
by core fire off this replaced version of the function, causing things to break.
Steps to reproduce
Two scripts identified so far to cause the issue are https://unpkg.com/alpinejs@3.x.x/dist/module.cjs.js and https://cdn.flowplayer.com/releases/native/stable/plugins/chromecast.min.js, although there are likely others as well.
To reproduce, create a new project using the "standard" profile and add a script which modifies window.once to core/once in core.libraries.yml
(or, realistically, probably to any library) as in:
once:
remote: https://git.drupalcode.org/project/once
version: "1.0.1"
license:
name: GNU-GPL-2.0-or-later
url: https://git.drupalcode.org/project/once/-/raw/v1.0.1/LICENSE.md
gpl-compatible: true
js:
assets/vendor/once/once.min.js: { weight: -19, minified: true }
https://cdn.flowplayer.com/releases/native/stable/plugins/chromecast.min.js: { attributes: { defer: true }, type: external, process: false }
#or
https://unpkg.com/alpinejs@3.x.x/dist/module.cjs.js: { attributes: { defer: true }, type: external, process: false }
dependencies:
- core/drupal.element.matches
and then navigate to the homepage. Errors will be thrown by ajax.js
and others.
Proposed resolution
Instead of setting window.once
and calling to as a global, attach it to the Drupal
global as Drupal.once
or remove it from the global namespace by other means so as to avoid conflicts with third-party libraries.