javascriptjquerytwitter-bootstraprequirejs

jQuery noconflict, bootstrap and requirejs


The environment I'm developing in is using jQuery 1.7.1 while I need to use specifically jQuery 2.1.3. But when I do use 2.1.3 some legacy code breaks (as its using 1.7.1) so I had to use jQuery.noconflict on 2.1.3

jquery-private.js:

define(["jquery"], function(jQuery){
    return jQuery.noConflict(true);
});

so then inside my requirejs config file I have this:

config.js:

requirejs.config({
    baseUrl: '../',
    paths: {
        'jquery': 'js/jquery/jquery',
        'jqueryui': 'js/jquery-ui/jquery-ui',
        'bootstrap': 'js/bootstrap',
    },
    map: {
        // '*' means all modules will get 'jquery-private'
        // for their 'jquery' dependency.
        '*': {
            'jquery': 'jquery-private'
        },

        // 'jquery-private' wants the real jQuery module
        // though. If this line was not here, there would
        // be an unresolvable cyclic dependency.
        'jquery-private': {
            'jquery': 'jquery'
        }
    },
    shim: {
        'bootstrap': {
            deps: ['jquery']
        }
    }
});

so then when I require bootstrap

widget.js:

require([
    "jquery",
    "bootstrap"
], function(jqr){
    // SOME CODE
});

I get an error:

Uncaught Error: Bootstrap's JavaScript requires jQuery version 1.9.1 or higher

So bootstrap is picking up 1.7.1.

How can I make it pickup 2.1.3 without making changes to bootstrap.js file?


Solution

  • Why it is failing?

    Your current attempt cannot work because your jquery-private setup ensures that as soon as jQuery is loaded as a RequireJS module, noConflict() is called. So long as the code loaded with RequireJS is referring to jQuery through a define call (e.g. define(['jquery'], function ($) {) there is no problem. However, any code that is shimmed will refer to the $ or jQuery globals and thus will refer version 1.7.1.

    A Solution

    Absent any other constraints, the preferred solution is to have the entire code base use the same jQuery version. Upgrade 1.7.1 to the latest in the 1.x series, and use that. According to the download page, there is no API difference between 2.x and a relatively recent release in the 1.x series (I would say 1.9.x and later). Of course, this means that the code that depended on 1.7.1 may have to be updated to work with the latest jQuery in the 1.x series.

    I would have suggested upgrading to the 2.x series but you mention in comments needing compatibility with IE 6 and 7. Note here: if you do manage to get 1.7.1 and 2.1.3 to load for the same page, that part of the code that uses 2.1.3 wont' be compatible with IE 6 and 7 so the page is effectively no longer compatible with IE 6 and 7.

    To use this solution, besides the changes that must be done for the upgrade, you must:

    1. Make sure that jQuery loads before RequireJS. Otherwise, it will detect RequireJS and call define but then it won't be reliably available to code that is not loaded with RequireJS. (I said it won't be "reliably available" because although once the jquery module is loaded, then the jQuery and $ globals will exist, the problem is that code that is not loaded with RequireJS cannot wait for RequireJS to load jQuery. So, unless you take pains to write your own synchronization code, it won't work reliably.)

    2. Create a RequireJS module which merely makes the jQuery loaded before RequireJS available to RequireJS modules as a module, so:

       define('jquery', function () { return jQuery; });
      

    This could be placed just before your call to require.config. This creates a "fake" jquery module which merely returns the global jQuery symbol. (It could just as well return $.)

    Alternatives?

    I do not see a robust alternative to the solution above. It is not that difficult to come up with a proof of concept that would demonstrate another approach. However, it would fail as soon as you try to use it on a real project. The problem is that RequireJS is inherently asynchronous, so you cannot start messing with the $ and jQuery globals to set them the way you want and be sure that everything you want to happen will happen at the time you want it to happen. Moreover, any code loaded before RequireJS that happens to initiate asynchronous operations that could happen interleaved with RequireJS' module loading would throw a monkey wrench into the whole operation. I'd rather refrain from proposing solutions that will fail as soon as they go from proof of concept to real world applications.

    I have better solution to define this module as this

        define(function(require) {
            var $ = require('jquery');
            //drop the `true` if you want jQuery (but not $) to remain global
            return $.noConflict(true);
        });