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.

Issue fork drupal-3254840

Command icon 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

mrweiner created an issue. See original summary.

mrweiner’s picture

Issue summary: View changes
cilefen’s picture

Component: ajax system » javascript
cilefen’s picture

The rough regex is to search for once\((.*)\).forEach\( and replace with once($1).each(, however the parent issue is really clear about forEach being the correct function.

mrweiner’s picture

Thanks for linking. You're right, it's pretty clear that forEach is intentional. It's possible that something is wrong with our env specifically, so I'd be interested to hear if anybody else is encountering this.

mrweiner’s picture

Issue summary: View changes
mrweiner’s picture

That regex did work, but actually did not fix the problem. Instead I started getting once().each is undefined instead, so something else is going on. Updated the issue description.

mrweiner’s picture

Issue summary: View changes

Alright, updated the issue again. This is not related specifically to forEach(), but is happening because sometimes once() appears to be set to the Window which cannot be iterated with forEach().

longwave’s picture

Can you reproduce this on a new install of Drupal? It's possible this is caused by a contrib module, custom module or a theme.

mrweiner’s picture

I was able to reproduce on a clean install by adding a flowplayer player that uses their asnyc embed code into a Full HTML block, and adding it to a page with big_pipe enabled.

I then added the this logging to ajax.js:

    Drupal.ajax.bindAjaxLinks = function (element) {
    const els = once('ajax', '.use-ajax', element);
    
    if (!Array.isArray(els)) {
      console.log('ajax ded');
      console.log(once)
    } else {
      console.log('ajax works')
      console.log(once)
    }

When it works correctly, the log line looks like function augmentedOnce(id, selector, context) and clicking on it takes me to /core/misc/jquery.once.bc.js?v=9.3.0 as expected. When it's broken, the log line looks like function once(e, n), and clicking on it takes me to https://cdn.flowplayer.com/releases/native/stable/plugins/chromecast.min.js added by the player.

I think the issue is that web/core/misc/jquery.once.bc.js is attaching once() to the window object, and callers are making the assumption that when they call once(), the window.once method defined by that file will be the one that is called. I suspect that if something else happens to override window.once then the overridden version is called instead. I'm not sure what else would cause console.log(once) to change like that. I'd imagine that a solution to this would involve drupal's own once not being attached to the window and invoked globally, but instead attached somewhere where it will reliably be not overwritten.

Unfortunately I'm not sure of a good way to to reproduce this without the flowplayer embed, although I'd imagine it must be possible. The demo embed does not load up if you implement it yourself, so we might need to figure out some other way to get a player to use for testing. I cannot provide embeds that we are using because the content belongs to my client. If needed, I could set up a trial account and provide an embed to use, although they're only free for 14 days.

droplet’s picture

Could you simply log `element` in

    Drupal.ajax.bindAjaxLinks = function (element) {
I was able to reproduce on a clean install by adding a flowplayer player that uses their asnyc embed code into a Full HTML block, and adding it to a page with big_pipe enabled.

I can't reproduce it. Use a module or only HTML code?

EDIT:
above flowplayer example is outdated I think. Use below one, the player is shown but no errors
https://flowplayer.com/developers/player/player-api#api-retrieval

xjm’s picture

Status: Active » Postponed (maintainer needs more info)

Postponing for an answer to #11.

ericpugh’s picture

I'm having the same issue with a custom theme using AlpineJS. I'm also having a hard time coming up with a dead simple example to reproduce (because I'm using a bundler in the theme) but I can at least reproduce the errors by including the following module in the theme's libraries.yml:

alpinejs:
  js:
    https://unpkg.com/alpinejs@3.x.x/dist/module.cjs.js: { attributes: { defer: true }, type: external, process: false }

With this addition I have about half a dozen once(...).forEach is not a function. Most importantly, in big_pipe.js which doesn't render the content in the placeholder. Of course, if I add the library using a minified/terser version there's no longer a conflict. (But that's pretty bad for the dev environment).

It seems likely that this might be an issue with a lot of packages.

Edit: window.once in devtools returns Alpine's "once" function

mrweiner’s picture

Status: Postponed (maintainer needs more info) » Active

Re: #11 and #12

I can't reproduce it. Use a module or only HTML code?

EDIT:
above flowplayer example is outdated I think. Use below one, the player is shown but no errors

As I said, "The demo embed does not load up if you implement it yourself, so we might need to figure out some other way to get a player to use for testing." My link is not outdated, but the snippet on their examples page does not cause the error.

No additional modules. This was a "standard" installation of drupal with big_pipe enabled, and I added the embed via "source" of the Full HTML textarea in a custom block. Here is a version of our embed with IDs removed, the format outlined by https://flowplayer.com/developers/player/player-api#creating-a-flowplaye......

        <div id="async_flowplayer" data-player-id="our-player-id">
                      <script src="//cdn.flowplayer.com/players/our-player-id/native/flowplayer.async.js">
                        {
                          "src": "our-video-id",
                          "autoplay": "true",
                          "loop": "true",
                          "hls": {
                            "startLevel": "3"
                          },
                          "autopause": "true"
                        }
                      </script>
        </div>

I'll note that the player has the chromecast plugin enabled in our account backend, which I think is adding the chromecast script I noted from the logging.

#13 though seems to confirm that the problem is likely related to window.once being overridden and the subsequently invoked by Drupal.

mrweiner’s picture

Actually, #13 is great -- makes this easily reproducible. Just update core/once in core.libraries.yml to the following and navigate to the blocks page at /admin/structure/block.

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://unpkg.com/alpinejs@3.x.x/dist/module.cjs.js: { attributes: { defer: true }, type: external, process: false }
  dependencies:
    - core/drupal.element.matches

Errors are present and the logging I mentioned in #10 above points to alpine's modue.cjs.js once method instead of Drupal's.

droplet’s picture

I still can't reproduce it sadly. Maybe someone can help to do a test.

I combined with flowplayer example with #14:
** I removed `hls` because it doesn't played if I added.

    <div id="async-player" data-player-id="cdcc4202-ef0b-4e03-a43a-d1fcf6d83157">
      <script src="//cdn.flowplayer.com/players/ffdf2c44-aa29-4df8-a270-3a199a1b119e/native/flowplayer.async.js">
        {
          "src": "f576651c-4cc6-4664-84fa-bb3b35ef1aba",
          "autoplay": "true",
          "loop": "true",
          "autopause": "true"
        }
      </script>
    </div>

I search all code from Chrome console. I think flowplayer scripts are scoped.

** BUT, what made me interested is you mentioned "big_pipe". A particular loading order may matter.

#13

According to your report, this may be a different issue. the same `once` function is overridden. I don't know if we should rename it or not. I can imagine it's a very common problem if we loaded different 3rd parties source code.

BTW, if you have not a loader, you can't load commonJS in browser, right? (it may still work...but ....) Why not use ESM version? Using a sourcemap version will help for debugging.

ericpugh’s picture

I'm using ES6 imports for AlpineJS in a custom theme. I posted the example with CommonJS trying to find a simple example to reproduce without downloading JS files, explaining a build setup, etc.

nod_’s picture

so it's a conflict with the global name once?

in that case core can't do much about it, we would need to change the library global name and update all the file closures to something like

((once) => {

})(newOnceName);

But that also means all contrib/projects will need to update, which is not realistic.

ericpugh’s picture

I see. I do appreciate the work being done to unwind JQuery from core.

Maybe there's a "feature request" here around utils in the global scope? Would it be possible to attach once to window.Drupal ? (I'm really not knowledgeable enough about the internals here to make any real suggestions).

mrweiner’s picture

so it's a conflict with the global name once?

Well, the conflict is when both core and some other third-party define a global once, and core tries to call it after it's been modified by the third-party. Core can do something about this, because the break was introduced by core's addition of the new core/once and core/jquery.once.bc libs. Potentially just the latter, actually, but I'm not 100% sure. Before 9.3, my flowplayer embed and presumably alpinejs both worked fine, but now they do not.

jquery.once.bc.js does this, which is at least part of the issue.

  window.once = Object.assign(augmentedOnce, drupalOnce, {
    remove: remove
  });

It seems like the simplest thing would be to instead attach core's new once() method to the Drupal global or something like that (like behaviors already are) where it is safe from modification instead of adding it right on the window. There are potentially a significant number of third-party libraries out there that will now cause core's own js logic to totally break just by being loaded by the page.

mrweiner’s picture

Status: Active » Needs review
StatusFileSize
new686.8 KB

Here's a simple proof of concept patch that does a find and replace from once( to Drupal.once (with the space present to avoid targeting PHP and only hit the JS once calls), and also changes window.once to Drupal.once.

For reproducable testing, simply update your drupal/once library in core.libraries.yml to the following (to include the "breaking" chromecast script I was encountering, without any alpinejs overhead), enable big_pipe, and visit the blocks admin page:

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 }
  dependencies:
    - core/drupal.element.matches

Before the patch, you should see the once(...).foreach errors, and afterward you should not. Marking as "needs review" to call our friendly neighborhood testbot, but there may be more that needs to be fixed as well.

mrweiner’s picture

Maybe somebody more familiar with the test runner knows what "custom commands failed" means?

longwave’s picture

Status: Needs review » Needs work

Click through to the CI results at https://www.drupal.org/pift-ci-job/2268880 - we use prettier to enforce JS style, the checks for that are failing.

mrweiner’s picture

Status: Needs work » Needs review
StatusFileSize
new739.15 KB

Ah okay, that's what I thought but wasn't sure. Didn't realize prettier needed to be run before tests. For anybody's reference, instructions at https://www.drupal.org/node/2986680. I think this one should be better.

EDIT: D'oh, this also replaced function once in the once.js. Will probably need to fix that but can't do that right now.

nod_’s picture

Couple of issues with this approach

  1. The once library is not tied to Drupal, it's a package you can use it wherever, see https://www.drupal.org/project/once. Changing assets/vendor/once files is not something we want to do
  2. Contrib started updating their code to use the new once function, it's going to be a problem to ask them to change it a second time, partially updated sites could be in trouble with this
nod_’s picture

Looking at the flowplayer script it adds the nodejs event API to the window and document objects to bind 2 event handlers (so only using .on)

i'm inclined to say this is a flowplayer issue.

ericpugh’s picture

i'm inclined to say this is a flowplayer issue.

... and an AlpineJS issue.

I understand that we're not going to be able to handle every issue with third party libraries, but I think it might be worth considering better ways to use a standalone JS packages. (Something like Vue's Vue.use() plugins comes to mind).

Contrib started updating their code to use the new once function, it's going to be a problem to ask them to change it a second time, partially updated sites could be in trouble with this

Sure. But if we thought this was going to continue to be an issue (and presumably there are plans to add more @drupal packages) it would probably be better to depreciate something sooner rather than later.

Final, in case anyone lands on this issue the (not great) work around seems to be to uglify the conflicting library. If anyone is specifically using AlpineJS I'm using rollup and terser in my theme.

// Example rollup.config.js
import { terser } from 'rollup-plugin-terser'
import resolve from 'rollup-plugin-node-resolve'

export default [
  {
    input: 'src/js/app.js',
    output: {
      file: 'dist/js/app.min.js',
      format: 'esm',
      sourcemap: true
    },
    plugins: [
      resolve(),
      // Mangle app.js to avoid naming conflicts with drupal core js.
      // @see: https://www.drupal.org/project/drupal/issues/3254840
      terser({
        mangle: {
          reserved: ['_']
        }
      }),
    ],
    watch: {
      include: ['src/**'],
      exclude: 'node_modules/**'
    }
  }
]
mrweiner’s picture

The once library is not tied to Drupal, it's a package you can use it wherever, see https://www.drupal.org/project/once. Changing assets/vendor/once files is not something we want to do

Understood, and in fact I did not mean to patch the once library itself. I'll be creating a new patch that does not touch its files. That said, my understanding is as follows (and if I'm misunderstanding any of the internals, please let me know):

The once package itself may be totally fine. In standalone projects you are able to namespace your imports and potentially avoid what we are seeing here. However, the way that Drupal leverages the package is problematic in that we have not namespaced it, instead deciding to attach a commonly used function name to the global namespace in jquery.once.bc.js. As long as calling once() in core/misc/ajax.js (or anywhere else) invokes window.once, any other library that also sets window.once is very likely to break Drupal's logic through no fault of the end user, and indeed there is no way to work around that.

Flowplayer and alpinejs should probably not be using the window object but, in the end, we probably should not be either. End-users could be pulling in any number of third-party libs and it seems odd that we'd be okay with breaking them when we actually don't need to.

Contrib started updating their code to use the new once function, it's going to be a problem to ask them to change it a second time, partially updated sites could be in trouble with this

Looking ahead, I agree with @ericpugh. There may be some short-term pain with asking contrib to make the updates a second time, but this seems to just have been an avoidable oversight with a potentially simple fix.

Further, existing sites that were working without issues (and theoretically should continue working, since this update was not intended to brick them) are already in trouble without a way forward. This feels potentially more problematic to me.

mrweiner’s picture

Title: "Uncaught TypeError: once(...).forEach is not a function" throughout core js » 3254840-uncaught-typeerror-once-foreach

Alright, I created an MR for an updated version of the changes from #24 which:

  • Does not affect any files in core/assets, including the once package itself per #25
  • Also replaces instances of $(once(...)) that were missed by my initial find and replace
  • Ensures that Drupal is actually passed into the closure in jquery.once.bc.js. I think this was the cause of the one functional test failure in #24
mrweiner’s picture

Title: 3254840-uncaught-typeerror-once-foreach » "Uncaught TypeError: once(...).forEach is not a function" throughout core js

Reverting issue title -- not sure what happened. Also removed "draft" from the MR title because I think it blocked tests?

mrweiner’s picture

Issue summary: View changes

Updating the issue description to better reflect my current understanding of what's going on.

mrweiner’s picture

Status: Needs review » Needs work

Needs work per failures: https://dispatcher.drupalci.org/job/drupal_patches/106604/console

There are some "Drupal.once is not a function" errors, so I'd assume that the Drupal global needs to passed in somehow for those cases. Looks like other tests just didn't get their once() calls replaced by the find and replace, so it makes sense that those are failing. I don't know whether those would also need to have the Drupal object made available for this approach.

I think I'm going to leave as-is for now until an approach is agreed upon since it seems silly to dial in tests for a solution that may not be used.

mrweiner’s picture

Sorry to spam this issue, but I think the once module itself is also to blame. I didn't realize that the minified version is just straight up setting var once = .... I'd assume this decision was made for ease of use, but we are seeing the side-effects here. This and the use of window.once both seem problematic to me.

nod_’s picture

Title: "Uncaught TypeError: once(...).forEach is not a function" throughout core js » window.once name conflict with other third party script
Priority: Major » Critical

Setting as critical as there is no good workaround yet

nod_’s picture

Issue summary: View changes
droplet’s picture

The issues of the global conflict do not exist in above 2 examples.

To load above examples in a plain HTML file. Then run `window.once` returned `undefined`.

At least, they don't export to `window` explicitly. Then if you have a build process, it should be no problems. You can use import alias..etc

Note:
this is older version:
https://cdn.flowplayer.com/releases/native/stable/plugins/chromecast.min.js

this is what loaded on my side from their auto loader:
https://cdn.flowplayer.com/releases/native/3/stable/plugins/chromecast.m...

I agree the global vars conflict still can be a problem with other scripts..The question is how common the `window.once` is.

mrweiner’s picture

@droplet I'm not sure how the conflict comes about from those 2 scripts, exactly, but adding them to a library as described does cause a conflict with core. Whether they are assigning to window.once or just setting a global var once, or whatever, I'm not sure. And I can't really speak to why the "old" version of the flowplayer script is being loaded on our site, but all I can say is that it is the one that they are serving to us with the embed.

I agree the global vars conflict still can be a problem with other scripts..The question is how common the `window.once` is.

I guess my thought is just that we should avoid polluting the global namespace if possible, regardless of how common a function name is. Especially if we want to start splitting more functionality into individual @drupal modules, we are going to open ourselves up to additional conflicts with each new module (as @ericpugh mentioned). @drupal modules could easily do something like the following and avoid the issue entirely:

var Drupal = Drupal || {};
Drupal.once = function(...) {...}

This would maintain the "iife" pattern and IE11 compatibility afaik, and adding methods, etc. to the Drupal global is already a well-established pattern.

mrweiner’s picture

Actually, I think you could implement the above with minimal headache for "contrib that has already begun upgrading". You could include both the "old" and the "new" versions of the scripts in core/once lib for the time-being. The global and Drupal scoped versions shouldn't conflict with each other, so I think that would allow contrib's global implementations to keep working while allowing core to instead implement Drupal.once. Global once() could then be given its own deprecation schedule, be that 10.0.0 or otherwise.

justafish’s picture

I guess my thought is just that we should avoid polluting the global namespace if possible, regardless of how common a function name is. Especially if we want to start splitting more functionality into individual @drupal modules, we are going to open ourselves up to additional conflicts with each new module (as @ericpugh mentioned). @drupal modules could easily do something like the following and avoid the issue entirely:

var Drupal = Drupal || {};
Drupal.once = function(...) {...}

I agree with this and I like the solution proposed to put it into the Drupal namespace

nod_’s picture

Status: Needs work » Needs review

Thanks for the feedback.

On my end I don't want to change the library name/exports/iife at this point, since it's not Drupal specific and it'd be counter productive to have a dependency on the Drupal object in a standalone package. the iife is for convinience since ppl should probably use the module version directly.

In any case what I could see working is

  • keep the once export we have today
  • add a second way of accessing once through Drupal.once
  • We update the docs to point people to Drupal.once instead of once directly.
  • From there we update all core scripts to use Drupal.once instead of once (a one line change): from ((once) => {})(window.once); to ((once) => {})(Drupal.once);

That way there is no impact on people who already updated their code, and there is way to restore the once global variable even temporarily.

In the worst case we could even force the once global to be this script when we run the behaviors and restore it afterwards:

  Drupal.attachBehaviors = function (context, settings) {
    context = context || document;
    settings = settings || drupalSettings;
    const behaviors = Drupal.behaviors;

    const onceBck = window.once;
    window.once = Drupal.once;

    // Execute all of them.
    Object.keys(behaviors || {}).forEach((i) => {
      if (typeof behaviors[i].attach === 'function') {
        // Don't stop the execution of behaviors in case of an error.
        try {
          behaviors[i].attach(context, settings);
        } catch (e) {
          Drupal.throwError(e);
        }
      }
    });
   
    window.once = onceBck;
  };

not great but would probably fix the problems described here without any code change needed anywhere else.

nod_’s picture

StatusFileSize
new4.12 KB

Ok working workaround. Not pretty but should unblock some use cases, implemented what I talked about in #41.

This works for the flowplayer use-case.

About alpine not sure what to do. Can't replicate, using the cdn.js version doesn't trigger any errors and can't load the cjs version without some more files around that makes loading commonjs in the browser works.

droplet’s picture

#41, #42 only worked for the particular scenario

For example, I have a script loaded before and have following code in my 3rd party script:

setTimeout(() => {
  console.log(once)
}, 2000);

Even executing scripts via `Drupal.attachBehaviors` isn't a must IMO.

Drupal also included `js-cookies` This is big brother but I think `window.Cookies` is very common. We need to apply the same standard for all scripts then.

Only RENAME can sort of this problem. (BUT if someone going to complain about `window.Cookies`, no deals?)

mrweiner’s picture

Status: Needs review » Needs work

About alpine not sure what to do. Can't replicate, using the cdn.js version doesn't trigger any errors and can't load the cjs version without some more files around that makes loading commonjs in the browser works.

The following still reproduces the error (without the patch) on a standard drupal install from the blocks admin page, so I'd give it a try for your testing:

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://unpkg.com/alpinejs@3.x.x/dist/module.cjs.js: { attributes: { defer: true }, type: external, process: false }
  dependencies:
    - core/drupal.element.matches

There are some other errors related to alpine, but both toolbar and bigpipe are throwing the once error. The other alpine errors are really irrelevant as long as the once errors shows up, since that's what demonstrates the conflict.

This works for the flowplayer use-case.

Sorry to say that it does not. I'm seeing less errors with the patch applied, but /admin/structure/blocks is still showing instances of the error from bigpipe.js line 50 with the patch applied. Seeing the same with the alpine script + patch. This is on a fresh, standard install.

nod_’s picture

right I usually disable bigpipe so my patch won't work in that case. And like droplet said it could be used outside the behaviors.

Guess we don't have an easy solution here. What's not fun is that a bunch of core js files don't put the once in the file-closure so can't even be automated as much as I wanted.

nod_’s picture

Tried to go this way to at least warn people of the problem but that doesn't work with the BC layer we add in jquery.once.bc file

  if (once) {
    Object.defineProperty(window, 'once', {
      value: once,
      writable: false,
    });
  }

I'm just trying to make sure that renaming once in all core scripts is the only way, because that's going to be a big change and I'd rather avoid it if at all possible :/

for the alpine lib I can reproduce (don't know what I was doing before), although I would say loading a commonjs module in a browser is not ideal especially when a native es module is also exported.
For the flowplayer I'm just sad about the conflict, in both case I don't see anything simpler than changing the namespace.

If we put stuff in Drupal, does it mean we'll want to do that for other libs? jquery, backbone, underscore, etc.? if that's the case might want to have a Drupal.utils namespace or something to avoid adding everything to Drupal.

mrweiner’s picture

For the flowplayer I'm just sad about the conflict, in both case I don't see anything simpler than changing the namespace.

Yeah I feel ya. I totally understand why it's all set up how it is. It's just unfortunate.

If we put stuff in Drupal, does it mean we'll want to do that for other libs? jquery, backbone, underscore, etc.? if that's the case might want to have a Drupal.utils namespace or something to avoid adding everything to Drupal.

Good point. Since jquery, etc, are already used as globals across the js ecosystem it may be alright to leave them there. But I can the see argument for consistency that if we put one lib in Drupal maybe they should all go there. I don't know that I'm familiar enough with all of the tooling to make a real argument one way or the other.

Fwiw, core already commonly does like Drupal.ajax, so it's not the end of the world to drop them right on Drupal, but if this is going to become a common approach then you're right that it might be a good idea to add something like Drupal.utils for the sake of organization.

mrweiner’s picture

Just want to drop a note in here that it'd be awesome to get a fix into 9.4.0 for this if possible since affected sites are stuck on 9.2.x until we figure out a fix, and 9.2 support is dropped with the release of 9.4.

droplet’s picture

I believe only 2 options:

1. RENAME
2. Also RENAME but put it under Drupal namespace

So, while waiting for answers from top maintainers, we can help to think about the NEW NAME.

of course, to me, that's option 3: Do nothing.

droplet’s picture

Actually, that's another option...

to modify the CORE & Contributed code directly.. The most simple way is SEARCH & REPLACE. (or a module triggered before Drupal JS aggregation)

Or load a JS compiler to rewrite it.. (babel / esbuild or whatever)

this is the only solution for scalable/unknown projects.. (conflicts can exist on 2 contributed modules also)

For a single project level this is the best option, you even able to load diff config to minify the JS files.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

robcarr’s picture

Patch at 42 no longer applies. Still seeing "Uncaught TypeError: once is not a function" error with 9.5.8 vanilla site

robcarr’s picture

StatusFileSize
new4.01 KB

Quick re-roll attached - not seeing Console error on 9.5.8

robcarr’s picture

StatusFileSize
new4.23 KB

Sorry - last patch wouldn't apply. Rerolled patch again

akram khan’s picture

StatusFileSize
new11.55 KB
new14.51 KB

try to fixed CCF #54

gauravvvv’s picture

Status: Needs work » Needs review
StatusFileSize
new330 bytes
new4.23 KB

I have fixed the build failure and attached a patch with interdiff. please review

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs issue summary update

There anyway to write a test for this?

Issue summary should be updated as well as it says proposed solution is still TBD

robcarr’s picture

StatusFileSize
new1.3 KB

I'm seeing this problem in D10.1 too, and it's clearly a problem generated by other modules.

A lot of the JS has been removed in D10, so the patches above that people have put a lot of work in won't apply. With the caveat of 'I've absolutely no idea what I'm doing' the attached patch stopped all the console errors and - in my case - has allowed the modals for Views to pop-up again. It may have stopped something else really significant working, but I'll revisit if that's the case.

robcarr’s picture

StatusFileSize
new1.24 KB

Sorry - bad patch...

robcarr’s picture

StatusFileSize
new1.27 KB

And again... so sorry

mrweiner’s picture

@smustgrave issue summary needn't be updated because no actual decisions have been made regarding how this should be addressed, unfortunately.

emb03’s picture

I am trying to make a custom form using ajax to add and delete items, simple, right? I am using Drupal 10.1 and running into major problems with not being able to use jquery.once function. How is one supposed to make a simple form using form_state for temporary storage? Are there any workarounds? I have tried everything even chat GPT is stumped :)

voleger’s picture

#63 jquery.once is deprecated since drupal:9.2.x and is removed since drupal:10.0.0. See https://www.drupal.org/node/3158256

_utsavsharma’s picture

StatusFileSize
new519 bytes
new1.42 KB

Tried to fix minor CCF in #61.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

emircan erkul’s picture

StatusFileSize
new1.01 KB

In my case, $.fn.once was not ready when script initialized which causes all headache.

Attached file solved my issue. Yes i should find the root cause but couldn't. Probably order of the loading those libraries causes but effected areas was huge to handle in my case. Anyway it's just temporary, we soon upgrade to D10.

emircan erkul’s picture

StatusFileSize
new1.03 KB

Sorry path was a bit wrong here is correct one

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.