Both module loaders and shared namespaces are valid techniques for modularising your JavaScript code. However, doing both at the same time is a recipe for difficult-to-track defects.

On a recent large-scale JavaScript development project, we were open to different strategies for modularising our code. The version of Backbone Boilerplate that we bootstrapped the project from supported require.js, but also seemed to encourage a shared namespace. Furthermore, Backbone itself used global namespacing, having previously eschewed AMD. So we thought to ourselves: lets use both a module loader and shared namespaces. How bad could it be?

Not good, as it turns out. I’ll give you an example which uses AMD modules.

Say you have a namespace.js module, which does nothing other than provide a place to put stuff:

[source language=”javascript” light=”true”]
// namespace.js
define([],
function() {
return {};
});
[/source]

And you then have another module a.js that adds stuff to the namespace, and then returns the namespace:

[source language=”javascript” light=”true”]
// a.js
define([
"namespace"
],
function(namespace) {
namespace.a = "I’m module A";
return namespace;
});
[/source]

And a module b.js that imports a.js:

[source language=”javascript” light=”true”]
// b.js
define([
"a"
],
function(namespace) {

return …;
});
[/source]

All good so far. But say that we also have a module c.js that imports namespace.js and b.js, but forgets to import a.js – even though it actually also uses the namespace.a property:

[source language=”javascript” light=”true”]
// c.js
define([
"namespace",
"b"
],
function(namespace, b) {
console.log(namespace.a);
return …;
});
[/source]

If we were to execute this module, it’ll output to the console:

I'm module A

It worked because b.js imported a.js. However, if you were to tweak b.js so that it didn’t import a.js anymore:

[source language=”javascript” light=”true”]
// b.js
define([],
function() {
// Don’t use ‘a’ anymore
return …;
});
[/source]

Then c.js will suddenly stop working as expected, because a.js hasn’t been called by the time it executes, with the result that namespace.a is undefined.

That mightn’t sound too bad, but it gets more complicated if b.js imported another module, which in turn imported a.js. Or b.js imported another module, which in turn imported another module, which…well, I think you get the picture. Any changes to your dependency tree can result in things in seemingly unrelated places suddenly becoming undefined.

I want to emphasize here that it’s shared namespacing that’s the problem, not namespacing full-stop. Namespacing within the object that is returned by a module is still a useful and safe strategy. For example:

[source language=”javascript” light=”true”]
define([],
function() {
var moduleNamespace = {};
moduleNamespace.x = …;
moduleNamespace.y = …;
return moduleNamespace;
});
[/source]

And nor am I saying that you should can get rid of global namespaces entirely. Indeed, it’s likely that you’ll be using libraries that add to a global namespace – for example, Backbone plugins. If you’re using a module loader, you’ll have to wrap these libraries in shim modules (if you were using require.js 1.x, you’d use the use.js plugin; with require.js 2.x, you’d use the built-in shim config). In such cases, it’s important to make clear in your code that such modules, when loaded, will be adding to a global namespace as a side-effect.

Undoing the Mess

So what if you’ve already gotten yourself into this sort of situation, and you need to get out? Well, having spent a couple of days having to back it out of a project myself, here’s three steps I’d suggest:

1. Firstly, make sure that each of your modules returns an object that is at the root of everything created by the module. Do NOT return the namespace that the module adds to. For example, instead of doing this:

[source language=”javascript” light=”true”]
// a.js
define([‘namespace’],
function(namespace) {
namespace.a = "I’m A!";
return namespace;
});
[/source]

you should do this:

[source language=”javascript” light=”true”]
// a.js
define([‘namespace’],
function(namespace) {
namespace.a = "I’m A!";
return namespace.a;
});
[/source]

If your module adds several objects to the namespace, then consolidate them underneath a single, root object. So the following:

[source language=”javascript” light=”true”]
// x.js
define([‘namespace’],
function(namespace) {
namespace.y = "I’m Y!";
namespace.z = "I’m Z!";
return namespace.y;
});
[/source]

would become:

[source language=”javascript” light=”true”]
// x.js
define([‘namespace’],
function(namespace) {
namespace.x = {y: "I’m Y!", z: "I’m Z!"};
return namespace.x;
});
[/source]

If this seems a bit ugly, hang in there, because it’ll get cleaned-up in a second.

2. Next, convert your modules to always use the value returned by a dependency, rather than a namespaced value. So this:

[source language=”javascript” light=”true”]
// b.js
define([‘namespace’, ‘a’],
function(namespace) {
console.log(namespace.a);
return …;
});
[/source]

should become:

[source language=”javascript” light=”true”]
// b.js
define([‘a’],
function(a) {
console.log(a);
return …;
});
[/source]

3. Finally, stop using the shared namespace entirely. So this:

[source language=”javascript” light=”true”]
// a.js
define([‘namespace’],
function(namespace) {
namespace.a = "I’m A!";
return namespace.a;
});
[/source]

becomes:

[source language=”javascript” light=”true”]
// a.js
define([],
function() {
var a = "I’m A!";
return a;
});
[/source]

or in the more complex case:

[source language=”javascript” light=”true”]
// x.js
define([‘namespace’],
function(namespace) {
namespace.x = {y: "I’m Y!", z: "I’m Z!"};
return namespace.x;
});
[/source]

becomes:

[source language=”javascript” light=”true”]
// x.js
define([],
function() {
var x = {y: "I’m Y!", z: "I’m Z!"};
return x;
});
[/source]

This brings you back to a land of self-contained modules and sanity. It’s not rocket-science, but it’s much easier to track what’s going on.

At the end of the day, I guess all of this is just another variation on an old theme: how global variables are bad. In short, whilst shared namespaces may be an acceptable lowest-common denominator for modularising JavaScript, doing it in conjunction with a module loader puts you at risk of difficult-to-track problems. You have been warned.

Written by Ben Teese

I'm a Senior Consultant at Shine Solutions.

One comment

Leave a Reply

%d bloggers like this: