Skip to content

Instantly share code, notes, and snippets.

@tobsn
Created December 10, 2010 19:50
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save tobsn/736705 to your computer and use it in GitHub Desktop.
Save tobsn/736705 to your computer and use it in GitHub Desktop.
there are many problems when you act as third party that offers a javascript file and you want to use jquery - this solved all of my problems. the little function checks if jquery is already embedded, includes it and waits until its loaded if its missing
(function(j,q,u,e,r,y,R,o,x){try{o=jQuery;if(o&&(!R||(R&&o.fn.jquery==R))){x=true}}catch(er){}if(!x||(R&&o.fn.jquery!=R)){(q=j.createElement(q)).type='text/javascript';if(r){q.async=true}q.src='//ajax.googleapis.com/ajax/libs/jquery/'+(R||1)+'/jquery.min.js';u=j.getElementsByTagName(u)[0];q.onload=q.onreadystatechange=(function(){if(!e&&(!this.readyState||this.readyState=='loaded'||this.readyState=='complete')){e=true;x=jQuery;jQuery.noConflict(true)(function(){y(x)});q.onload=q.onreadystatechange=null;u.removeChild(q)}});u.appendChild(q)}else{y(o)}})(document,'script','head',false,false,(function($){$(function(){
/* code goes here */
console.log($.fn.jquery);
})}));
// readable:
(function (j, q, u, e, r, y, R, o, x ) {
// IE8 undefined crash fix
try {
// check if we can assign jQuery
o=jQuery;
// if jquery is defined and version is not set or version is set and matches the existing one
if( o && ( !R || ( R && o.fn.jquery == R ) ) ) {
x = true;
}
}
catch(er) {}
// if jquery isnt set or version is defined and its not matching
if( !x || ( R && o.fn.jquery != R ) ) {
(q = j.createElement(q)).type = 'text/javascript';
if (r) { q.async = true; }
// dont need http|https - /1/ means latest 1.x version
q.src = '//ajax.googleapis.com/ajax/libs/jquery/'+(R||1)+'/jquery.min.js';
u = j.getElementsByTagName(u)[0];
// onload event for callback
q.onload = q.onreadystatechange = (function () {
// first fire when completely loaded and parsed & check if this happened before
if (!e && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) {
e = true;
// copy space into new var, release control and execute callbacks with passed in own version of jQ
x = jQuery;
jQuery.noConflict(true)(function(){y(x);});
// IE memory leak protection
q.onload = q.onreadystatechange = null;
u.removeChild(q);
}
});
u.appendChild(q);
}
else {
// jquery is already there and version matches or we dont care about version
y( o );
}
// async callback
})(document, 'script', 'head', false, false, (function ($) {
$(function(){
/* code goes here */
console.log( $.fn.jquery );
});
// version if needed - optional (see compressed version above)
}),'1.4.2');
@SlexAxton
Copy link

What about sites using jquery 1.2.6, etc? It seems like even with a version check, you're trusting the user quite a bit to not compromise your plugins, data, or any functionality you rely on.

@tobsn
Copy link
Author

tobsn commented Dec 12, 2010

already working on it ;)

@tobsn
Copy link
Author

tobsn commented Dec 13, 2010

okay, its fixed.
i think those two first if's are redundant but dont have the time right now to check the logic - its definitely working tough.

@SlexAxton
Copy link

This is still pretty dangerous. You are sharing a data cache and a plugin namespace with whoever is on the page. If you decided to pull in jQuery UI, you'd override all their settings, etc. - If you store data on any elements, the jQuery.cache is shared, so collisions become possible including the event handlers that are stored in this cache. You also assume that no one ever edits a jQuery file. I can tell you from experience that many sites have no problem adding in their own functionality/patches directly into the jQuery source file. These files will return the right version in jQuery.fn.jquery, but be potentially harmful.

The best option I've seen to solve these issues at various levels of success is to reinject jQuery into the page, when possible. You could look for the actual script tag of jquery.min.js and reinject a new script tag (with a noConflict(true)) so you execute your own copy and don't share a data cache (or have jQuery.expando collisions), and prevent plugins from colliding. It will reinject from the cache, so it'll be a zero-byte hit. It still leaves you open to raw edits to the jQuery file (random patches, etc), so you could just look for a jQuery instance from the google/other apis in a script tag, and only reinject if it's a known good cdn.

Better yet, you could take an extra parameter in the initialization of your application. myApp.init(..., 'local.good.jquery.1.4.2.js') and inject that one if it's passed in. That way it's explicit to the user, for them to get good performance.

On top of all that, there is an unexpected side affect of injecting jQuery dynamically and using a noConflict call in the onload callback:

Unfortunately, all IEs less than version 9 don't execute an atomic callback after a script loads. This means that between the execution of your injected jQuery file and the callback where you invoke noConflict on the global jQuery function, other code can run.

This means that (in your example), if someone had 1.3.2 on their page, and you loaded your script in IE8, you'd load in 1.4.2, it would execute, then, the domReady/click event handler may execute (rare, but very possible, especially on the internet, where pages are loaded so often, that even race conditions show up often) between the time when jQuery pointed to your 1.4.2 copy, and the time when you called noConflict(true) on it. That would cause the dom ready callback to execute on your copy of jQuery, use it's data cache and then get lost whenever the noConflict call runs again later. Very bad. Very possible (actually likely to happen at least once over N loads where N is large).

All this to say: I can't see this as being a safe method of jQuery injection for third party tools. In order to do a safe injection, you need a reinjection (if it already exists) and subsquent reexecution of jQuery, and in <IE8 you need the noConflict call to be in the jQuery.js file so a race condition can't bite you. You could do some browser inference (e.g. if ("attachEvent" in window)) to serve up a jquery.withnoconflict.js file on your server and use a CDN for the rest of browsers that do guarantee atomic load callbacks. This has been the most reliable, cacheable, safe injection of a third party jQuery instance that I have found. Just my two cents.

@tobsn
Copy link
Author

tobsn commented Dec 13, 2010

do you have a small test case for it?
simple html with script to force a conflict - that would be helpful to find a way around it.
i just tested it with
script src="google-cdn-1.4.4"></script
script>console.log($.fn.jquery)</script
script src="file-on-my-server-with-code-above-including-1.4.2-same-console.log"></script
script>console.log($.fn.jquery)</script

would return:
1.4.4
1.4.4
1.4.2

@SlexAxton
Copy link

You aren't running any handlers, etc. It's pretty hard thing to see right away.

Here is the test I ran with asynchronous injection of scripts and their subsequent callback handlers. This is an easier example to show that this is possible. It applies to all scripts, including jQuery. I'm using labjs for the dynamic script injection, but that doesn't matter. I also tested with direct onload expandos, etc, and the results were the same.

http://slexaxton.com/jsloadtests/ (you may have to load it a few times, with a hard refresh, to see a failure in an IE, but it's pretty common).

In every browser that's not IE8 and below, it should read '# executed' followed by the same '# loaded.' - The order of the scripts themselves don't much matter, but the fact that scripts can execute between the execution of an injected script and the execution of their onload callbacks is the alarming thing.

A Screenshot in IE8 in case you/someone doesn't have it: http://slexaxton.com/pix/7b5c.png (notice 4-6-4 order)

vs.

A Screenshot in Chrome Dev channel (9.xx): http://slexaxton.com/pix/faee.png (all numbers load in pairs - aka an atomic callback)

Hope that helps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment