14

On my website I load most JavaScript asynchronously using RequireJs. Please see the following for my RequireJs configuration:

require.config({
    paths: {
        'jquery': '//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery'
    },
    shim: {
        'jquery.accordion': {
            deps: ['jquery']
        }
    }
});

Say I have the following code defined within the body to load a file asynchronously:

require(['DisplayAccordion']);

Where DisplayAccordion.js contains the following:

define(['jquery', 'jquery.accordion'], function($) {
    $(function() {
        $('.xyz').accordion();
    });
});

Note: jquery.accordion is simply a jQuery plugin which doesn't have AMD support and requires the global jQuery variable to be defined.

This works fine but now say I drop a script reference on my page to a third party library. For example:

<script src="//example.com/ThirdParty.js"></script>

Where the third party library loads it's own version of jQuery. Now I am getting the error:

Object doesn't support property or method 'accordion'.

After stepping through the code I found that it executes in the following order:

  1. ThirdParty.js
  2. jquery.min.js - third party version
  3. jquery.min.js - my version
  4. jquery.accordion.js - where $ points to my version reference of jQuery
  5. DisplayAccordion.js (callback function) - where $ points to the third party version of jQuery

Now I can see why I get the error because the plugin is attached to a different object. However I'm not sure why this would do this.

The information below will simply explain why using $.noConflict(true) will not work.

After some research on the issue. I modified my config to:

require.config({
    paths: {
        'jquery': '//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery'
    },
    map: {
        '*': { 'jquery': 'jquery-private' },
        'jquery-private': { 'jquery': 'jquery' }
    },
    shim: {
        'jquery.accordion': {
            deps: ['jquery']
        }
    }
});

Where jquery-private.js is defined as:

define(['jquery'], function($) {
    return $.noConflict(true);
});

Please note that this was taken from http://www.requirejs.org/docs/jquery.html#noconflictmap

Now it executes in the following order:

  1. ThirdParty.js
  2. jquery.min.js - third party version
  3. jquery-private.js (callback function)
  4. jquery.min.js - my version
  5. jquery.accordion.js - where $ is undefined
  6. DisplayAccordion.js (callback function) - where $ points to the third party version of jQuery

As you can imagine this wouldn't work either since $ is undefined within the jquery.accordion.js file.

After abit of further debugging I discovered the third party library also calls:

$.noConflict(true);

I think I understand what's going on here. When it calls $.noConflict(true) within the third party library it tries to set the global variables $ and jQuery to the previous version. However since no previous version has loaded it is set to undefined.

Now when it calls jquery-private.js and returns $.noConflict(true) it will return the global jQuery variable which has been set to undefined. However it will now set the global jQuery variable to the third party version of the library.

So when it loads jquery.accordion $ is undefined. But when it next calls DisplayAccordion.js it is now referencing the third party version of the jQuery library.

I'd appreciate it if someone could suggest a fix. Thanks

2 Answers 2

1

I'd suggest you make your own version of the jquery.accordion.js - making it into a RequireJS module (wrap it in a define(["jquery-private"], function($){ ... }); etc)

Everything else needs to ask for jquery-private instead of jquery - so the only mention of jquery in a require()/define() call would be in jquery-private itself.

While this would mean not being able to load it from a 3rd party auto-updated CDN, it would let it run on the "proper" version of jQuery.

In the meantime (if you know which one it is - GitHub lists several projects of that name) you could also submit a bug report / pull request to GitHub to make it RequireJS capable - then change the path once it's been updated :-)

Sign up to request clarification or add additional context in comments.

5 Comments

This won't fix the problem. Note how in the last step of the OP's attempt (both of attempts), the DisplayAccordion module, which does load jquery as a module, gets the third party version of jQuery, rather than the version the OP wants. What you suggest here will do the same thing: jquery.accordion will get the third party version of jQuery rather than the one the OP wants. If explained in my answer why this happens.
Doh - I see - the major problem with your answer is simply that it ignores the fact that OP has no control of the load order (which is why it doesn't help in any way other than informative). How about using the jquery-private module instead of jquery - it'll still require accordion to be wrapped, but should let the correct version be used...
Using jquery-private won't fix the issue. That's what the OP did in the 2nd attempt in the question. As I pointed out in my previous comment, in both attempts DisplayAccordion gets the wrong version. As for the value of my answer, yes, I've read the OP's comment and realize it won't work if the load order of the OP's code relative to ThirdParty.js cannot be constrained. For the record, the OP added the crucial caveat that there is no control over the load order after I posted my answer. At any rate, to me this indicates that the order should be constrained.
Thanks but as Louis has mentioned this is not going to work. Just found an interesting post which may help engineering.sharethrough.com/blog/2014/01/17/…. However this seems more targeted at the developers of third-party.js than me.
The load order was why I removed my old answer - technically my old answer would actually work, but it's also very hacky which isn't a good thing. If you knew anything about how RequireJS works, using jquery-private together with making accordion into an actual module using the result from the require call for jquery-private would also work - but everything wanting to use the accordion version would need to use jquery-private, not just that single call - try it and you'll see what I mean. jquery-private needs to be the only thing asking for jquery directly.
1

The only way I can see the behavior you initially get happening is if you load ThirdParty.js after RequireJS has been loaded. Here is what happens:

  1. RequireJS is loaded.

  2. ThirdParty.js loads.

  3. jquery.min.js third party version loads. It checks whether define.amd exists. It does, so it calls define('jquery', ... and registers as an AMD module.

  4. jquery.min.js your version loads. You may wonder why RequireJS would bother with loading this at all if jquery is already defined. We may be dealing with a race condition here. If there is anything that triggered the loading of jquery before step 3, I'd expect RequireJS to schedule a useless load of your version of jQuery. define('jquery', ... will be called again but will do nothing because of the first call earlier.

  5. jquery.accordion.js executes, where $ points to your version of jQuery. Yes, because in step 4, the global $ was set to your version.

  6. DisplayAccordion.js executes, where $ points to the third party version of jQuery. Yes, because in step 3, the it is the third party version that registered as a module.

To fix this, I would make sure that ThirdParty.js and its version of jQuery load before RequireJS. How you achieve this depends on how ThirdParty.js loads jQuery. Since ThirdParty.js calls $.noConflict, you should be okay after that with loading your own code and not getting interference.

1 Comment

Thanks I forgot that jQuery had that little define bit at the bottom. Unfortunately your suggested fix is not possible as this code is part of content management system and my client can drop the third party scripts anywhere within the body of the page.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.