Monday, June 16th, 2008
As the module-loading space is quite crowded at the moment (e.g., Google, Dojo, Yahoo, JSModule, etc.), we asked Kris to explain what makes modules.js different. He had some interesting things to say.
Existing Module Loaders Fall Short of Traditional Language Import Semantics
Kris started out his notes to us with an interesting point:
He then points out some ways in which modules.js meets this objective where others fail (along with an admission that some of his information about these other loaders might be a bit out-of-date):
Compared to JSModule
JSModule is the most similar [to modules.js]. It also uses XHR, eval, singleton
module cache, and scope injection. It’s also lighter. In general,
modules.js is what JSModule could grow up to be. JSModule is a bit
messy; it pollutes the scope chain that it implicitly passes to its
modules with its own internal variables because it uses eval in local
scope instead of global scope (I have a fun way around this problem).
JSModule also adds itself to global scope; modules.js cleverly avoids
this. modules.js includes modules using their URL relative to the
module loader URL or the module URL if it’s prefixed with a dot; this
permits easy migration and refactoring. JSModule also supports an
include path; when I explored that feature for modules.js, I
determined that searching for the script over HTTP performed very
poorly even though it provides a better approximation of how other
languages work server side.
Compared to the Dojo and Google Loaders
Dojo loader (last I checked) and Google’s loader both perform strictly
asynchronous loads and piggyback one layer of dependency management on
the onLoad handler. The key difference here is that module’s loaded
with modules.js can have recursive dependencies. In Dojo and Goog,
there is an important distinction between loading scripts in global
scope and in module scope. In global scope, you simply initiate an
asynchronous load of all the modules you want and attach an onLoad
observer that will be signaled when the page has loaded AND all of the
requested modules have loaded. However, onLoad may fire before all of
the modules required by your modules are loaded. To get around this,
in module scope, you have to write complicated dependency management
code to ensure that the scripts load in order: [your dependencies,
your continuation, the onload continuation]. This can be very
complicated and detracts from time spent actually writing features.
Google loader uses document.write to do script insertion which has two
advantages over modules.js: it prevents script flicker on pages with
long DOMs, and it can load cross domain scripts, which is handy if
you’re loading standard libraries from their cache.
Compared to YUI Loader
YUI Loader uses a static dependency table to ensure that YUI modules
are loaded in the correct order. This means that third party modules
have to monkey patch this table if they have inter-module
dependencies. It’s similar to modules.js in that it uses XHR.
A Note about Global Scope
YUI, Goog, and Dojo all evaluate modules in global scope. modules.js
protects global scope with an enclosure and provides developers with
the ability to share functions without adding them to the transitive
global scope. Instead, you add them to the module object and import
them into your imported object’s scope. Nothing ever /needs/ to be
added to global scope, and developers are protected in many ways from
accidentally “sharing” variables with other modules on global scope.
It’s not a solution for everybody, but it’s a good solution for many
Frankly, the community sees a lot of new Ajax libraries written by folks who don’t appear to have familiarized themselves with the state of the art before diving in. While I have respect for anyone who takes the time to contribute to the community, it’s refreshing to see a craftsman at work. Nice work, Kris!
Posted by Ben Galbraith at 8:00 am