Tuesday, February 09, 2010

RunJS is now RequireJS

As mentioned before, I considered renaming RunJS to RequireJS. I did the transition, and RequireJS is on GitHub. There is a conversion script that will convert CommonJS modules to the Transport/C proposal that works with RequireJS.

I have converted Raindrop to use a modified Dojo 1.4 that uses RequireJS instead of the normal Dojo loader, and all Raindrop modules are written in the Transport/C module format that RequireJS understands. Raindrop works, so the RequireJS code has been proven in a real project that has many modules with nested dependencies. The RequireJS code is already battle-tested.

I have opened a thread on the jQuery forum about using RequireJS for jQuery's require() needs. I can do a build with Dojo 1.4 that uses RequireJS, and any Dojo 2.0 effort is likely to use RequireJS as the module loader.

I believe RequireJS is the loader browser-based toolkits should use. At the very least, the module format and API supported by RequireJS should be used by browser-based toolkits, even if they want to build their own loader.

Next plans for RequireJS:

1) Contact the MooTools and Prototype folks to see if they want to use it. It allows loading code that does not export a module value, and has access to the global environment. They can use it to load code that augments native prototypes. RequireJS can load existing, plain JS files that do not define a module too.

2) Put up a web site with builds. While you can use RequireJS just from grabbing it from GitHub, it would be nice to have the builds of RequireJS with its different levels of functionality already built and easy to download.

2) Do a fork of Node that uses RequireJS on the server. I believe the async module format used by RequireJS is great fit for Node and its async goals.

3) See if I can do a fork of Narwhal to do the same thing.

I believe I can get RequireJS to work on the server and still support the existing CommonJS format when the server supports synchronous loading. By having native support in some server-based systems for RequireJS, it will be easier to share code with the browser.

To recap, my three main issues with using the existing CommonJS module spec, and why RequireJS exists:

1) So far the CommonJS group does not think the browser is common enough to qualify as a first class citizen in the module spec. The group is mainly concerned with environments outside the browser. As a result, the CommonJS module spec does not work well natively in the browser -- it either requires an XHR-based loader, which we have found to have problems in Dojo, or require a server-side transform process. A server-side transform process should not be required to do web development in the browser.

RequireJS uses a function wrapper around the module to avoid these problems and allow loading modules via script tags. Just save the file and hit reload in the browser.

2) There is a free variable, called "exports". It is an object. You cannot set the value of exports inside your module code, you can only add properties. This means for instance, your module cannot be a function. In Dijit, Dojo's widget system, all widgets are constructor functions. The "exports" restriction makes your APIs awkward if you want to export functions for module values. The claim is that this exports restriction helps with circular dependencies, but it only helps a little bit. To me, it is not worth slightly improving an edge case when it sacrifices a greater usefulness and simplicity in user's modules.

RequireJS and its format can handle circular dependencies just fine. In the format supported by RequireJS, you to define a module as a function. Although, you can still use exports as CommonJS uses it if you so desire.

3) The require.main property seems like a hack. It is normally used so that a module can say if (require.main === module.id) or if (require.main === module) then do some "main" work. The module format should just define an exports.main convention for indicating "main" functionality. It is less typing, and more robust, since different code entry points have a different idea of "main". For instance, an HTTP request handler likely has specific requirements on what it considers to be the "main". The top level entry point should decide what code to execute as "main", not logic inside the module.

RequireJS does not support the require.main idiom.

So I believe the path used by RequireJS is more robust overall, works better/scales better in the browser. However, I still want to provide enough support for the existing CommonJS modules in the meantime to allow more code sharing.

In the long run though, the CommonJS format as it exists today should be replaced with something better. It is troublesome that the CommonJS group is not really targeting the browser, but over time, the broader JS community will expect browser toolkits to support CommonJS specs. It does not seem right to end up with non-optimal solutions in the browser when the browser is the most common JS platform.

6 comments:

Kris Zyp said...

I'm all for RequireJS/Transport/C as module loading mechanism for the browser, but I don't see why server side JS should be need to change it's module system. The vast majority of the CommonJS modules I have written are completely meaningless on the browser, why should I have to use much more verbose async methods to define dependencies to cater to an environment that they will never be used in? That would just be silly.

James Burke said...

Kris Zyp, my main compliants with the current CommonJS module format, cannot assign export and require.main, could be fixed without forcing Transport/C everywhere. "exports" to me is an odd design choice anyway, "exports" would not be needed if there was a function callback that allowed a return value, that looks cleaner to me.

Converting modules between a Transport/C style and the existing format is also not bulletproof, errors can occur due to how the module is parsed. But I appreciate there are things that would not be shared between browser and server.

So for me at the very least export assignment and require.main should be fixed, and Transport/C should just be part if the module spec. Server devs do not need to use the Transport/C syntax, but their server loader should be able to load modules that use the Transport/C syntax.

Kevin Dangoor said...

FWIW, the goal is for CommonJS modules to be competitive with the modules that people write in Python, Ruby, Java, etc. In other words, users should not be writing lots of extra scaffolding.

CommonJS modules are not as convenient in the browser as something that can be loaded in the browser straight from a script tag, but with a bit of server side support (or a build tool) they run just dandy in the browser with no scaffolding written by the user.

And, there's a decent chance that ECMAScript Harmony will have a module system that runs in the browser, does not require any sort of Transport format and is similar to the CommonJS modules (export keyword instead of exports variable).

http://wiki.ecmascript.org/doku.php?id=strawman:simple_modules

Like Kris, a common Transport format is a great idea -- but it's a Transport format, not a module format and server environments have no need to do anything special to "transport" the module.

James Burke said...

Kevin Dangoor: As mentioned on the CommonJS list, I can see the case for keeping the existing CommonJS module format and a transport format as separate specs, I just want some server side solutions to implement both specs, and I will focus my efforts on that.

I still think being able to export a function as the module value as very useful though, and require.main is awkward/more typing.

As for ECMAScript specs, I believe there is at least one other module proposal out there, to me there is no guarantee the exports model will prevail.

As for the tradeoff on verbosity, I believe having great browser support will make up the difference, at least compared to the extra costs of either setting up a server side transform system or doing a build for each change to a module before reloading in the browser. I am speaking from a front-end developer perspective though. I can appreciate a more server-focused developer might see the tradeoff differently.

So I think the right way to find out about what resonates best, both in an ECMAScript module format and with a transport format, is with more data via implementation.

Implementing exports in the module format will help test the ECMAScript proposal, and being sure that server-side systems implement both module and transport specs will test if those specs are useful in practice.

Kris Zyp said...

FWIW, I certainly agree that it would be great to be able to set the module export to be a function, I've been arguing for that for a while. I guess we just haven't had consensus on exactly how that would work.

I am not sure what the issue is with require.main, I thought was just used to trigger a function when you start a module from the command line.

James Burke said...

Kris Zyp: I do not fancy require.main in the module spec for a couple reasons:

1) require.main has to do with code execution bootstrapping but does not have anything to do with defining modules, and does not make sense in the module spec.

2) Module definitions are better if they are more declarative in nature, the if(require.main === module.id) construct seems more imperative.

I do not like that a module is deciding to execute a "main" functionality on its own. The environment should choose when to execute that functionality, it may not be part of defining the module.

I prefer just to standardize on an exports.main = function() {} if a command-line entry point wants to be defined. Other execution contexts seem to favor conventions or specs on method names on the module, so why not command-line too?

In any case, the first issue is the bigger one, it seems like something that belongs in a different spec or convention outside the module spec.