Wrapper to allow a module to work with AMD/CommonJs or script tags?
Asked Answered
B

3

17

I just took a shot at wrapping one of our modules that is meant to be included via a <script> tag in some boilerplate to allow optional AMD loading with requirejs.

It was quite painful and the best I could come up with is:

(function(){
var exports, jQuery;
if (typeof window.define === 'function' && typeof window.requirejs === 'function') {
    exports     = {};
    define(['jquery'], function (jq) {
        jQuery = jq;
        return thisModule();
    });
} else {
    exports     = window;
    jQuery      = window.jQuery;
    thisModule();
}


function thisModule() {
}

})();

Notice that this is

  • A LOT of boilerplate
  • Requires you to declare dependencies in variables (only jQuery in this case thankfully) AND amd
  • Needs yet more code if I want to have CommonJs support.

I am primarily concerned about the second point as that one is going to be a doozy when I get beyond wrapping our core files. I'm sure there's some neat(er) wrapper implementations out there but I can't find any.

Anyone have any tips?

Besom answered 6/2, 2013 at 18:58 Comment(4)
One thought: I wouldn't test typeof window.requirejs == 'function'. You probably don't care about the AMD implementation. Instead test window.define.amd. But that's just a tweak.Otis
Thanks @ScottSauyet - I think I saw that somewhere in the knockout.js source and was wondering if that's a better way.Besom
Note also that Require has an option to load non-AMD modules reasonably well with only a small amount of configuration. So although yours might be an interesting goal, you might be able to get away without it.Otis
@ScottSauyet - yup, I'm aware of the shim - mine is also partially an exercise in getting our devs to declare their dependencies explicitly.Besom
P
8

What you are trying to re-create something that already exists, I did exactly the same thing, coming up with a slightly different solution in my StackOverflow question.

To cut a long story short the name you need to know is "Universal Module Definition" and there's a GitHub located at https://github.com/umdjs/umd with a variety of different implementations.

Populace answered 28/6, 2013 at 12:20 Comment(1)
awesome, thanks - I knew there must be something like that floating around.Besom
C
0

Are you trying to do this for an internal module, or an external module?

If you're not require-ing in additional modules, would it be possible to build your modules assuming AMD, and then just shim out the define() function somewhere else in your code, if it's not there? Of course, you'll have to use named modules, but you'd have to basically do that anyway...

If your modules all return their exports from the define() function, it would be relatively simple, and your shimmed define function could look something like this:

//Whatever additional guards you want could be added, of course...
if (typeof(window.define) === undefined){
  window.define = function(name, deps, callback){
    window.myNamespace[name] = callback();
  };
}

At least that way you wouldn't have to add the boilerplate to every module...

If you've got a larger library with lots of interdependent sub-modules, you're probably just going to have to commit to using Require all the way, or not, and then use your wrapper code around your whole library to handle AMD support, the way Jquery and Knockout JS do it.

Cockneyfy answered 14/2, 2013 at 16:37 Comment(6)
This won't work - if you are doing define('blah', ['jQuery', 'underscore'], function($, _) { .... }) your approach would always result in $ === _ === undefined. This is the major problem I was having and why I had to declare dependencies the way that I did.Besom
Yeah, that's why I said you can only do this if you're not using require to pull in other modules. But now I'm a bit confused. If you're pulling in dependencies via require, then why are you trying so hard to make require optional?Cockneyfy
We have a large set of internally used scripts that have accumulated over years. They have all sorts of implicit dependency assumptions for other libraries and each other. You know how it goes. These libraries are quite useful. We still have plenty of projects that don't use amd and aren't going to be, certain other ones I'd like to do properly with amd. So basically it has to work both ways. It kind of seems like this is the sort of situation a ton of shops should be finding themselves in around now (and the votes on this post seem to confirm it).Besom
That makes a lot of sense, and its a tough problem. What would happen if you just assumed Jquery was around, instead of trying to require it in? I've worked on a few projects where we pulled Jquery and a few other 3rd party libraries out of our dependency chain, partly to simplify our code, and partly because we wanted them in a separate bundle anyway. It's actually a pretty reasonable way to work, and it might simplify your problem a bit.Cockneyfy
Yes but what about the next level of dependencies which depend on my core file, or dependencies that depend on that?Besom
For things like Jquery, Jquery UI, Underscore, and other low-level 3rd party libraries, that hasn't been a problem for me. It's safe enough to assume that they're there, or that they can be added if needed. For my apps, dependency management of my own modules has been more important than require-ing in every 3rd party library I use. I agree with you that it's a tougher problem if your dependency chain is several levels deep, or if you're sharing modules across several teams, but those problems will probably always be a bit harder to solve, even if you use Require all the way down.Cockneyfy
B
0

After hacking on this I managed to come up with the following which seems significantly better and could even be included as a shim in a regular script tag:

A couple notes and drawbacks.

  • You have to replace any explicit setting on the window object with the exports object
  • It assumes that any dependency exists as a similarly named property on the window object (though it also makes sure to place that property there). This is generally safe enough in my case but you could easily hack in something like the requirejs paths configuration.
  • Actually I'm not convinced that the entire exports concept is particularly necessary, or at least not necessary in all cases.
(function () {
    var define, exports = {};
    if (window.define && window.define.amd) {
        define = window.define;
    } else {
        exports = window;
        define = function (name, dependencies, fn) {
            var deps = [];
            for (var i = 0; i < dependencies.length; i++)
                deps.push(window[dependencies[i]]);
            var module = fn.apply(undefined, deps);
            if (!window[name]) window[name] = module;
        };
    }

    define('mylib.interaction', ['jQuery', 'mylib.core', 'jQuery.UI'], function($, mylib) {
        return /*....blah...*/;
    })
})()
Besom answered 15/2, 2013 at 19:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.