Cover by Addy Osmani

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

O'Reilly logo

AMD and CommonJS: Competing, but Equally Valid Standards

Both AMD and CommonJS are valid module formats with different end goals.

AMD adopts a browser-first approach to development, opting for asynchronous behavior and simplified backward compatibility, but it doesn’t have any concept of file I/O. It supports objects, functions, constructors, strings, JSON and many other types of modules, running natively in the browser. It’s incredibly flexible.

CommonJS on the other hand takes a server-first approach, assuming synchronous behavior, no global baggage, and attempts to cater for the future (on the server). What we mean by this is that because CommonJS supports unwrapped modules, it can feel a little more close to the ES.next/Harmony specifications, freeing us of the define() wrapper that AMD enforces. CommonJS modules however only support objects as modules.

UMD: AMD and CommonJS-Compatible Modules for Plug-ins

For developers wishing to create modules that can work in both browser and server-side environments, existing solutions could be considered a little lacking. To help alleviate this, James Burke, a number of other developers, and I created Universal Module Definition (UMD; https://github.com/umdjs/umd).

UMD is an experimental module format that allows the definition of modules that work in both client and server environments with all or most of the popular script-loading techniques available at the time of writing. Although the idea of (yet) another module format may be daunting, we will cover UMD briefly for the sake of thoroughness.

We originally began defining UMD by taking a look at the simplified CommonJS wrapper supported in the AMD specification. For developers wishing to write modules as if they were CommonJS modules, the following CommonJS-compatible format could be used:

Basic AMD hybrid format

define( function ( require, exports, module ){
    
    var shuffler = require( "lib/shuffle" );

    exports.randomize = function( input ){
        return shuffler.shuffle( input );
    }
});

It’s important however to note that a module is really only treated as a CommonJS module if it doesn’t contain a dependency array and the definition function contains one parameter at minimum. This also won’t work correctly on some devices (e.g., the PS3). For further information about the above wrapper, see http://requirejs.org/docs/api.html#cjsmodule.

Taking this further, we wanted to provide a number of different patterns that not just worked with AMD and CommonJS, but also solved common compatibility problems developers wishing to develop such modules had with other environments.

One such variation we can see below allows us to use CommonJS, AMD, or browser globals to create a module.

Using CommonJS, AMD, or browser globals to create a module

Define a module commonJsStrict, which depends on another module called b. The name of the module is implied by the filename, and it’s best practice for the file name and the exported global to have the same name.

If the module b also uses the same type of boilerplate in the browser, it will create a global .b that is used. If we don’t wish to support the browser global patch, we can remove the root and pass this as the first argument to the top function.

(function ( root, factory ) {
    if ( typeof exports === 'object' ) {
        // CommonJS
        factory( exports, require('b') );
    } else if ( typeof define === 'function' && define.amd ) {
        // AMD. Register as an anonymous module.
        define( ['exports', 'b'], factory);
    } else {
        // Browser globals
        factory( (root.commonJsStrict = {}), root.b );
    }
}(this, function ( exports, b ) {
    //use b in some fashion.

    // attach properties to the exports object to define
    // the exported module properties.
    exports.action = function () {};
}));

The UMD repository contains variations covering modules that work optimally in the browser, those best for providing exports, those optimal for CommonJS runtimes, and even those that work best for defining jQuery plug-ins, which we will look at next.

jQuery plug-ins that function in all environments

UMD provides two patterns for working with jQuery plug-ins: one that defines plug-ins that work well with AMD and browser globals and another that can also work in CommonJS environments. jQuery is not likely to be used in most CommonJS environments, so keep this in mind, unless we’re working with an environment that does that does play well with it.

We will now define a plug-in composed of a core and an extension to that core.

  • Our plug-in will be composed of a core and an extension to that core.

  • The core plug-in is loaded into a $.core namespace, which can then be easily extended using plug-in extensions via the namespacing pattern. Plug-ins loaded via script tags automatically populate a plugin namespace under core (i.e., $.core.plugin.methodName()).

The pattern can be quite nice to work with, because plug-in extensions can access properties and methods defined in the base or, with a little tweaking, override default behavior so that it can be extended to do more. A loader is also not required to make any of this fully functional.

For more details of what is being done, please see the inline comments in the code samples below.

usage.html:

<script type="text/javascript" src="jquery-1.7.2.min.js"></script>
<script type="text/javascript" src="pluginCore.js"></script>
<script type="text/javascript" src="pluginExtension.js"></script>

<script type="text/javascript">

$(function(){

    // Our plug-in "core" is exposed under a core namespace in 
    // this example, which we first cache
    var core = $.core;

    // Then use use some of the built-in core functionality to 
    // highlight all divs in the page yellow
    core.highlightAll();

    // Access the plug-ins (extensions) loaded into the "plugin"
    // namespace of our core module:

    // Set the first div in the page to have a green background.
    core.plugin.setGreen( "div:first");
    // Here we're making use of the core's "highlight" method
    // under the hood from a plug-in loaded in after it

    // Set the last div to the "errorColor" property defined in 
    // our core module/plug-in. If we review the code further down,
    // we can see how easy it is to consume properties and methods
    // between the core and other plug-ins
    core.plugin.setRed("div:last");
});
    
</script>

pluginCore.js:

// Module/plug-in core
// Note: the wrapper code we see around the module is what enables
// us to support multiple module formats and specifications by 
// mapping the arguments defined to what a specific format expects
// to be present. Our actual module functionality is defined lower 
// down, where a named module and exports are demonstrated. 
// 
// Note that dependencies can just as easily be declared if required
// and should work as demonstrated earlier with the AMD module examples.

(function ( name, definition ){
  var theModule = definition(),
      // this is considered "safe":
      hasDefine = typeof define === "function" && define.amd,
      // hasDefine = typeof define === "function",
      hasExports = typeof module !== "undefined" && module.exports;

  if ( hasDefine ){ // AMD Module
    define(theModule);
  } else if ( hasExports ) { // Node.js Module
    module.exports = theModule;
  } else { // Assign to common namespaces or simply the global object (window)
    ( this.jQuery || this.ender || this.$ || this)[name] = theModule;
  }
})( "core", function () {
    var module = this;
    module.plugins = [];
    module.highlightColor = "yellow";
    module.errorColor = "red";

  // define the core module here and return the public API

  // This is the highlight method used by the core highlightAll()
  // method and all of the plug-ins highlighting elements different
  // colors
  module.highlight = function( el,strColor ){
    if( this.jQuery ){
      jQuery(el).css( "background", strColor );
    }
  }
  return {
      highlightAll:function(){
        module.highlight("div", module.highlightColor);
      }
  };

});

pluginExtension.js:

// Extension to module core

(function ( name, definition ) {
    var theModule = definition(),
        hasDefine = typeof define === "function",
        hasExports = typeof module !== "undefined" && module.exports;

    if ( hasDefine ) { // AMD Module
        define(theModule);
    } else if ( hasExports ) { // Node.js Module
        module.exports = theModule;
    } else { 

        // Assign to common namespaces or simply the global object (window)
        // account for for flat-file/global module extensions
        var obj = null,
            namespaces,
            scope;

        obj = null;
        namespaces = name.split(".");
        scope = ( this.jQuery || this.ender || this.$ || this );

        for ( var i = 0; i < namespaces.length; i++ ) {
            var packageName = namespaces[i];
            if ( obj && i == namespaces.length - 1 ) {
                obj[packageName] = theModule;
            } else if ( typeof scope[packageName] === "undefined" ) {
                scope[packageName] = {};
            }
            obj = scope[packageName];
        }

    }
})( "core.plugin" , function () {

    // Define our module here and return the public API.
    // This code could be easily adapted with the core to
    // allow for methods that overwrite and extend core functionality
    // in order to expand the highlight method to do more if we wish.
    return {
        setGreen: function ( el ) {
            highlight(el, "green");
        },
        setRed: function ( el ) {
            highlight(el, errorColor);
        }
    };

});

UMD doesn’t aim to replace AMD nor CommonJS but merely offers some supplemental assistance for developers wishing to get their code working in more environments today. For further information or to contribute suggestions toward this experimental format, see https://github.com/umdjs/umd.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required