Question

I'm making a widget that consists of a snippet of code to drop in your site that will then pull in content via js. Here's an example snippet:

<div data-token="bc1cea6304e8" id="my-widget">
    <script src="http://localhost:8080/assets/eg-widget.js" type="text/javascript"></script>
</div>

I'm doing a test to check if the page already has jQuery, and if so, that the version is at least 1.5.

if (typeof jQuery == 'undefined' || isVersion("1.5", ">")) {

    var script_tag = document.createElement('script');
    script_tag.setAttribute("type", "text/javascript");
    script_tag.setAttribute("src",
        "http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js");

    // Try to find the head, otherwise default to the documentElement
    (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(script_tag);

    if (script_tag.readyState) {
        script_tag.onreadystatechange = function () { // For old versions of IE
            if (this.readyState == 'complete' || this.readyState == 'loaded') {
                scriptLoadHandler();
            }
        };
    } else { // Other browsers
        script_tag.onload = scriptLoadHandler;
    }
} else {
    // The jQuery version on the window is the one we want to use
    jQueryEg = window.jQuery;
    scriptLoadHandler();
}

In either case, we set a variable jQueryEg to the jQuery we decide to use so that we can avoid conflicts.

If jQuery is not already loaded, everything is dandy. But if an older version is loaded, we run into conflicts when trying to load other libraries such as jQuery-ui.

To do this, we set window.jQuery to our newly loaded jQuery so that when we load jquery-ui and bootstrap.js, their references to window.jQuery or $ point to the correct version.

The problem is that sometimes the other parts of the page are calling jQuery at the same time that we've set window.jQuery to our version which causes problems.

Any thoughts on how we can avoid these conflicts?

jQueryEg = window.jQuery.noConflict(true);
window.jQueryEg = jQueryEg;

// jquery-ui reference window.jQuery so we need to set that to our new jquery for a sec

if (jQueryEg.ui == undefined || jQueryEg.ui.datepicker == undefined) {
    var otherjQuery = window.jQuery;
    window.jQuery = jQueryEg;

    var script_tag = document.createElement('script');
    script_tag.setAttribute("type", "text/javascript");
    script_tag.setAttribute("src",
        "//ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.min.js");
    (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(script_tag);

    script_tag.onload = function () {
        window.jQuery = otherjQuery;
        main();
    };

}
Was it helpful?

Solution

Having run into this exact situation before, in the end the determination was what you probably already know, and don't want to hear: There is simply no good way to avoid all edge-cases when loading multiple versions of external libraries like that, due to the reasons you have already specified. A summary of the complications involved are:

  • The main problem, of course: that the loading of external scripts is asynchronous by its nature, and there is no "just before execution" / "just after execution" callback to ensure that everything is wrapped nicely. ie: there is no way to "surround" dynamically-loaded code.
  • Scripts are executed in-order when placed in the document directly, but there is no way to conditionally load one based on something determined by another.
  • Finally, for user-friendliness reasons (ie: allowing random people across the web to use your snippet with zero code knowledge and no support staff), restrictions such as "ensure this snippet is pasted before any other scripts!" are right out. Even if they could work (*and they won't, really see the other two points), they would be ignored, especially by those who don't have full control over their pages.

Unfortunately, the only answer is the obvious one: Host modified versions of the libraries on your own servers.

Of course, you don't need to serve these versions all the time, so you can still benefit from the use of Google's CDN. Meanwhile, you can still use your own CDN to deliver the modified versions. The main guidelines to follow are:

  1. Run your script after the entire page is loaded (using onDOMReady, etc), to ensure the conflict doesn't happen "after" your own script is included.
  2. Determine if a conflict is possible. Serve your modified versions in those cases, and serve from a public CDN otherwise.
  3. Check for compatibility with other libraries often. Your code will evolve over time, just as the libraries you use will. The "determine if a conflict is possible" step needs to be accurate for this to work. Err on the side of caution: serve your own version if you are unsure.
  4. Always provide an easy option for the paster to say "always use the modified version", in case your detection fails somehow.
  5. Always serve compressed, uglified versions of your scripts, with appropriate cache headers, to reduce the load on your own servers.

You'll usually find that the number of hits on your own server are actually quite minimal, comparatively. As always, don't trust me: keep statistics on your own, real-world scenarios!

OTHER TIPS

Once I run into a similar issue. Looking at the jQuery-UI code, I noticed that it's all wrapped in an immediately invoked function expression (or maybe I wrapped it myself? I can't remember):

( function ( jQuery, undefined ){
  // Code here...
})( jQuery );

to create private scopes. So my solution was to

  1. Save the current jQuery to jQuery_OLD
  2. Load the new jQuery version
  3. Load jQuery-UI, and let it grab the compatible version
  4. Restore old jQuery

So the following gives the idea

<script src="jquery-1.4.js"></script>
<script>
    var jQuery_1_4 = jQuery.noConflict();
</script>
<script src="jquery-1.8.js"> </script>
<!-- load jquery UI -->
<script src="jquery-ui.js"> </script>
<!-- restore window.jQuery -->
<script>
    jQuery = jQuery_1_4;
</script>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top