2011-03-27

A first look at the upcoming JavaScript modules

Update 2012-10-22: Note that this feature is still actively being worked on and in flux.

Judging by a tweet by David Herman, he is excited about something (he is shouting!) and so should we be:

MODULES FOR ECMASCRIPT, Y'ALL. MODULES FOR ECMASCRIPT. THAT IS ALL. [@littlecalculist]
This means: ECMAScript Harmony (or ECMAScript.next [1]), as it is currently being planned, will have modules. This post gives you a first look, summarizing the material at [2].

Goals of modules:
  • Static scoping
  • Orthogonality from existing features
  • Smooth refactoring from global code to modular code
  • Fast compilation
  • Simplicity and usability
  • Standardized protocol for sharing libraries
  • Compatibility with browser and non-browser environments
  • Easy external loading
Terminology: The word “module” can mean three things. The differences are subtle, but you don’t need to worry about them too much, because it is usually intuitively clear how things work and there is almost no overlap between the meanings:
  • Module: the module as source, either defined inline or loaded externally.
  • Module instance: The evaluated module in use, linked to other modules. It has internal state and external (exported) bindings.
  • Module instance object: The module instance reified as an object. Naturally, only exported bindings are accessible.
Modules are very similar to objects (as they should be), but you have to explicitly export things that should be externally visible.
    module Math {
        export function sum(x, y) {
            return x + y;
        }
        export var pi = 3.141593;
    }
In the scope where the module is defined, you can access its contents via the module name.
    alert("2π = " + Math.sum(Math.pi, Math.pi));
If you import, you can drop the module name.
    import Math.{sum, pi}; 
    alert("2π = " + sum(pi, pi));
Use * to import everything:
    import Math.*; 
    alert("2π = " + sum(pi, pi));
You can rename the things you import:
    // avoid name clash between two modules:
    import Geometry.{draw: drawShape};
    import Cowboy.{draw: drawGun};
You can also nest modules and access them via a path of names.
    module Widgets {
        module Button { ... }
        module Alert { ... }
        module TextArea { ... }
        ...
    }
 
    import Widgets.Alert.{messageBox, confirmDialog};
    ...
Instead of providing a body for a module, you can also load a module definition from the web or from the file system. The source code of a module file does not have the module {...} brace around it.
    module JSON = require('http://json.org/modules/json2.js'); // web
    module File = require('io/File'); // file system
    import require("bar.js").y; // file system
Reflection: modules can be used and examined as if they were objects: You can pass the object to functions as an argument (e.g. to provide them with an API to do something). You can list the property keys to find out what is being exported. Etc.
    module M = Math; // static module reference
    var N = Math; // dynamic reference to module instance object
    var twoPi = M.pi + N.pi;
    console.log(Object.keys(M));
More features:
  • All globals in a module are only module-global. This allows for shared state and nicely encapsulates things.
  • Cyclic dependencies are possible.
  • A module can be loaded dynamically, the resulting module instance object is handed to a callback.
Related reading:
  1. David Herman on ECMAScript.next
  2. harmony:modules [ES Wiki]
  3. Modules and namespaces in JavaScript [patterns etc. for current-day ECMAScript 5]

No comments: