How to avoid loading jQuery twice with RequireJS

By February 27, 2013Uncategorized

When working with existing sites or content management systems, you have little say on where and when jQuery is loaded. To complicate matters, some pages may have jQuery auto-loaded, and others may not (yay for performance boosts, nay for client-side plugins). Do you bite the bullet and write unmanageable scripts? Or do you believe in RequireJS and dodge the bullet matrix-style? Let me show you how.

With RequireJS, you can asynchronously load JavaScript files when needed. For example, you can write a simple JavaScript module that depends on jQuery like this:

require([
   'jquery'
], function ($) {
   $('body').append('<h1>Added by RequireJS module</h1>');       
});

Where does ‘jquery’ come from? That is the path alias or module name you set up in the RequireJS config. This will allow it to asynchronously load jQuery when requested by this name, then pass it in as a parameter in the function for you to use (the parameter here is “$” but can be anything).

So when creating “paths” aliases in your main.js, put a condition in there to test if jQuery has been loaded or not. Then you can return the existing jQuery object or load up your own. You can even require a minimum version of jQuery if you like. Check this out:

;(function () {
  var paths = { ... };
     
    //HANDLE JQUERY IF LOADED ALREADY TO AVOID OVERWRITING EXISTING JQUERY PROPERTIES AND PLUGINS
    //CHECK FOR OLD VERSIONS OF JQUERY
    var oldjQuery = !!(window.jQuery && !!window.jQuery.fn.jquery.match(/^1\.[0-4]/));
  
    //LOAD JQUERY IF NOT AVAILABLE OR BELOW MIN
    if (!window.jQuery || oldjQuery) {
        paths.jquery = [
            '//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min',
            //If the CDN location fails, load from this location
            'libs/jquery/jquery.min'
        ];
    } else {
        //REGISTER THE CURRENT JQUERY
        define('jquery', [], function () { return window.jQuery; });
    }
     
    //CONFIGURE REQUIRE JS
    require.config({
        ...
        paths: paths,
        ...
    });
  
    //START REQUIRE JS
    require([
        'jquery',
        'app'
    ], function ($, App) {
        //HANDLE MULTIPLE JQUERY VERSIONS IF NECESSARY
        if (oldjQuery) $.noConflict(true);
  
        //INITIALIZE APP
        App.init();
    });
})();

Now when ‘jquery’ is required in any of your JavaScript modules, it will reuse the existing jQuery object by finding the module named ‘jquery’, or RequireJS will asynchronously load it from the path you specified. Cool huh?

Caveat

There is a catch to the way you set this up though. You must load your require.js and main.js AFTER the existing jQuery is linked on the page. If main.js is called before, then the jQuery from the CMS might load before RequireJS finishes retrieving your own jQuery file (after it already tested negative for the existing jQuery). Best thing to do is to load require.js, main.js, and all your JavaScript modules in the footer of the page.

However, with Sitefinity it makes sense to load it directly underneath it’s auto-loaded jQuery. This way, your RequireJS modules will work in the page designer too. The way to do this is to link require.js and main.js after the opening <form /> tag. This is because Sitefinity inserts its own jQuery as the first control in the <form />. With this assumption, you can do this:

<body>
    <form id="form1" runat="server">
        <asp:PlaceHolder ID="PlaceHolder1" runat="server">
            <script src="<%= ResolveUrl("~/Scripts/require.js") %>" type="text/javascript"></script>
            <script src="<%= ResolveUrl("~/Scripts/main.js") %>" type="text/javascript"></script>
        </asp:PlaceHolder>
...

You can also do this in the code-behind somewhere:

PageManager.ConfigureScriptManager(this.Page, ScriptRef.JQuery);
 
var scripts = PageManager.ConfigureScriptManager(this.Page, ScriptRef.Empty);
scripts.Scripts.Add(new ScriptReference(path + "/libs/require/require.js"));
scripts.Scripts.Add(new ScriptReference(path + "/main.js"));

With this code, it using the script manager Sitefinity uses and adds jQuery, then all the script references AFTER (as they appear in your code). The PageManager.ConfigureManager will not add jQuery again if it was already loaded.

 Then on the frontend (for both of the cases above), it will get rendered like this:

<body>
    <form method="post" action="" id="form1">
           …
    <script src="/ScriptResource.axd?d=OZdGa8QSokU5bJG-N6...2Dm9dH7QRbLzwVmoe690&t=ffffffff8d3388bc" type="text/javascript"></script>
    <script src="/Scripts/require.js" type="text/javascript"></script>
    <script src="/Scripts/main.js" type="text/javascript"></script>
      ...

In this example, the jQuery condition in main.js will usually find jQuery loaded by Sitefinity and reuse the object. However, on the pages where Sitefinity “doesn’t need” jQuery, RequireJS will asynchronously load the jQuery file from the path you specified. Either way, ONE jQuery is served to all your modules and self-contained. No more wiping out each other’s jQuery plugin namespace!

Happy Coding!!

The following two tabs change content below.