Rants, rambles, news and notes from another geek

Playing Around With JavaScript Modules

Recently I’ve been playing around a lot with JavaScript modules. The particular use case I’ve been thinking about is the creation of a large complex JavaScript library in a modular and sensible way. JavaScript doesn’t really do this very well. It does it so poorly, in fact, that a sizeable number of projects are all done in a single file. I did fine a number that used file concatenation to assemble the output scrpit, but this seems like a stone-age technique to me.

This led me to look at the two competing JavaScript module techniques: Asynchronous Module Definition (AMD) and CommonJS (CJS). AMD is the technique used in RequireJS) and CommonJS is the technique used by nodejs.

The RequireJS project has a script called r.js which will “compile” a set of AMD modules into a single file. There are other projects like Browserify which do the same thing for a collection of CommonJS modules Basically, all of these figure out the ordering from the dependencies, concatenate the files, and inject a minimalistic bootstrapper to provide the require/module/exports functions. Unfortunately, this means that they all have the downside of leaving all the ‘cruft’ of the module specification in the resulting file.

To illustrate what I mean by ‘cruft’, I will use one of the examples from the browserify project. This project has three JavaScript files that use the CommonJS module syntax, and depend on each other in a chain.

bar.js
1
2
3
module.exports = function(n) {
	return n * 3;
}
foo.js
1
2
3
4
var bar = require('./bar');
module.exports = function(n) {
	return n * bar(n);
}
main.js
1
2
var foo = require('./foo');
console.log(foo(5));

When I run this through browserify, it produces this output:

out.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){
var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);
if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")
}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){
var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports
}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o])
;return s})({1:[function(require,module,exports){
module.exports = function(n) {
	return n * 3;
}

},{}],2:[function(require,module,exports){
var bar = require('./bar');

module.exports = function(n) {
	return n * bar(n);
}

},{"./bar":1}],3:[function(require,module,exports){
var foo = require('./foo');
console.log(foo(5));

},{"./foo":2}]},{},[3])
;

When you run the resulting program in either a browser or nodejs, it does the right thing and tells you 75. But look at how much garbage there is! To deal with the require & module parts, it defines a bunch of stuff at the top, seriously bloating the file. The original program code was 9 lines of code with 169 characters). The “compiled” version was 14 lines and 733 characters. Minifying it with uglifyjs only shrinks it down to 713 characters.

In an ideal world, I would like system that can produce this from those same three source files:

ideal.js
1
2
3
4
5
6
7
8
9
(function() {
	function bar(n) {
		return n * 3;
	}
	function foo(n) {
		return n * bar(n);
	}
	console.log(foo(5));
}());

That looks better to me and is only 114 characters long (minified it is 95). Because I used the module pattern, like the compiled version, it doesn’t leak anything into the global namespace.

I’d read that JQuery used AMD modules, and knew that their resulting JavaScript files weren’t cluttered up, so I took a look at their build script. Interestingly they use the optimizer from RequireJS, but then do a bunch of post processing. Essentially they rip out all the declares and requires and such, and then wrap it in a closure. Pretty cool.

I found a number of other tools for doing single-file compiles, but none of them seemed to take the kind of aggressive approach to optimization that I think would be ideal when building a large library, so for now I’ve been using a hacked up version of the JQuery build system.

I like modular code, but I’m still not very happy about what I’ve found for JavaScript. I can get cohesive, decoupled and testable components using either the AMD or CommonJS approach, but still haven’t found a reasonable way to bring it all together when building a single library.

Next up, I’m going to look at what I can get from TypeScript modules. Maybe it will give me more of what I want.

Comments