Double jQuery Jeopardy

The jQuery JavaScript library is a wonderful tool. It’s also very popular, being used on 65% of the Top 10 million highest-trafficked sites on the Web. Unfortunately, jQuery’s popularity, combined with third-party JavaScript code, can contribute to a subtle bug that can be hard to track down and understand the first time you encounter it. The bug is triggered when multiple copies of the jQuery library are included on a page.

I first encountered this bug on a site that had been in production for several years. A request was made to add some third-party JavaScript code to the site. This seemed harmless enough, especially since everything about it was self-contained and the existing site code didn’t need to interact with the new JavaScript. The initial tests looked good, but then a bug was found. Most of the jQuery/JavaScript code on the site still functioned normally, but a few pieces failed with JavaScript errors that said certain plugin methods were not a function.

This was definitely odd. The files that defined the plugins were still being included properly, with no 404 or JavaScript errors. No question, the bug was being caused by the third-party JavaScript file because removing it made the code work again. Stranger still, the only time plugin methods were failing with errors was when the plugin methods were called in event handlers that fired sometime after the third-party JavaScript code had loaded. The plugin methods could be called before then, but after the third-party code loaded it was as if the plugins had never been defined.

I realized that the third-party code was loading its own version of jQuery and that was the reason for the error. The following code snippet is some simple HTML that re-creates the same bug. Notice that it doesn’t matter that the duplicate version of jQuery is the same version, loaded from the same URL as the original.

This will cause the JavaScript error that complains the “myPlugin” property is not a function. Logging out the value of “jQuery.fn.myPlugin” will show that it’s undefined after the page has loaded.

What’s happening is the second inclusion of jQuery overwrites the global “jQuery” and “$” variables with a new jQuery object. This jQuery object is different from the original object that had the plugin method “myPlugin” added to its prototype, so it does not have a “myPlugin” method defined. The “jQuery(function () { … })” code runs after the inclusion of the second jQuery library and references jQuery via the global “$” variable, so by the time it runs, the global “jQuery” and “$” variables point to the new, unique jQuery object. Note that it doesn’t matter the same version of jQuery was included again because the jQuery file will always create a new jQuery object.

The solution is to create a variable that references the original jQuery library in the code that calls the “myPlugin” method and then access jQuery through that variable instead of through the global “jQuery” or “$” variables. There are a variety of ways to do this on your own. You can create your own unique variable name and then access jQuery via that variable inside the domReady callback:

Or for the convenience of still accessing jQuery through the “$” variable, you can create your own reference to jQuery through a closure:

The simplest “why didn’t I know about that” solution relies on the fact that jQuery passes a reference to itself as the first argument to the callback function you register to run on domReady. You can name that argument whatever you want, but naming it “$” makes the fix trivial:

Notice that the only difference between the original version and the working version is the addition of a single “$” in just the right place. Certainly brings to mind the legend of the plumber who charges $1 to tighten a bolt and $99 for knowing what bolt to tighten into the digital age.

I highly recommend using this pattern to capture a local reference to the current jQuery library whenever setting up domReady code or other event listeners that will run later. Ideally, you wouldn’t be including jQuery twice on the same page, but third-party code is often out of your control and it’s likely to include its own version of jQuery. Just adding a single “$” to your code can save you from unexpected bugs years down the road.

Incidentally, I wish I had taken my own advice. A few months after fixing the initial bugs on the site I mentioned to start the article, another bug on a seldom-used page was discovered. It was being caused by the same issue.

Resources

  • Usage of JavaScript Libraries for Websites

    W3Techs
  • The Handyman’s Invoice

    Snopes

Once Upon a Time…