Modules in Javascript

JavaScript does not have modules builtin, but since it is such an open language, it is possible for the developer to create a custom module system. Looking around on the web, it is soon obvious that there are several ways to do this. Here I will present that path I have taken. I have chosen to separate the different levels in the module hierarchy with periods. Some people think this is a bad idea since for every call to a function in such a module the JavaScript engine has to look up one property per level. My solution to this is to use shortcuts, as you will see.

First off, I use a modified version of the namespace code presented in JavaScript: The Definitive Guide, 5th Edition by David Flanagan (see note about copyright further down). This is my version, with the comments removed for brevity (get the full version here):

var Module;
if (Module && (typeof Module != "object" || Module.NAME))
  throw new Error("Namespace 'Module' already exists");

Module = {};
Module.NAME = "Module";
Module.globalNamespace = this;
Module.modules = { "Module": Module };

Module.createNamespace = function(name) {
  if (!name) throw new Error("Module.createNamespace( ): name required");
  if (name.charAt(0) == '.' ||
    name.charAt(name.length-1) == '.' ||
    name.indexOf("..") != -1)
    throw new Error("Module.createNamespace( ): illegal name: " + name);

  var parts = name.split('.');
  var container = Module.globalNamespace;
  for(var i = 0; i < parts.length; i++) {
    var part = parts[i];
    if (!container[part]) container[part] = {};
    else if (typeof container[part] != "object") {
      var n = parts.slice(0,i).join('.');
      throw new Error(n + " already exists and is not an object");
    }
    container = container[part];
  }
  var namespace = container;
  if (namespace.NAME) throw new Error("Module "+name+" is already defined");
  namespace.NAME = name;
  namespace.onInit = function(initHook) {
    namespace.init = function() {
      if (namespace.initCalled == undefined)
        namespace.initCalled = true;
      else
        return;
      initHook();
    };
  };
  Module.modules[name] = namespace;
  return namespace;
};

EDIT:Looking through my code I've noticed at least one thing that is sub-optimal. Obviously the onInit method does not need to have access to the createNamespace scope. It could be defined outside. Doing so is, however, left as an exercise for the reader.

I use the method Module.createNamespace to create the namespaces for my modules. Here is an example, where I have put Diego Perini's crossbrowser cursor code in a module:

// Original author: Diego Perini
//
// Modified by Peter Jaric:
// * inserted semi colons at end of lines
// * removed demo code
// * introduced return variables
// * wrapped in module code

(function() {
  var module = Module.createNamespace("org.jaric.other.cursor");

  // Public function declarations
  module.getSelectionStart = getSelectionStart;
  module.getSelectionEnd = getSelectionEnd;

  // Implementation
  function getSelectionStart(o) {
    var index;
    if (o.createTextRange) {
      var r = document.selection.createRange().duplicate();
      r.moveEnd('character', o.value.length);
      if (r.text == '')
        index = o.value.length;
      else
        index = o.value.lastIndexOf(r.text);
    } else
      index = o.selectionStart;
    return index;
  }

  function getSelectionEnd(o) {
    var index;
    if (o.createTextRange) {
      var r = document.selection.createRange().duplicate();
      r.moveStart('character', -o.value.length);
      index = r.text.length;
    } else
      index = o.selectionEnd;
    return index;
  }
})();

Let me comment on a few things. I have put all code inside an anonymous function like this:

(function() {
  // Code here
})();

This is a common way to avoid polluting the "global namespace". All code inside the function body will be local to the function, except the code we explicitly export. At the end there are a couple of parentheses, making sure the function is called.

First we call Module.createNamespace. That will create the namespace hierarchy if it doesn't already exist. The top components are created in the global namespace. If that is not desired, it is easy to change in the Module code. The resulting namespace will bu returned and assigned to our "module" variable.

Then we fill the module with those functions we'd like to make publicly available. Since function definitions are evaluated first we can refer to them early in the code. This makes for a nice separation between the interface and the implementation. It's only part of the interface of course, since we do not see the argument lists. Only the functions listed here will be visible to code using this module.

Finally all functions are defined.

This is an example on how to refer to a module and use it:

var start = org.jaric.other.cursor.getSelectionStart(someElement);

If a function in a module is going to be called often and the multiple lookups in the intermediate module objects should be avoided, a kind of shortcuts can be used:

var cursorModule = org.jaric.other.cursor;
var start = cursorModule.getSelectionStart(someElement);
var end = cursorModule.getSelectionEnd(someElement);

Sometimes there may be dependencies between modules and then the simple scheme outlined above might not work, because when one module refers to another it might not yet be created. To solve this, the module code can be placed in an "init handler", like this:

(function() {
  var module = Module.createNamespace("module.with.dependencies");

  module.onInit(function() {
    // Public function declarations
    module.fun = fun;

    // Implementation
    function fun(o) {
      return "hello";
    }
  });
})();

Notice that we've only added a anonymous wrapper function that is sent to the onInit function. When this code is run, the module will be created, but its implementation will not be ready until the module's init function is called:

module.with.dependencies.init();

Note about copyright:

In Flanagan's book there is this note about use of his code:

This book is here to help you get your job done. In general, you may use the code in this book in your programs and documentation. You do not need to contact us for permission unless you're reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Selling or distributing a CD-ROM of examples from O'Reilly books does require permission. Answering a question by citing this book and quoting example code does not require permission. Incorporating a significant amount of example code from this book into your product's documentation does require permission.

2 thoughts on “Modules in Javascript

Leave a Reply

Your email address will not be published. Required fields are marked *