Wednesday, March 2, 2011

CommonJS-ish Modules for the browser

One of the nifty underpinnings of the CommonJS platform is the Modules specification - basically providing a standardized namespacing mechanism with on-demand initialization, dependency management, etc.

There seem to be a handful of implementations for the browser now, though I didn't see any at the time when I wrote my version.

Another aspect is that I opted to do something a bit lighter weight than the CommonJS spec inasmuch the Modules spec seemed to be a bit heavyweight in some aspects (I'm not terribly convinced by the support for relative paths in module references), as well as other aspects not being appropriate for a browser environment.

Specifically: implementing the Modules spec appears to require also taking responsibility for module loading which just relinquishes a bit too much control. With Modules also driving a "one module, one file" policy this basically guarantees poor performance in the browser.

i.e. a single synchronous 50k js file that contains 10 modules will be loaded more quickly than 10 5k files loaded asynchronously. Or at least that's the theory.

So instead I created a substantially simpler mechanism that keeps the on-demand initialization, registry and automatic dependency management but presumes that the actual modules have been loaded by some other mechanism.

var _registeredModuleInits = {}
var _registeredModuleObjects = {}
var appBridge;

function _RegisterModule(moduleName, moduleInit) {
    _registeredModuleInits[moduleName] = moduleInit
}

function _RequireModule(moduleName) {
    if (_registeredModuleInits[moduleName] == null && appBridge != null) {
        var script = appBridge.load(moduleName + ".js");
        if (script != null)
            eval(script);
    }
    if (_registeredModuleInits[moduleName] == null) {
        return null;
    }
    var moduleObj = _registeredModuleObjects[moduleName];
    if (moduleObj == null) {
        moduleObj = _registeredModuleObjects[moduleName] = {}
        _registeredModuleInits[moduleName](moduleObj, _RequireModule);
    }
    return moduleObj;
}

function require(module) {
    return _RequireModule(module);
}

An example module would be something like:
_RegisterModule("loader", function(exports, require) {
    var requirementCount = 1;
    var loadHandlers = [];

    exports.addRequirement = function() {
        requirementCount++;
    }

    exports.resolvedRequirement = function() {
        requirementCount--;
        if (!requirementCount) {
            for ( var idx = 0; idx < loadHandlers.length; idx++)
                loadHandlers[idx]();
        }
    }

    exports.addLoadEvent = function(handler) {
        loadHandlers.push(handler);
    }
});

with the singleton being accessed with "require('loader')".

One other aspect of my implementation is the apparently unused appBridge variable - this has been handy when using embedded JS in a non-browser environment. Essentially this allows the module system to take over loading responsibility (but presumed to be done synchronously as embedded JS has different performance characteristics).

I'll probably revisit this soon with a bit of namespace cleanup etc, but otherwise I find it a valuable building block for managing my JavaScript code.

Regards

Andrew

No comments:

Post a Comment