Wednesday, March 14, 2007

Javascript Module Loader

I had an epiphany when writing a module loader. The trouble is that I didn't want modules to have access or ability to mess with the module loader's local variables. Since I was just calling eval with the text of the module, it had all of this access for reading and writing. Since I don't trust even myself, this was not acceptable. The problem was finding a way to sweep all the names under the carpet.

Here's the trivial solution:

(function () {
	/* module loader */
	this.require = function (url) {
		var require = function (url) {
			/* this require is for the module */
		};
		var text = http.requestText(url);
		eval(text);
	}
})();

Here's a less naive solution:

var evalModule = function (text, require) {
	eval(text);
};
(function () {
	this.require = function (url) {
		var text = http.requestText(url);
		var require = function (url) {};
		evalModule(text, require);
	};
})()

That solution still leaves evalModule as a variable that the module could potentially supplant. I needed to isolate evalModule. My solution uses a member variable of a jail object which eventually gets populated with the evalModule function without giving the closure access to either jail or itself..

(function () {
	var jail = {};
	this.require = function (url) {
		var text = http.requestText(url);
		var require = function (url) {};
		evalModule(text, require);
	};
	return jail;
})().evalModule = function (text, require) {
	eval(text);
}

Of course, it's not enough to isolate the module in a closure. I also give it a Module object as its context object.

(function () {
	var jail = {};
	this.require = function (url) {
		var text = http.requestText(url);
		var require = function (url) {};
		var module = new Module(url);
		evalModule.call(module, text, require);
	};
	return jail;
})().evalModule = function (text, require) {
	eval(text);
}

So, modules look like this:

var module = this;
assert(this != window);
with (require('module.js')) {
	/* go do stuff without fear of name collisions */
}

1 comment:

Kris Kowal said...

If you're looking for JavaScript modules, I used this technique in the Chiron module loader, http://modulesjs.com. You can read the source at https://cixar.com/tracs/javascript/browser/trunk/src/modules.js