Imagine web-application with lots of JavaScript code and CSS files which depend on each other.
Each page needs specific JS and CSS files, but it's not always easy to say which exactly - pages are constructed from relatively independent parts.
Moreover, some content which needs JS may be loaded asynchronously.
So, I'm goin to make some research about flexible ways of dependency management with JavaScript and blog about it.



RequireJS
(requirejs.org, https://github.com/jrburke/requirejs)

An implementation of CommonJS AMD (asynchronous module definition) specification (http://www.commonjs.org/specs/)
There are quite a few similar solutions which differ in minor details - see http://www.commonjs.org/impl/
Seems to have rather high development activity. Script size: 77.8kb uncompressed, 12.4kb compressed.

At its simplest RequireJS can be used in a following way.
Include RequireJS script in your page:

<script src="scripts/require.js">
</script>
Somewhere in the scripts in you page call RequireJS functionality specifying modules list and a callback which should be called after modules load:
require(["some/module", "a.js", "b.js"], function(someModule) {
//this would run after requirements have been loaded
//some/module is retrieved by convention from some/module.js
});
However, recommended approach is to specify main file when including RequireJS:

<script data-main="scripts/main" src="scripts/require.js">
</script>

RequireJS allows to define modules. A module is an isolated namespace located in separate .js file which can have dependencies to another modules.
Usually module takes a whole .js file.
To me it look like a kind of dependency injection - modules receive references to their dependencies from outside.
Here is an example of module definition:


define("my_module", //module name
["my_widget", "my_messages"], //module dependencies
function(widget, messages) {
//This is a module function. Whatever it returns becames module content.
//Its dependencies my_widget and my_messages generally should have similar function too,
//values they return became respective module content and are passed as 'widget' and 'messages' parameters to this function.
//By the time this function is called it's guaranteed that my_widget and my_messages have been already loaded
return {
msg: "hello, world!",
show: function() {alert(this.msg)}
//other code which may use widget and messages
}
}
}
);

So, dependencies are defined inside a script which needs them - there's no central place which lists all dependencies.
On the one hand this simplifies developers collaboration (everyone can foccus on changes inside his own module), but on the other hand
it can be better not to modify some files (for example, third-party libraries) as it would complicate their upgrade at later time.


By default, for same hierarchy level script loading order is not guaranteed. This is not a problem for modules as they should
interact only through dependencies passed to their functions. But plain .js files may expect some loading order so that global
workspace would be prepared correctly. Usually it's possible to guarantee order with following syntax:

require(["order!one.js", "order!two.js", "order!three.js"], function () {
//This callback is called after the three scripts finish loading.
});
However, there are some limitations - see RequireJS API for details.

RequireJS has no explicit support for CSS files loading. As they explain on their FAQ page (http://requirejs.org/docs/faq-advanced.html#css)
some browsers don't allow to determine when CSS file was loaded, so code that relies on elements style (size, position, etc.) can be broken.
FAQ also includes an example of CSS loading functions for those who don't care about loading order.
Modules may load CSS files content as their dependencies using "text!" modifier - see API (http://requirejs.org/docs/api.html#text).



Jingo
(http://code.google.com/p/jingo/)

More or less functionaly similar to RequireJS but has slightly different syntax. Project doesn't have any activity since November 2009.
Script size: 31.2kb uncompressed, 7.6kb compressed.

That's how one would declare 'hallmart.Store' module which references 'hallmart.Greeter'

jingo.declare({
require: [ //here goes list of required modules
'my_required_module',
'another_required_module'
],
name: 'my_requiring_module', //a name of declared module
as: function() {
//here goes the body of the module.
//It will be executed after loading of all dependencies
}
});
Unlike RequireJS Jingo doesn't use result returned by module body function (method 'as'),
neither does it provide references to modules loaded as requirements (simply because their body function result is either discarded).
So, cooperation between modules uses global namespace.

In order to use modules in some page one should define anonymous module which references all the necessary dependencies:

jingo.anonymous({
require: [
'firstLibraryModule',
'secondLibraryModule'
],
exec: function() {
//This will run after all requirements are loaded.
//Optional, can be skipped if all work is done by depencencies
}
});

Documentation doesn't mention any CSS loading support.




Pyramid-js
(http://code.google.com/p/pyramidjs/, http://joel.inpointform.net/software-development/pyramid-js-a-web-dependency-manager/)

Rather recent - appeared on code.google in year 2011. Uncompressed script size - 19.8kb. Compressed script isn't provided by author.

This dependency manager takes a different approach. All dependencies are supposed to be declared in one .js file:

Pyramid.rootPath = './'; //where from load dependencies
//Set up file dependencies
Pyramid.newDependency({//declare dependency with couple of .js files
name: 'jquery',
files: ['jquery.js', 'jquery-ui.js']
});
Pyramid.newDependency({//declare dependency with both .js and .css files
name:'myWidget',
files: ['myWidgetStyle.css', 'myWidgetCode.js']
});
Pyramid.newDependency({
name:'main',
files: ['init.js'],
dependencies: ['jquery','myWidget']
});


And then page should include PyramidJS, dependencies file and call Pyramid API to load main module:


<script src="standardResources/Pyramid-1.0.1.js" type="text/javascript">
</script>
<script src="dependencies.js" type="text/javascript">
</script>
&ltscript type="text/javascript">
Pyramid.load('main');
</script>

Unfortunately, there are no callbacks available to run some code when module has been loaded. If you need to run some code when module A has loaded
you need to place it in module B and specify in requirements file that B depends on A.

As you can see, PyramidJS supports CSS inclusion, but this feature should be used with caution. When PyramidJS needs to apply CSS file it just adds tag with appropriate "href" property.
So, There is no way to find out when styles have finished loading. This should be kept in mind when manipulating DOM from javascript at load time as styles may
be not applied by the that moment.



YUI3 modules
(http://yuilibrary.com/yui/docs/yui/)

YUI toolkit provides this feature among others. Size of script needed for modules loading: 268kb uncompressed, 66.4 kb compressed.
But in addition to this script YUI asynchronously loads few more which are needed in order to perform basic functions.
Approach taken by YUI is rather close to one of PyramidJS: first declare requirements tree in some specific place, then call YUI API to load modules tree.
Modules are defined in general YUI configuration object - the one which is passed to YUI object creation function. This looks like following:


<script src="yui/build/yui/yui.js">
</script>
<script>
YUI({modules: {
script1: {fullpath: "script1.js", requires: ["script2"]},
script2: {fullpath: "script2.js", requires: ["styles"]},
styles: {fullpath: "style1.css", type: "css"}
}}).use("script1", function(Y) {
//Optional - callback which will run after all dependencies are loaded
//Takes YUI object as argument
});
</script>

Pay attention that YUI also supports CSS loading - developer just needs to specify module type.
CSS is applied by inserting new tags with "href" - not with content.
Nevertheless, documentation claims that YUI can recognize when CSS loading finishes, guarantees that CSS files of the same dependency level will be loaded prior to .js files and even add some callback to such event
(http://yuilibrary.com/yui/docs/api/classes/Loader.html), but doesn't say how do they do it. I tried to debug a little to find out
how did they bypass standard limitations but this part is very complicated.

In order to have a real module which integrates in YUI infrastructure, not just plain asynchronously included file,
.js files which contain modules should look like this:

YUI.add('mymodules-mod1', function(Y) {
//this function is executed after call to YUI.use
//Y argument is the YUI object instance for which 'use' method has been called
Y.namespace('mynamespace');

Y.mynamespace.Mod1 = function() {
// expose an API
};

}, '0.1.1' /* module version */, {
requires: ['base'] //modules on which this module depends - I think it somewhat duplicates YUI object configuration
});

CurlJS
(https://github.com/unscriptable/curl)


CurlJS is a part of a bigger set of javascript tools - CujoJS (http://cujojs.com/). This loader sticks to AMD-type modules and mostly resembles RequireJS but it supports CSS loading and can guarantee that CSS files are loaded before execution of scripts - there are some tricks and workaround inside to make this work in all browsers.
It also sticks to promises-based API (http://wiki.commonjs.org/wiki/Promises) which allows more compact and comprehensive syntax.
Uncompressed size of minimal distribution: 21.2 kB, compressed: 5.12 kB. However, in order to handle CSS and non-moduled JS files correctly it needs plugins - 15.3 kB and 4.9 kB respectively, uncompressed.
So, let's keep in mind 41.4 kB as uncompressed size.

This is typical example of CurlJS usage somewhere in HTML page:

<script src="js/curl.js"></script>
<script>
curl(["js/myModule", "js/css!css/stylesheet.css"]).then(function(myModule, stylesheet) {
console.log("Modules loaded", myModule, stylesheet);
},
function(ex) {
console.log("Modules failed to load", ex);
});
</script>

Here "js/myModule" refers to file "js/myModule.js" which contains module definition and "js/css!" refers to CSS loading plugin
which resides in "js/css.js" file. Both myModule and stylesheet.css will be loaded prior to executing success callback function.

myModule.js may look this way:

define(["js/myModule2", "js/js!js/legacyLib2.js", "js/js!js/legacyLib1.js", "js/css!css/moduleStylesheet.css"], //dependencies list
function(mod2, lib2, lib1, modCss) {
//here goes module body
return {doSomething: function(alert("hello!")}; //this object will be passed to code that declares dependency on this module
});

All dependencies are guaranteed to be loaded before executing module body.
Here "js/myModule2" refers to another module (which calls 'define' internally) and "js/js!" refers to legacy JS loader plugin (placed at "js/js.js"),
without this plugin CurlJS would expect that 'define' is called somewhere and will be very disappointed when it finds no calls. So,
"js/js!js/legacyLib1.js" and "js/js!js/legacyLib2.js" refere to some third-party libraries which don't contain modules and don't call 'define'.
And, as you can see, modules can also depend on CSS stylesheets.

Later in the same page it's safe to ask for the same modules:

curl(["js/myModule"]).then(function(myModuleAgain) {
console.log("Modules loaded again", myModuleAgain);
});

Neither JS file will be loaded again, nor module body executed - CurlJS will just take what the module body has returned when it was called first time
and pass it through myModuleAgain to handler function.



Let's put it all together

RequireJS Jingo PyramidJS YUI modules CurlJS
size (uncompressed) 77.8k 31.2k 19.8k >268kb 41.4k
maturity high medium low high medium
development activity high low medium high high
depencency configuration in modules in modules centralized combined in modules
CSS support no no partly yes yes
module incapsulation full partial no partial full


That's all for now. I would appreciate comments and suggestions about these or, possibly, better libraries.