This entry is part 8 of 12 in the series Server-Side JavaScript, Pintura, and Persevere 2.0

For developers that are creating libraries and modules, it is generally preferable to make your code available to as broad of range of users as possible. There are several different module formats in JavaScript (a module is an encapsulation of code that draws on other modules). Picking one format is often mutually exclusive to the other formats. However, in this post I want to demonstrate how you can write JavaScript modules that can be used with multiple module loaders using some simple boilerplate. Of course not all the module loaders necessarily make sense for all modules. If you are writing a module that relies on the Node file system API, it only needs it to work with the NodeJS/CommonJS module format. Likewise, a DOM-based module wouldn’t need to run on Node.

Here we’ll deal with the actual module format, the mechanism of specifying dependencies and exporting or returning functions from the module that can be used by the users. This does not deal with the normalization of the actual APIs of the underlying system, although you might want to take a look at promised-io if you would like normalization of IO interaction across the browser, Node, and Rhino/Narwhal.

The four formats we’ll review are:

  • CommonJS – This format has been widely accepted on a number of platforms, perhaps most notable is Node. CommonJS uses require() calls to load dependencies and an exports object to export functions to other consumers. This is an easy to use system with very broad adoption with server-side JavaScript. The require() calls are synchronous, and consequently CommonJS is criticized for being poorly suited for direct use in the browser where synchronous dependency loading is inefficient, locks the user interface, and eliminates the ability to do cross-domain loading.
  • CommonJS Asynchronous Module Definition API (AMD) – This format was largely developed by James Burke for the RequireJS library and molded to work as a wrapper around CommonJS modules for asynchronous loading of dependencies. AMD is first and foremost targeted at the browser. This is a fantastic module system, combining the best ideas from numerous other module attempts, and is supported by RequireJS and Yabble. Dojo is moving to support and use AMD for better performance and interoperability.
  • Dojo – This module format is used by the Dojo Toolkit, and is similar to the CommonJS module system, and inspired the work done with AMD.
  • Plain JS Scripts – This isn’t really a module, as dependencies can’t be expressed. However, making dependency-free modules available as plain JS scripts can provide an opportunity for very wide distribution since the scripts can be used virtually anywhere.

First, let’s look at very lightweight, but powerful boilerplate, an adapter that will let you write a module for both AMD and CommonJS standard module systems, using AMD style dependency declarations. This combination allows us to create modules that can be used both in browser and server environments:

(function(define){
define(["dep1", "dep2"], // a list of our dependencies
function(dep1, dep2){ // the arguments match up with our dependencies
 // the contents of our module
  ...
  // what we return from our module
  return {
    someExportedFunction: function(){...},
    ...
  } 
});
})(typeof define=="function"?define:function(factory){module.exports=factory.apply(this, deps.map(require));});

We can also achieve compatibility with AMD and standard CommonJS by putting boilerplate around the standard CommonJS module format:

(function(define){
define(function(require,exports){
// module contents
 var dep1 = require("dep1");
 exports.someExportedFunction = function(){...};
 ...
});
})(typeof define=="function"?define:function(factory){factory(require,exports)});

You can even combine styles, for example you could use CommonJS style require calls with the AMD style of using a return to export functionality:

(function(define){
define(function(require){
 return {
   someMethod: function(){
    ...
   }
  }
});
})(typeof define=="function"?define:function(factory){module.exports = factory(require)});

One thing to note about the design of this boilerplate is that it is specifically designed to preserve a form suitable for static analysis. It is tempting to use define as a value in an expression resulting in the function to be called like (define||function(deps,factory){...})(function([..deps...]){...});, but this fails to preserve the define(...) form that is expected by dependency resolution components like RequireJS’s build tool and Transporter.

Next we will look at the boilerplate for creating dependency free modules that can run on all four systems:

(function(name, factory){
	typeof require == "undefined" ? 
		(typeof dojo != "undefined" && dojo.provide(name)) &
			// direct script
			factory(this[name] = {}) : 
		typeof exports == "undefined" ? 
			// browser transport/C loader or RequireJS
			define(name, ["exports"], factory) :
			// CommonJS environment
			factory(exports);
})("module-name", function(exports){
	// actual module contents following CommonJS format (assigning values/function as properties of exports)	
	...
});

Remember that you can’t declare any dependencies with this last boilerplate, because it is designed to work as a plain script without the help of any module loader (that would resolve the dependencies). But, if you have a dependency free module, this gives you maximum portability.

Using Any type of Module

If you are on the other side of the fence, and you are an application developer that wants to utilize modules of different formats in your application, there are other options. If you are writing a Node application, you can use Nodules, it will accept CommonJS standard, AMD, and CommonJS Transport/D module formats. You can also use this in conjunction with Transporter to deliver modules in CommonJS or AMD format to the browser.

On the browser side, Yabble is an excellent module loader that works with CommonJS, AMD, and Transport/D module formats.

These are simple templates you can use to make it easy to maximize the portability of your modules and the audience that can utilize your code. I have started wrapping some of my modules that make sense to use both client and server side, and you can do so too.