1

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?

Cedric Reichenbach
  • 8,970
  • 6
  • 54
  • 89

1 Answers1

2

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.

Louis
  • 146,715
  • 28
  • 274
  • 320
  • Thanks a lot. I tried with the following shim configuration: `shim: {'jquery-ui': {deps: ['jquery']}, 'jquery-ui.touch-punch': {deps: ['jquery-ui']}}`, but still have the same problem. Debugging shows that jquery-ui's `factory` that would set `$.ui` is never being executed after optimization, even though `define('jquery-ui', ["jquery"], factory)` is run. – Cedric Reichenbach Dec 04 '16 at 20:31
  • Did you make sure `shim` is present both in the run time configuration and in the build configuration? Having the file which contains the call to `require.config` be part of the build is not enough. You need to set `shim` explicitly in your build config, or (better, IMO) set the `mainConfigFile` option to point to the file that has `require.config`. – Louis Dec 05 '16 at 11:34
  • Yeah, I had assured this before. But there was another piece of config missing: `wrapShim: false`. AFAIU, this is due to jQueryUI AMD-requiring jQuery, but being depended upon by non-AMD touch-punch. Said setting apparently wraps touch-punch in an AMD module to assure correct loading order ([documentation](https://github.com/requirejs/r.js/blob/master/build/example.build.js)). Maybe you can add this to your answer for completeness, and I'll accept it. – Cedric Reichenbach Dec 05 '16 at 20:00
  • Were you originally using `wrapShim: true` in your configuration? If not, then adding `wrapShim: false` should make no difference, because `false` is the default if you do not set `wrapShim`. – Louis Dec 05 '16 at 20:05
  • Oh sorry, that was a typo. I'm now using `wrapShim: true` to make it work, and didn't have it before (i.e. default to `false`). – Cedric Reichenbach Dec 05 '16 at 21:02
  • Ah, yes. My usage scenarios are slightly different so I had forgotten that issue. I've edited my answer. – Louis Dec 06 '16 at 11:41