Modules in JavaScript
Modules: lots of languages have them; JavaScript doesn't, but we can create a similar effect.
What is a module?
In functional programming, functions are first-class objects, but the classes and interfaces of OOP are absent.
As a way to keep code organized, modules are a good fit in these languages, somewhat analogous to the one-class-per-file convention in Java.
If you write JavaScript in a functional style, you might find modules a good fit.
Some people use the term "module" to mean JavaScript imported from another file and loaded at runtime, perhaps by XHR.
I think pulling in required dependencies from various files is a task for preprocessing, not the end user's browser.
A great way to drive that preprocessing step is by tracing module dependencies, as declared by import statements, but that's a topic for another post.
The definition of "modules" I'm using here has to do with namespacing and code organization, not dependencies or loading code at runtime.
A module has an internal namespace, within which the programmer may use any names whatsoever, and an external interface which defines the identifiers that users of the module can access.
Inside a module, helper functions and variables can be created and named freely without any worry of polluting the global scope.
The module programmer can use names like "x", or "frobnicator$$WhatzitThingamabobFactoryFactory_foo", and nobody but the maintainer needs to know what that means or deal with it.
The exported interface is where clean, meaningful names matter, and exported names need to avoid clashes with other names that may already be in scope in the user's code.
Creating a namespace
In JavaScript, every function definition creates a new scope.
A variable declared within this scope using var
(and not returned as part of the function's return value) will not be accessible outside the function unless assigned as a property of an object which is.
We can use this fact about functions to create a namespace in which we can name things freely and then export only what we choose.
This is a well-known and well-documented approach used in various ways in many JavaScript projects and libraries.
To provide a handle to the module, we put one name per module into the global scope.
We can use all caps for this name to avoid any conflicts with ordinary variables, which rarely use all caps.
This is also somewhat of an established convention.
Importing
In languages with modules, an import statement typically names a module, and may also name a subset of that module's public interface if the entire interface is not needed or desired.
An import statement may also give the module a local name to disambiguate that module's exports from those of other modules.
This system gives the programmer who uses the module final control over the named objects that exist in the global namespace of their program.
We can create as much or as little of this flexibility as desired in JavaScript, with a few limitations.
Implementation
First we wrap the entire module in an anonymous function to create a local scope.
The return value of this function will become the module object, so we assign it to a global variable which will be the name of our module.
FOO=function(){
...
...
}();
The function can also be wrapped in parentheses if you prefer:
FOO=(function(){
...
...
})();
The parentheses are not necessary but some people consider them an aid to the reader, as you do not need to read to the end to see that the value of FOO is not the function itself but its return value—at least if you are familiar with the convention.
All that remains is to return some value which can be used to import the module.
FOO=function(){
...
var x=42;
function frobnicator$$WhatzitThingamabobFactoryFactory_foo(a,b){return a+b};
...
var g=this; // reference to the global object
return {import: // return an object with a single property, "import"
function(prefix, object){ // which takes optional prefix and object arguments
object=object||g; // if object not provided, use the global object
[['exportedName',Math.PI] // now a list of (export name, expression) pairs
,['foo',y]
,['bar',frobnicator$$WhatzitThingamabobFactoryFactory_foo]
...
].forEach(function(export){ // see note about forEach below
// for each export, we create a property on the object
// the property name is prefixed by the prefix if one was provided
// the object is either the global object, or the second argument to import()
object[(prefix||'')+export[0]]=export[1]})}}
}(); // close and call the anonymous function
forEach is an Array feature implemented by Mozilla in JavaScript 1.6.
In browsers which do not have this feature, it can be added to the Array prototype object, as explained in the MDC documentation.
If it's not already being used elsewhere, we might prefer to just use an ordinary for loop, as I did in the real-world example mentioned below.
Import Examples
To use the module FOO, we call FOO.import() with zero to two arguments.
The first argument is a prefix string which will be added to the exported names.
The second argument is an object to which the exports will be added as properties (somewhat like the extend() function in some JavaScript libraries), otherwise they will be added to the global scope (as properties of the global object).
Here are some examples of importing and using the FOO module With the exported names in the example above ('exportedName', 'foo', and 'bar').
All of these call the same function with the same arguments.
A simple .import() call imports all the module's names into the global scope where they can be used directly:
FOO.import()
bar(exportedName, exportedName)
We can import names with a prefix:
FOO.import('foo_')
foo_bar(foo_exportedName, foo_exportedName)
Or add properties to an existing object:
var someObject = ...;
...
FOO.import('',someObject)
someObject.bar(someObject.exportedName,someObject.exportedName)
Using a prefix and an object:
var i={} //imports
FOO.import('foo',i)
BAR.import('bar',i) // import several modules into the same object
i.foo_bar(i.foo_exportedName, i.foo_exportedName)
There are infinitely many variations along these lines.
Other interesting ideas include passing this
or a prototype object as the second argument.
Real-world Example
The motivation for this approach was exporting a part of a much larger project to publish in my previous post, Unicode Character Classes in ECMAScript Regular Expressions.
See the CSET.import() call in the demos on that page, and the CSET module source code for an example of the approach in use.
Limitations
Each module adds one global object, so with modules FOO, BAR, BAZ, etc. the global scope can get cluttered and there can be name collisions between modules.
You can't import a module into the local scope, i.e. the variable object.
It would be convenient to import a module's exports as local variables, but ECMAScript doesn't provide a way to access the local variable object, so it cannot be extended by calling a function.
The import function is written directly in every module, resulting in code duplication.
This also gives some flexibility in doing funny things in the import function, but that's also probably more of a downside than an advantage in practice.
A solution to most of these issues would be to implement the module system one level higher, by using some kind of metaprogramming, macro, or code generation approach.