Code in JavaScript the smart, modular way

JavaScript words

Treat your JavaScript with the respect it deserves, with more modular design, AMD modules, and CommonJS

Some people still seem surprised that JavaScript is regarded as a respectable, grown-up programming language for serious applications. In fact, JavaScript development has been maturing nicely for years, with the best practices for modular development as a prime example.

The benefits of writing modular code are well documented: greater maintainability, avoiding monolithic files, and decoupling code into units that can be tested properly. For those of you who'd like to get caught up quickly, here are the common practices employed by modern JavaScript developers to write modularized code.

Module pattern

Let's start with a basic design pattern called the module pattern. As you might suspect, this enables us to write code in a modular way, allowing us to protect the execution context of given modules and to expose globally only what we want to expose.The pattern looks something like:

(function(){

//'private' variable

var orderId = 123;

// expose methods and variables by attaching them

// to the global object

window.orderModule = {

getOrderId: function(){

//brought to you by closures

return orderId;

}

};

})()

An anonymous function expression, acting as a factory in this case, is written and called immediately. For speed, you can explicitly pass in variables to the function call, effectively rebinding those variables to a local scope. You'll also see this sometimes as a defensive maneuver in libraries supporting old browsers where certain values (such asundefined) are writeable properties.

(function(global, undefined){

// code here can access the global object quickly,

// and 'undefined' is sure to be 'undefined'

// NOTE: In modern browsers 'undefined' isn't writeable,

// but it's worth keeping it in mind

// when writing code for old browsers.

})(this)

This is just a design pattern. With this technique, to write modular JavaScript, you don't need to include extra libraries. The lack of dependencies is a big plus (or mission critical) in some settings, particularly if you're writing a library. You'll see that most of the popular libraries will use this pattern to encapsulate internal functions and variables, exposing only what's necessary.

If you're writing an application, however, there are a few downsides to this approach. Let's say you create a module that sets a few methods on window.orders. If you want to use those methods in other parts of your application, you need to make sure the module is included before you call them. Then, in the code where you're callingwindow.orders.getOrderId, you write the code and hope the other script has loaded.

This may not seem like the end of the world, but it can quickly spiral out of control on complicated projects -- and managing script inclusion order gets to be a pain. Also, all of your files have to be synchronously loaded, or you'll invite race conditions in to break your code. If only there was a way to explicitly declare the modules you wanted to use for a given bit of code....

AMD (asynchronous module definition)
AMD was born out of the need for specifying explicit dependencies while avoiding the synchronous loading of all scripts. It's easy to use in a browser but is not native, so you need to include a library that does script-loading, like RequireJS or curl.js. Here is what it looks like to define a module using AMD:

// libs/order-module.js

