Search code examples
javascriptjquery-uirequirejsamdrequirejs-optimizer

jQuery.ui undefined after requirejs optimization


I have several JavaScript files in my web application which were previously loaded through <script> tags, in order of relative dependencies. Now I'm re-organizing them through require.js in order to accumulate and minify them for production.

For starters, I'd like to simply load all files into the global (window) context, without AMD encapsulation yet:

require([
    'jquery'], function() {
    require([
        'jquery-ui'], function() {
        require([
            'jquery-ui.touch-punch',
            // [...]
        ]);
    });
});

The idea here is that 'jquery' defines a global (window context) jQuery variable, 'jquery-ui' sets jQuery.ui and 'jquery-ui.touch-punch' alters jQuery.ui (for better touch support).

This works well when running as is, i.e. without optimization. However, after compiling into one file and minifying using the RequireJS optimizer, the following error occurs:

Uncaught TypeError: Cannot read property 'mouse' of undefined

This happens on a line trying to access jQuery.ui.mouse.

Inside the browser console, jQuery is correctly set in the window context, but jQuery.ui is undefined. However, manually executing require(['jquery-ui']) does set jQuery.ui as expected.

It seems like jQuery UI behaves differently after optimization, but I can't see how exactly or why. What am I doing wrong?


Solution

  • The Scoop

    Set the dependencies between non-AMD modules using shim rather than through nested require calls (which do not in fact set dependencies). Make sure also to use wrapShim: true in the configuration you give r.js.

    Explanation

    One or more of the modules you are trying to load are not AMD modules, but you are not actually defining dependencies between your non-AMD modules.

    When you nest require calls like you do, you are coercing the order in which some modules are loaded at run time, but this does not actually establish a dependency between modules. So this strategy works as long as the modules are not optimized but may fail after optimization.

    There are only two ways to establish dependencies:

    1. By passing an array of dependencies to a define call. This method is used by modules actually written for the AMD spec. (RequireJS also supports using the CommonJS way of requiring modules but behind the scenes, it is transformed to a define call with an array of dependencies. So it does not constitute a 3rd way.)

    2. By setting a shim which list dependencies. This method is for non-AMD modules.

    If you don't set a shim, then the optimizer is free to order the non-AMD modules in whichever order it wants. jquery-ui.touch-punch could appear before jquery-ui and jquery in the optimized file, because there's no reason it shouldn't, and then you'd run into trouble. If you look at the code of this plugin, you see that it is not AMD-aware. It just expects jQuery and jQuery UI to be present and will fail if they are not present.

    When you do set a shim, then you coerce the order of the non-AMD modules in the optimized file.

    You need wrapShim because the jquery-ui.touch-punch when using an AMD-aware version of jQuery UI. Otherwise, jQuery UI's factory won't be run before the plugin needs it.