Universal JavaScript packages

Writing modular and portable JavaScript is a challenge. Should I expose my package as a global object, or use a module format like CommonJS or AMD? In this post I’ll show you how to create universally consumable JavaScript packages with very little effort. I will also explain why this is only barely a manageable solution.

Note: This article was originally published on February 17 2015 on the SthlmConnection company blog. Things have moved along a bit since then, but a lot of the same issues still exist around JS modules.

JavaScript is very flexible when it comes to how variables and functions are scoped. It’s up to each script file if and how it wants to use the global scope. The situation is OK for smallish projects where you are in control of all of the source code, but as soon as you start depending on external packages, you lose control over how the global namespace is used (or polluted). There is also no way in the language itself to define dependencies on other pieces of code. Traditionally, all of the code that an application depends on has to be manually loaded from the start.

For a long time, the standard way to expose your code to other people’s code has been to have each package define a single object with a (hopefully) unique name on the global scope. With jQuery, library authors learned to piggy-back on its global “$” namespace using jQuery plugins.

JavaScript Modules

But JavaScript needed something more robust. Something that made it possible to define code dependencies and encapsulate imported code in a scope that the application developer defines. And so JavaScript modules were born:

var myLibrary = require('vendor/my_library');

The above code would load the appropriate script file and populate a local variable with the package’s main object/function. That sounds good, and it does work well in certain contexts (like Node.js), but it’s a little more complicated in other circumstances. Script loading can occur in many different ways in JavaScript. For browser applications, it can happen through a build step that concatenates all dependencies into a single script file, or through asynchronous HTTP requests. For server applications, script files are read from the file system on demand. The tools vary a lot depending on what type of applications you’re building.

Also, there isn’t one JavaScript module format. At the moment, there are two popular, competing formats: CommonJS (mostly used on the server, shown in the example above) and AMD (the more common option for browser applications). A third format has been established as part of the next version of the JavaScript specification — ES6, but it has not been widely implemented yet.

Until ES6 modules are commonly available, applications that want to use modules rely on external tools that implement AMD (RequireJS) or CommonJS (Node.js, Browserify).

Universal encapsulation

So how do you decide which specification to use? The good news is that as a package author, you may not have to. There is a way to create JavaScript packages that support both formats.

The UMD project defines a number of templates for wrapping your code in a universal module definition, such that it is recognized as a module everywhere. This is achieved with a couple of conditional statements that checks to see which module system is in use in the current environment, if any.

Let’s say you’ve built a JavaScript library called MyLibrary. It exposes a single global function:

var MyLibrary = function() {
// This is where the magic happens.
}

Wrapped in a UMD template, this would become:

(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.MyLibrary = factory();
}
}(this, function() { // Define and return a single function or object.
var MyLibrary = function() {
// This is where the magic happens.
}
return MyLibrary;
}));

This is quite ugly, but it gets the job done. The package is not only loadable as an AMD or CommonJS module, it is also exposed as a global object if neither of the two is available.

Let the machines do the dirty work

But what if you don’t want to pollute your source files with all of this irrelevant boilerplate code? You add UMD as an automatic step in your build process! If you are using Gulp or Grunt, just add gulp-umd or grunt-umd to your project.

This is a simple example of how you could set this up with Gulp:

var gulp = require('gulp'); // <- Side note: CommonJS in action!
var umd = require('gulp-umd');
gulp.task('default', ['scripts']);
gulp.task('scripts', function() {
return gulp.src('src/*.js')
.pipe(umd())
.pipe(gulp.dest('build/'));
});

And just like that, your source files are automatically turned into universally compatible JavaScript modules. All you need to do is to make sure that each source file defines exactly one global object or function.

Limitations

As Trek Glowacki explains in a recent post, you run into trouble as soon as your package relies on yet another package. This is perhaps more of a problem with JavaScript package managers (npm and Bower) and the way they define dependencies, but it’s all related.

The bottom line is that if you have external dependencies in your library, you will either have to include them in your own library code (horrible), or tell your users to define them manually in their projects — if you want to use universal modules that is. In other words, universal dependency management and modularity in JS remains an unsolved problem.

Further reading


Originally published at www.sthlmconnection.com.