After some digging, I learned that many of these projects use a pattern called the Universal Module Definition.
- Your application is split up into distinct, self-contained pieces (modules), each responsible for a specific bit of functionality.
- A module doesn’t add any unnecessary variables or functions to the global namespace. It is encapsulated—it exposes an interface for other modules to use, but that’s it.
- Modules are potentially reusable in other contexts.
- Modules do not depend rigidly on each other. In other words, they’re loosely coupled, and dependencies could probably be swapped without too much trouble.
Probably not the best definition in the world (Addy Osmani explains it much better than I can), but it captures the basic idea as far as I understand it.
As it turns out, built-in module functionality is coming in the ECMAScript 6 specification, but that still appears to be a ways off. In the meantime, other, less-official specifications have emerged to fill in the gap. The big players are CommonJS and Asynchronous Module Definition (AMD). CommonJS is the system used in Node.js, and the most popular implementation of AMD is RequireJS, which I like to use on the front end.1
There are many ways you could go about solving this problem. But one solution I particularly like is UMD—the Universal Module Definition.
The Universal Module Definition
UMD is actually nothing more than a collection of patterns you can find in a repository on GitHub. There are lots of different patterns, or definitions, each for a different set of requirements. But they’re all essentially variations on the same idea.
For example, let’s look at returnExports.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
I won’t go into a lot of detail about how IIFEs work (see Ben Alman’s article for an excellent explanation). In a nutshell, an IIFE consists of the following things:
- a function definition
- parentheses wrapped around the function to turn it into a function expression, which can be executed immediately, whereas a function declaration cannot
- a pair of parentheses at the end, optionally containing arguments to be passed into the function; these parentheses signal that the function is to be executed right away
IIFEs are a useful tool for encapsulating code, because variables declared inside the function are not accessible outside it. In the case of UMD, the IIFE gives you control over exactly how the module is exposed.
Let’s go back to the
returnExports example. Here it is again with the internals stripped out:
1 2 3 4 5 6 7 8 9
The function has two parameters,
factory. If you look at the arguments at the bottom, you can see that the argument passed in for
this, which represents the global object (the
window object in a browser context). Although the global object is accessible inside the function (since it’s global), using a local variable to reference it is often a good idea because it shortens the lookup chain and can possibly improve performance.
factory parameter gets the value of a function that is passed in as the second argument. This factory function is where the module is actually defined. It’s the equivalent of the
define function in RequireJS, or the function you assign to
module.exports in Node. As in those individual situations, this function can return anything, but usually it’s going to be an object or a function.
Now let’s look at what happens inside the main function:
1 2 3 4 5 6 7 8 9 10 11
I call this part the environment detection. Here we have a series of if-then statements to determine what kind of module loader is present, if there is any. First we test if it’s an AMD loader by looking for a
define function; that function should also have a property called
amd. If it’s there, we use the
factory function as the callback for
define, thus defining an AMD module.
If an AMD loader isn’t there there, we then look for the CommonJS
exports object. As the comments in the code mention, this part doesn’t follow the strict CommonJS specification, but there is another definition for that. As with AMD, if we are using a CommonJS-like loader, then the function assigns the
factory function to
module.exports, making it ready to be loaded as a Node module.
If neither of those loaders are present, then the last option is to load the module as a global variable. This is where the
root parameter comes in: the script assigns the
factory function as a property of
root, which in most cases is going to point to
window. Here they have called the global variable
returnExports, but that’s just because that’s the name of the module pattern we’re using—you can call it anything. If the script gets to this point, the module will be available as a global, and so the script can be included through an HTML script tag, and other scripts will be able to use it.
You might have noticed an argument called
b that’s getting passed to the
factory function. That’s basically just a demonstration of how dependencies are handled in UMD. If your module depends on other modules, you can put them in the way
b is done here. If not, you can take that part out. In fact, the actual returnExports definition includes a variant that uses no dependencies, so you can follow that one if it applies.
The definition we looked at here is only one of many included in the UMD repository. Be sure to check it out the other versions to see if there is a different ones that better fits the particular requirements of the module you’re writing.
The downside of the UMD approach is that it does add a bit of boilerplate to your code. I personally don’t think it’s that much, but I suppose it could get old after a while. To make things a little easier, I created a package of Sublime Text snippets for all the UMD patterns. If you use Sublime Text, that package should hopefully make it a little easier and quicker to incorporate UMD into your workflow. There is also a Grunt plugin and a Gulp plugin available to automate the process of wrapping your code in UMD.
An alternative to UMD is something I just recently learned about called uRequire. The goal of uRequire is apparently to take the ugliness out of UMD boilerplate and the like (which, again, I don’t think is too much of a problem) and make it easier to adapt your modules to most environments. I haven’t used it myself, but it looks promising, so maybe I will write about that in the future.
So there you go. I hope this post has helped you understand how UMD works and encouraged you to start using it in your own projects.
I am also aware of a project called Browserify, which lets you load modules in Node fashion in the browser. It’s really picking up steam lately, but my experience is mainly with AMD and RequireJS, so that’s what I’m focusing on here. But since Browserify uses Node-style module loading, UMD can still be used for it.↩