Skip to content
mnaoumov.dev
Go back

require.js, jQuery and race conditions

Hi folks

I am working on one old legacy ASP.NET WebForms project. It had a lot of inline JavaScript rendered from server-side. Personally I think it is a very bad practice. I think the modern world tends to use static JavaScript files and unobtrusive markup.

I don’t see an easy way to refactor that quickly so I’m improving the code gradually.

I started to use require.js with my new scripts and migrate legacy scripts slowly.

Let me show you the problem I faced.

Due to the fact that I have to use both AMD scripts and legacy-abusing-window-object scripts I had to load jQuery twice.

On my master page I have

<asp:ScriptManager runat="server">
    <Scripts>
        <asp:ScriptReference Path="~/components/requirejs/require.js" />
        <asp:ScriptReference Path="~/_scripts/require.config.js" />
        <asp:ScriptReference Path="~/_scripts/some-new-amd-module.js" />
        <asp:ScriptReference Path="~/components/jquery/dist/jquery.js" />
        <asp:ScriptReference Path="~/_scripts/some-legacy-script.js" />
    </Scripts>
</asp:ScriptManager>

I load jquery.js explicitly because some-legacy-script.js depends on it.

some-new-amd-module.js looks like

(function script() {
    "use strict";

    define(["jquery", "some-jquery-plugin"], function module($) {
        $(function domReady() {
            $(".my-selector").somePlugin(); // The problem appears here
        });
    });
})();

The plugin *some-jquery-plugin.js* is not AMD plugin, it is just doing something like

$.fn.somePlugin = function() { // some logic }

And the problem is the following. Sometimes the call for somePlugin() fails (the line is commented above). It fails saying that somePlugin() does not exist.

It is not the permanent problem, it is pretty random which made me think that it is caused by race condition. And after some debugging when the issue occurred, I found that

> $ === window.$ false

require.js loads modules asynchronously so don’t know when exactly this will happen and in what order.

Our problem is the following: sometimes the load order happens to be

  1. jQuery loaded by require.js. It set window.$ variable and stored it internally within require.js cache
  2. jQuery loaded by a normal script tag. It overrode window.$ variable
  3. some-jquery-plugin.js is loaded. It set window.$.fn.somePlugin function
  4. some-new-amd-module.js is loaded. It took $ variable from require.js cache and tried to use $.fn.somePlugin function

and BANG!

So the problem was identified and how are we going to fix it?

One obvious solution is to get rid of local $ variable and this will fallback to the window.$

define(["jquery", "some-jquery-plugin"], function module() {

I don’t like this solution. I would prefer to write truly modular code.

I tried some approaches and none of them worked reliably. Finally I ended up with the one I would like to share with you.

require.config.js

(function script() {
    "use strict";

    require.config({
        map: {
            '*': { jquery: "jquery-fix-conflict" },
            "jquery-fix-conflict": { jquery: "jquery" }
        },
        shim: {
            "some-jquery-plugin": ["jquery"]
        }
    });
})();

and

jquery-fix-conflict.js

(function script() {
    "use strict";

    define(["jquery"], function module($) {
        setTimeout(function resetModule() {
            require.undef("jquery-fix-conflict");
        }, 0);
        window.jQuery = window.$ = $;
        return $;
    });
})();

This looks like a dirty hack and I wish to get rid of it but probably it will live until I rewrite everything to use AMD scripts.

Stay tuned

P.S. You may have noticed that I don’t use anonymous functions and provide some name for all functions even for IIFE ones. That’s my recent habit I’ve got after watching https://frontendmasters.com/courses/advanced-javascript/ The author Kyle Simpson made his point of readable call stacks.

P.S 2: All the require.js samples I’ve seen suggest to start your module straight with define but my habits force me to start with IIFE and “use strict”


Share this post on:

Previous Post
My recent open-source activity
Next Post
I raise jQuery bug for IE8