Best practices for keeping AMD modules portable with CommonJS

342 views
Skip to first unread message

Patrick Mueller

unread,
May 22, 2011, 4:27:18 PM5/22/11
to amd-im...@googlegroups.com
Is there a set of best practices if I'd like to be able to use my AMD modules in a CommonJS environment?

For instance, in AMD allows the object a module exports to be set by returning it from the factory function.  However, there is no portable way to do a "set exports" in CommonJS, so it seems like you shouldn't use the "return the object to export" pattern if you want to maintain portability.

What else?

--
Patrick Mueller
http://muellerware.org

James Burke

unread,
May 22, 2011, 6:33:48 PM5/22/11
to amd-im...@googlegroups.com
On Sun, May 22, 2011 at 1:27 PM, Patrick Mueller <pmu...@gmail.com> wrote:
> Is there a set of best practices if I'd like to be able to use my AMD
> modules in a CommonJS environment?
> For instance, in AMD allows the object a module exports to be set by
> returning it from the factory function.  However, there is no portable way
> to do a "set exports" in CommonJS, so it seems like you shouldn't use the
> "return the object to export" pattern if you want to maintain portability.

I'll answer this along with what you ask in the other thread about
boilerplate that works in both environments:

(function (define) {

define(function (require, exports, module) {
//Place all dependencies at the top of the
//module. do not use code branches like
//if/else to grab dependencies
var a = require('a');

//Only assign properties to exports, don't use return.
exports.color = 'blue';
});

}(typeof define === 'function' && define.amd ? define : function (cb){
cb(require, exports, module);
});

When this file gets optimized by an AMD-aware optimizer, it will add a
name and dependency array to the interior define call. So, the module
would not be usable after optimization in a traditional CommonJS
module environment. That should be OK though, since the source module
can be, and it does not have to be a named module ID.

So note that as long as there is just one module in a file, a module
ID is not a required argument to the define() call, they can be
anonymous modules. There are some simplified AMD loaders that only
deal with named module IDs/only deal with optimized code, but a fully
compliant AMD loader can handle anonymous modules.

So, you could apply the above boilerplate via a tool to "convert" an
AMD module to be usable in a CommonJS environment, or always author in
CommonJS and then apply the following boilerplate via a tool to
convert the module for AMD use:

define(function(require, exports, module){
//traditional commonjs module goes here.
});

The r.js project[1] now includes a -convert command that can do this
wrapping (it also pulls out the dependencies into a dependency array).
It has not been formally released yet, you will need to build it with
via "node dist.js".

The other option is to always author in AMD and use an AMD adapter for
the CommonJS environment, like the r.js RequireJS adapter for Node and
Rhino.

The part of traditional CommonJS modules that are not supported in AMD
are supporting conditional/computed dependencies. So things like this:

if (someCondition){
a = require('a1');
}else{
a = require('a2');
}

In an async environment, this is best done via a callback-type of
require. Note that the above can work in AMD, it just means that both
a1 and a2 will be evaluated as dependencies before this code is
called. So, if a2 should never be executed if someCondition is true,
that may be a problem. To avoid having to qualify these kinds of rules
too much, I normally just say "don't use computed dependencies via
require('string') but use a callback-based require", but that is
likely to be an environment-specific callback API. This caveat is not
needed though for most modules.

There are also some very brittle circular dependency cases that rely
on setting exports properties before doing the require() for the
circular dependency. These are normally brittle since they depend on
which module in the circular dependency is executed first. Circular
dependencies are possible in AMD, so it normally means just reworking
the circular dependency logic a bit.

The vast majority of CommonJS modules should work fine with just
adding the define() wrapping mentioned above.

[1] https://github.com/jrburke/r.js

James

Agelos Pikoulas

unread,
Sep 28, 2013, 2:18:27 PM9/28/13
to amd-im...@googlegroups.com
Check the documentation of uRequire  that lists (and solves) the various issues and incompatibilities between AMD and commonJs module formats.
Reply all
Reply to author
Forward
0 new messages