Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RequireJs Issue When Loading Multiple Versions of jQuery

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

like image 778
nfplee Avatar asked Sep 09 '15 15:09

nfplee


People also ask

Can you include multiple version of jQuery in the same page?

Yes, you can use multiple versions of jQuery on the same page. To avoid any kind of conflict, use the jQuery. noConflict() method.

In what situation you would use multiple version of jQuery and how would you include them?

noConflict() method allows you to use multiple frameworks , while using jQuery. The noConflict() method releases the hold on the $ shortcut identifier, so that other scripts can use it. In jQuery's case, $ is just an alias for jQuery, so all functionality is available without using $.

Why do we need RequireJS?

RequireJS is a JavaScript library and file loader which manages the dependencies between JavaScript files and in modular programming. It also helps to improve the speed and quality of the code.

What is RequireJS config?

Advertisements. RequireJS can be initialized by passing the main configuration in the HTML template through the data-main attribute. It is used by RequireJS to know which module to load in your application. For instance − <script data-main = "scripts/main" src = "scripts/require.js"></script>

How does RequireJS work with jQuery?

While RequireJS loads jQuery just like any other dependency, jQuery's wide use and extensive plugin ecosystem mean you'll likely have other scripts in your project that also depend on jQuery.

What is the AMD module name in RequireJS config?

jQuery defines named AMD module 'jquery' (all lower case) when it detects AMD/RequireJS. To reduce confusion, we recommend using 'jquery' as the module name in your requirejs.config. requirejs.config ( { baseUrl: 'js/lib', paths: { // the left side is the module ID, // the right side is the path to // the jQuery file, relative to baseUrl.

Can I use a different version of jQuery for different libraries?

In some cases you may require a different version of jQuery to support a Javascript library written for a specific version. However, simply including a second version of jQuery may cause unexpected conflicts. NOTE: To check the current version of jQuery, use the $.fn.jquery property.

Can I use multiple versions of jQuery with Sitefinity?

However, simply including a second version of jQuery may cause unexpected conflicts. NOTE: To check the current version of jQuery, use the $.fn.jquery property. After loading a second version of jQuery, calling the jQuery.noConflict (true) method restores the globally scoped jQuery variables to those of Sitefinity’s built-in jQuery library.


2 Answers

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

like image 182
Rycochet Avatar answered Oct 18 '22 12:10

Rycochet


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.

like image 20
Louis Avatar answered Oct 18 '22 11:10

Louis