CommonJS Modules implementation

| 11 Comments

I've implemented the CommonJS Modules 1.0 specification with the code in this file. It appears to pass the compliance tests when run in Firefox and Chrome on Linux, and also when run standalone in Tracemonkey, Rhino or V8.

Note that this implementation does not use the namespace probing technique I described in my previous post.

Update: One of the things that really surprised me when testing my implementation was to discover that the CommonJS spec requires (this is not explicit in the specification text, but it is explicit in the conformance tests) the require() function to return the actual exports object of a module, and not make a defensive copy of it.

Suppose a program includes module A which includes modules B and C. Module C can require B and then add, replace, or remove methods from B's API. Later, when the program includes Module B directly, it will get the modified version of B. In order to correctly use this modified module B, the programmer will have to read the documentation for module C!

Update 2: There are great comments to this post, including a link to Luke Smith's blog post that argues that the synchronous nature of CommonJS modules is not a good fit for client-side scripting. In response I wanted to make clear that I posted this code because I thought it was interesting, not because I think that client-side programmers should go out and start using it right away.

11 Comments

Thanks for that.

I'm concerned that the specs don't allow for asynchronous upload. I understand that handling dependencies is a lot more complicated in that case, but it's doable. YUI did it. And there are obvious, immediate benefits.

Tobie,

I've wondered about how to load modules asynchronously, but haven't put a lot of thought into it yet. Synchronous loading seems like the status quo--pretty much the same thing as having a bunch of tags in your HTML file, isn't it? So if the argument for asynchronous loading is that it allows modules to be downloaded in parallel, then there is an even stronger argument for joining all your modules into a single file and minifying them so they load in a single quick (synchronous) batch. I'm not sure I'm on solid ground here, but it seems to me that the argument for async loading for most applications is really an argument against using lots of separate modules or for using something like the Google Closure compiler.

But my point above only applies for programs (and their modules) that put all their requires() calls up at the top before running any other code. If you're writing programs that conditionally load modules on an as-needed basis, then asynchronous loading is presumably much more important.

Thanks for fixing the typo in the XHR open(..) comment. The first time I looked at the code it said "note the *asynchronous* get". :)

I wrote a huge comment, but it just looked unwieldy and of inappropriate size, so I moved it to this blog post: http://lucassmith.name/2009/11/commonjs-require-api-is-a-poor-fit-for-client-side-js.html

The main point is that the synchronous require(..) pattern is a poor fit for client side scripting in light of the performance demands of today's sites and RIAs. Async is the way to go, but the CommonJS require method doesn't play well with that.

re your update: in that case i wouldn't suggest using module C as monkey patching is most ofen a bad idea :) but explictly forbidding it in the spec seems harsh, as it might be usefull in some cases.

@oberhamsi the spec forbids monkey patching to restrict what is considered interoperable, and thus eligible for the CommonJS standard library. This is to ensure that those modules will work in systems like Caja and potentially strict modules in future ES standards, where there's likely to actually be lexical scoping and a sharable set of read only primordial objects. You'll find that many implementations do not enforce the rule, so monkey patching can occur in particular installations on an application-by-application basis.

@David Ihab and I pushed for a defensive copy when we were pitching the specification to the group. Due to overwhelming group sentiment that it would complicate cases of cyclic dependency and reduce "fixability", we retreated to the "sandbox" as the finest grain of capability isolation for security purposes. This means that all modules in a sandbox share the same capabilities and are free to fiddle with each other's exports. However, it is trivial to construct sub-sandboxes in secure environments, since loaders can be shared, and new modules are a function call away from reinstantiation.

I wrote a rather thorough XHR based loader for browsers long before CommonJS got ratified. It unfortunately suffers the same run-time dependency resolution traits of your implementation and I've since abandoned it.

http://github.com/kriskowal/chiron/blob/master/lib/chiron/modules.js

Now, in Narwhal, we're supporting script injection techniques with server-side support or a compilation step (which is common anyway). This enables us to get optimal packing with the browser's connections without suffering the chattiness of require-on-demand synchronous XHR.

http://github.com/280north/narwhal/blob/master/lib/narwhal/server.js
http://github.com/280north/narwhal/blob/master/lib/narwhal/client.js
http://github.com/280north/narwhal/blob/master/lib/narwhal/inline.js
http://github.com/280north/narwhal/blob/master/lib/narwhal/server-test.js

It's my understanding the SportCore and thus Bespin are pursing this route.

Luke,

Thanks for the link to your response. I'll update my post to note explicitly that this is interesting code but not intended for production use.

Kris,

Thanks for this detailed comment!

Are you saying that module loaders must allow one module to patch another, but that modules must not do so?

Does a sandbox API typically look something like this: var require = (new Sandbox()).loader()?

David,

Thanks. After stepping down from my soap box, I suspected it was mainly done as a POC or academic exercise (but by then it was 2am, so I didn't follow up). In this regard, it's good that it was done, if for no other reason than to set this conversation in motion :)

I was mainly responding to your response to Tobie about the use of async loading. I agree that high modularity requires bundling to avoid the http and script blocking effects, but your point about joining the modules and minifying them for a single synch request is *almost* right. Async should be the preferred method, even with joined modules.

David,

Here's Narwhal's utility function for instantiating a sandbox:

http://github.com/280north/narwhal/blob/master/lib/sandbox.js#L389-411

The idea is that you can instantiate multiple sandboxes with a single loader, and you can instantiate multiple secure sandboxes from a single secure loader.

Here's the spec proposal for loaders:

http://wiki.commonjs.org/wiki/Modules/Loaders

In your post above, you state that your code works
"also when run standalone in Tracemonkey, Rhino or V8" -- and the comments of the code suggestion that there's a "read" function somehow related to V8.

I'd really like to use your code, and I have a working version of V8 -- but I have no idea about the "read" function you mention. Is it somethign built into / associated with V8? Or is it something that you're expected users to obtain on their own? Can you point me to where I can learn about this?

Also: do you know if there are any, more complete "standalone" commonjs implementations like yours, e.g. impementations that are not attached to one or another commonjs platforms that are meant to do other things (like node.js).

Thanks!

Dan,

I'm using the V8 shell program, not embedding the interpreter into a program of my own. In version 2.0.1, the read() function works just fine:


% echo "hello world" > /tmp/message
% /usr/local/js/v8/shell
V8 version 2.0.1
> read('/tmp/message')
hello world
>


I haven't been following commonjs recently and don't know about other implementations. And I caution that my implementation was a fun hack, and was intended as a toy, not a piece of production code.

Leave a comment

Books

Comprehensive coverage of Ruby 1.8 and 1.9

"The New Most Important Ruby Book"
Peter Cooper,
rubyinside.com

Completely updated for Ajax and Web 2.0

"A must-have reference"
Brendan Eich,
creator of JavaScript

The classic Java quick-reference

Advertising

Pages

Hosted By

Powered by Movable Type 4.21-en