define(function(){

//'private' variable

var orderId = 123;

// expose methods and variables by returning them

return {

getOrderId: function(){

return orderId;

}

});

It looks similar to what we were dealing with before, except that instead of immediately calling our factory function directly, we're passing as an argument to define. The real magic starts happening when you want to use the module later on:

define( [ 'libs/order-module' ], function(orderModule) {

orderModule.getOrderId(); //evaluates to 123

});

The first argument of define is now an array of dependencies, which can be arbitrarily long, and the factory function lists formal parameters for those dependencies to be attached to it. Now, some dependencies you need may have dependencies of their own, but with AMD, you don't need to know that:

// src/utils.js

define( [ 'libs/underscore' ], function(_) {

return {

moduleId: 'foo',

_ : _

});

// src/myapp.js

define( [

'libs/jquery',

'libs/handlebars',

'src/utils'

], function($, Handlebars, Utils){

// Use each of the stated dependencies without

// worrying if they're there or not.

$('div').addClass('bar');

// Sub dependencies have also been taken care of

Utils._.keys(window);

});

This is a great way to develop modular JavaScript when dealing with a lot of moving parts and dependencies. The responsibility of ordering and including scripts is now on the shoulders of the script-loader, leaving you free to simply state what you need and begin using it.

On the other hand, there are a few potential issues. First, you have an additional library to include and learn to use. I have no experience with curl.js, but RequireJS involves learning how to set up configuration for your project. This will take some hours to get familiar with the settings, after which writing the initial configuration should take mere minutes. Also, module definitions can grow lengthy if they lead to a pile of dependencies. Here's an example, taken from the explanation of this problem in the RequireJS documentation:

// From RequireJS documentation:

// http://requirejs.org/docs/whyamd.html#sugar

define([ "require", "jquery", "blade/object", "blade/fn", "rdapi",

"oauth", "blade/jig", "blade/url", "dispatch", "accounts",

"storage", "services", "widgets/AccountPanel", "widgets/TabButton",

"widgets/AddAccount", "less", "osTheme", "jquery-ui-1.8.7.min",

"jquery.textOverflow"],

function (require, $, object, fn, rdapi,

oauth, jig, url, dispatch, accounts,

storage, services, AccountPanel, TabButton,

AddAccount, less, osTheme) {

});

Ouch! RequireJS provides some syntactic sugar to deal with this, which looks quite a bit like another popular API for modular development, CommonJS.

CJS (CommonJS)
If you've ever written server-side JavaScript using Node.js, you've used CommonJS modules. Each file you write isn't wrapped in anything fancy, but has access to a variable called exports to which you can assign anything you want to be exposed by the module. Here's what it looks like:

// a 'private' variable

var orderId = 123;

exports.getOrderId = function() {

return orderId;

};

Then when you want to use the module, you declare it inline:

// orderModule gets the value of 'exports'

var orderModule = require('./order-module');

orderModule.getOrderId(); // evaluates to 123

Syntactically, this has always looked better to me, mostly because it doesn't involve the wasteful indentation present in the other options we've discussed. On the other hand, it differs greatly from the others, in that it's designed for synchronous loading of dependencies. This makes more sense on the server, but won't do on the front end. Synchronous dependency loading means longer page load times, which is unacceptable for the Web. While CJS is by far my favorite-looking module syntax, I get to use it on the server only (and while writing mobile apps with Appcelerator's Titanium Studio).

One to rule them all
The current draft of the sixth edition of ECMAScript (ES6), the spec from which JavaScript is implemented, adds native support for modules. The spec is still in draft form, but it's worth taking a peek at what the future could look like for modular development. Quite a few new keywords are used in the ES6 spec (also known as Harmony), several of which are used with modules:

// libs/order-module.js

var orderId = 123;

export var getOrderId = function(){

return orderId;

};

You can call it in later in several ways:

import { getOrderId } from "libs/order-module";

getOrderId();

Above, you pick and choose which exports within a module you want to bind to local variables. Alternatively, you can import the whole module as if it were an object (similar to the exports object in CJS modules):

import "libs/order-module" as orderModule;

orderModule.getOrderId();

There's much more to ES6 modules (check out Dr. Axel Rauschmayer's 2ality blog for more), but the example should show you a few things. First, we have a syntax similar to CJS modules, which means extra indentation is nowhere to be found, although that's not always the case, as you will find in the 2ality link above. What's not obvious by looking at the example, particularly because it looks so much like CJS, is that the modules listed in the import statement are loaded asynchronously.

The end result is the easy-to-read syntax of CJS mixed with the asynchronous nature of AMD. Unfortunately, it'll be a while before these are supported fully across all commonly targeted browsers. That said, "a while" has been growing shorter and shorter as browser vendors tighten their release cycles.

Today, a combination of these tools is the way to go when it comes to modular JavaScript development. It all depends of what you're doing. If you're writing a library, use the module design pattern. If you're building applications for the browser, use AMD modules with a script-loader. If you're on the server, take advantage of CJS modules. Eventually, of course, ES6 will be supported across the board -- at which point you can do things the ES6 way and scrap the rest!

Questions or thoughts? Feel free to leave a message below in the comments section or reach out to me on Twitter,@freethejazz.

This story, "Code in JavaScript the smart, modular way" was originally published by InfoWorld.

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more