AMD is better for the web than CommonJS modules

2011-10-01 07:56

AMD is better for the web than CommonJS modules

by Miller Medeiros

at 2011-09-30 23:56:50

original http://blog.millermedeiros.com/2011/09/amd-is-better-for-the-web-than-commonjs-modules/

I’ve seen a few libraries and tools later using different kinds of ways to
handle dependency management, some of them are very similar to the way that
CommonJS modules looks like:

//use another module
var myLib = require('myPackage/myLib');

function foo(){
    console.log('foo');
    myLib.doSomething();
}

//expose module API
exports.foo = foo;

The beauty of CommonJS modules is how simple they are, you simply require something synchronously and that module can be used right away (just like magic).

The same code written in traditional AMD (AMD modules are flexible and can be written in different ways) would look like:

define(['myPackage/myLib'], function(myLib){

    function foo(){
        console.log('foo');
        myLib.doSomething();
    }

    //expose module API
    return {
        foo : foo
    };

});

Edit: Or even using this “simplified CommonJS wrapper” syntax:

define(function(require, exports, module){

    var myLib = require('myPackage/myLib');

    function foo(){
        console.log('foo');
        myLib.doSomething();
    }

    //expose module API
    exports.foo = foo;

});

In my opinion AMD modules are way better for the web right now than CJS modules, the asynchronous nature of AMD make it slightly more complex but it also expands the AMD power to a level that CJS modules can only dream of

AMD advantages

  • AMD modules are flexible.
  • Plugin support (extremely useful and powerful).
  • Can load more than just JavaScript files.
  • Path aliases and other advanced config settings to simplify path resolution and dependency listing.
  • Works in the browser without a build (most popular AMD loaders supports this feature).
  • Is asynchronous by nature.
  • Works in current browsers, no need to wait for Harmony.
  • Dependencies are usually listed on the same location making it easy to identify what are the dependencies.
  • Avoid globals by default since modules are wrapped by closures.
  • Can run the same code on both environments by simply using an AMD loader that works on a CJS environment (see r.js).
  • It’s being adopted by popular JavaScript libraries like Dojo (1.6+), Mootools (2.0), jQuery (1.7)
  • Lazy-load scripts if needed.

Examples

Path alias to simplify module look-up and file versioning (really important feature):

//configure RequireJS/curljs paths
require.config({
    //set base folder of all dependencies
    baseUrl : 'js',
    paths : {
        //avoid typing long path all the time
        'foo' : 'lib/lorem-ipsum/dolor/foo',
        //version file for cache busting
        'lorem/ipsum' : 'lorem/ipsum-v23'
    }
});

define(['foo/bar', 'lorem/ipsum'], function(bar, ipsum){
    //this will load 'js/lib/lorem-ipsum/dolor/foo/bar.js'
    //and 'js/lorem/ipsum-v23.js'
});

Using plugins to load different kinds of dependencies:

//using plugins to load different file types
define([
        'text!./lipsum.md',
        'image!img/lol_cat.jpg'
    ],
    function(lipsum, lol_cat){
        console.log(lipsum);
        document.body.appendChild(lol_cat);
    }
);

Dynamic module loading + returning other types of data:

define(['require'], function(require){
    var mods = ['foo/bar', 'lorem', 'dolor'];

    function loadModuleByIndex(index){
        //dependency should be an Array
        require([ mods[index] ], function(m){
            //let's assume all modules have an init() method
            m.init();
        });
    }

    //modules can return any kind of data (string, object, function, etc..)
    return loadModuleByIndex;
});

Notes

The main reason for writing this post was a twit by John Hann:

the more “universal module boilerplate” i see, the more i want to go with CJS 1.1.1 format. but then again, AMD has way more flexibility. hm – @unscriptable

And also because I’ve seen that some new tools and libraries are trying to mimic the CJS module format (I’m looking at you ender.js) or that have some special notation to include other files during build (google closure, sproutcore, …) and I feel they are going to the “wrong way” since AMD is clearly more flexible and becoming more popular each day.

I’ve been using AMD (RequireJS) for almost 1 year and can’t imagine going back to the era of complex namespacing (MyApp.something.bar), awkward script concatenation (which can usually result in problems if concatened on the wrong order), adding multiple script tags to the HTML, not being able to load scripts on demand, explaining to eveyone that they should wrap the code inside a self executing function to avoid generating globals, not being able to easily share code between projects, etc…

Don’t enforce a build step during development (even if automatic), one of the beauties of developing JavaScript is that you can simply refresh the browser to see the updates, run the build task only for deployment (combine and minify files to reduce number of requests and increase load performance).

AMD greatest benefit isn’t being able to load scripts on-demand, as some people may think, the greatest benefit is the increase of the code organization/modularity and also the reduced need for globals/namespacing.

More

RequireJS and curl.js are the most popular AMD loaders, check them out and use r.js to optimize your AMD modules into a single file and do some pre-processing of external resources before deploy. r.js can also be used to run AMD modules inside node.js and Rhino and to convert CommonJS modules into an AMD compatible format.

RequireJS mailing list is a good place to ask questions. There is also the AMD-implement list where different AMD loader implementors are discussing about features and how things should work to increase compatibility of the code.

James Burke (RequireJS creator) wrote a good post explaining why AMD is a good module format and why we should avoid the “bikesheding”, read it if you considering other module formats.

Check also a really good presentation by Brian Cavalier and John Hann post (both created after my post).

PS: when I started coding JavaScript as my main programing language I really missed “Classes”, now I see that what I was really missing was being able to split my code between multiple files “in a sane way” and to share code across different applications, inheritance is almost unrelated with that… (composition > inheritance)

Embrace AMD and be happy

Edit 2011/09/30: added “simplified CommonJS wrapper” suggested by James Burke and also renamed packages suggested by John Hann. Also removed comments about node.js supporting basic AMD modules since it seems they removed the native support.

Edit 2011/10/06: added link to John Hann’s post and Brian Cavalier’s presentation.

Related