JavaScript – Modules

An important reason to organize code into classes is to make
that code more modular and suitable for reuse in
a variety of situations. Classes are not the only kind of modular
code, however. Typically, a module is a single file of JavaScript
code. A module file might contain a class definition, a set of related
classes, a library of utility functions, or just a script of code to
execute. Any chunk of JavaScript code can be a module, as long as it
is written in a modular way. JavaScript does not define any language
constructs for working with modules (it does reserve the keywords
imports and exports, however, so future versions of the
language might), which means that writing modular JavaScript is
largely a matter of following certain coding conventions.

Many JavaScript libraries and client-side programming frameworks
include some kind of module system. Both the Dojo toolkit and Google’s
Closure library, for example, define provide() and require() functions for declaring and
loading modules. And the CommonJS server-side JavaScript
standardization effort (see http://commonjs.org)
has created a modules specification that also uses a require() function. Module systems like this
often handle module loading and dependency management for you and are
beyond the scope of this discussion. If you use one of these
frameworks, then you should use and define modules following the
conventions appropriate to that framework. In this section we’ll
discuss very simple module conventions.

The goal of modules is to allow large programs to be assembled
using code from disparate sources, and for all of that code to run
correctly even in the presence of code that the module authors did not
anticipate. In order for this to work, the various modules must avoid
altering the global execution environment, so that subsequent modules
are allowed to run in the pristine (or near pristine) environment that
it expects. As a practical matter, this means that modules should
minimize the number of global symbols they define—ideally, no module
should define more than one. The subsections that follow describe
simple ways to accomplish this. You’ll see that writing modular code
in JavaScript is not at all tricky: we’ve seen examples of the
techniques described here throughout this book.

Objects As Namespaces

One way for a module to avoid the creation of global variables
is to use an object as its namespace. Instead of defining global
functions and variables, it stores the functions and values as
properties of an object (which may be referenced by a global
variable). Consider the Set class of Example 9-6. It
defines a single global constructor function Set. It defines various instance methods
for the class, but it stores them as properties of Set.prototype so they are not globals.
That example also defines a _v2s() utility function, but instead of
making it a global function, it stores it as a property of Set.

Next, consider Example 9-16. That example
defined a number of abstract and concrete set classes. Each class
had only a single global symbol, but the whole module (the single
file of code) defined quite a few globals. From the standpoint of a
clean global namespace, it would be better if this module of set
classes defined a single global:

var sets = {};

This sets object is the
namespace for the module, and we define each of the set classes as a
property of this object:

sets.SingletonSet = sets.AbstractEnumerableSet.extend(...);

When we want to use a class defined like this, we simply
include the namespace when we refer to the constructor:

var s = new sets.SingletonSet(1);

The author of a module cannot know what other modules their
module will be used with and must guard against name collisions by
using namespaces like this. The programmer who uses the module,
however, knows what modules are in use and what names are defined.
This programmer doesn’t have to keep using the namespaces rigidly,
and can import frequently used values into the
global namespace. A programmer who was going to make frequent use of
the Set class from the sets
namespace might import the class like this:

var Set = sets.Set;     // Import Set to the global namespace
var s = new Set(1,2,3); // Now we can use it without the sets prefix.

Sometimes module authors use more deeply nested namespaces. If
the sets module was part of a larger group of collections modules,
it might use collections.sets as
a namespace, and the module would begin with code like this:

var collections;      // Declare (or re-declare) the single global variable
if (!collections)     // If it doesn't already exist
    collections = {}; // Create a toplevel namespace object
collections.sets = {} // And create the sets namespace within that.
// Now start defining our set classes inside collections.sets
collections.sets.AbstractSet = function() { ... }

Sometimes the top-level namespace is used to identify the
person or organization that created the modules and prevent name
collisions between namespace names. The Google Closure library, for
example, defines its Set class in the namespace goog.structs.
Individuals can reverse the components of an Internet domain name to
create a globally unique namespace prefix that is unlikely to be in
use by any other module authors. Since my website is at
davidflanagan.com, I could publish my sets
module in the namespace com.davidflanagan.collections.sets.

With namespaces this long, importing values becomes important
for any user of your module. Rather than importing individual
classes, however, a programmer might import the entire module to the
global namespace:

var sets = com.davidflanagan.collections.sets;

By convention, the filename of a module should match its
namespace. The sets module should be stored in a file named
sets.js. If that module uses the namespace
collections.sets, then this
file should be stored in a directory named
collections/ (this directory might also include
a file named maps.js). And a module that used
the namespace com.davidflanagan.collections.sets would be in
com/davidflanagan/collections/sets.js.

Function Scope As a Private Namespace

Modules have a public API that they export: these are the
functions, classes, properties, and methods that are intended to be
used by other programmers. Often, however, module implementations
require additional functions or methods that are not intended for
use outside of the module. The Set._v2s() function of Example 9-6 is an example—we don’t want users of the Set
class to ever call that function, so it would be better if it was
inaccessible.

We can do that by defining our module (the Set class in this
case) inside a function. As described in Functions As Namespaces, variables and functions defined
within another function are local to that function and not visible
outside of it. In effect, we can use the scope of a function
(sometimes called a “module function”) as a private namespace for
our module. Example 9-24
shows what this might look like for our Set class.

Example 9-24. A Set class in a module function

// Declare a global variable Set and assign it the return value of this function
// The open parenthesis and the function name below hint that the function 
// will be invoked immediately after being defined, and that it is the function
// return value, not the function itself, that is being assigned.
// Note that this is a function expression, not a statement, so the name
// "invocation" does not create a global variable.
var Set = (function invocation() {
   
    function Set() {  // This constructor function is a local variable.
        this.values = {};     // The properties of this object hold the set
        this.n = 0;           // How many values are in the set
        this.add.apply(this, arguments);  // All arguments are values to add
    }

    // Now define instance methods on Set.prototype.
    // For brevity, code has been omitted here
    Set.prototype.contains = function(value) {
        // Note that we call v2s(), not the heavily prefixed Set._v2s()
        return this.values.hasOwnProperty(v2s(value));
    };
    Set.prototype.size = function() { return this.n; };
    Set.prototype.add = function() { /* ... */ };
    Set.prototype.remove = function() { /* ... */ };
    Set.prototype.foreach = function(f, context) { /* ... */ };

    // These are helper functions and variables used by the methods above
    // They're not part of the public API of the module, but they're hidden
    // within this function scope so we don't have to define them as a 
    // property of Set or prefix them with underscores.
    function v2s(val) { /* ... */ }
    function objectId(o) { /* ... */ }
    var nextId = 1;

    // The public API for this module is the Set() constructor function.
    // We need to export that function from this private namespace so that
    // it can be used on the outside.  In this case, we export the constructor
    // by returning it.  It becomes the value of the assignment expression
    // on the first line above.
    return Set;
}()); // Invoke the function immediately after defining it.

Note that this function definition followed by immediate
invocation is idiomatic in JavaScript. Code that is to run in a
private namespace is prefixed by “(function() {” and followed by
“}());”. The open parenthesis at the start ensures that this is a
function expression, not a function definition statement, so any
function name that clarifies your code can be added to the prefix.
In Example 9-24 we used the name “invocation” to
emphasize that the function would be invoked immediately after being
defined. The name “namespace” could also be used to emphasize that
the function was serving as a namespace.

Once module code has been sealed up inside a function, it
needs some way to export its public API so that it can be used from
outside the module function. In Example 9-24, the
module function returned the constructor, which we then assigned to
a global variable. The fact that the value is returned makes it very
clear that it is being exported outside of the function scope.
Modules that have more than one item in their API can return a
namespace object. For our sets module, we might write code that
looks something like this:

// Create a single global variable to hold all collection-related modules
var collections; 
if (!collections) collections = {};

// Now define the sets module
collections.sets = (function namespace() {
    // Define the various set classes here, using local variables and functions
    //    ... Lots of code omitted...

    // Now export our API by returning a namespace object
    return {
        // Exported property name : local variable name
        AbstractSet: AbstractSet,
        NotSet: NotSet,
        AbstractEnumerableSet: AbstractEnumerableSet,
        SingletonSet: SingletonSet,
        AbstractWritableSet: AbstractWritableSet,
        ArraySet: ArraySet
    };
}());

A similar technique is to treat the module function as a
constructor, invoke it with new,
and export values by assigning them to this:

var collections; 
if (!collections) collections = {};
collections.sets = (new function namespace() {
    //    ... Lots of code omitted...

    // Now export our API to the this object
    this.AbstractSet = AbstractSet;
    this.NotSet = NotSet;      // And so on....

    // Note no return value.
}());

As an alternative, if a global namespace object has already
been defined, the module function can simply set properties of that
object directly, and not bother returning anything at all:

var collections; 
if (!collections) collections = {};
collections.sets = {};
(function namespace() {
    //    ... Lots of code omitted...
    
    // Now export our public API to the namespace object created above
    collections.sets.AbstractSet = AbstractSet;
    collections.sets.NotSet = NotSet;     // And so on...

    // No return statement is needed since exports were done above.
}());

Frameworks that define module loading systems may have other
methods of exporting a module’s API. There may be a provides() function for modules to
register their API, or an exports
object into which modules must store their API. Until JavaScript has
module management features of its own, you should choose the module
creation and exporting system that works best with whatever
framework or toolkit you use.

Comments are closed.