My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Monday, March 08, 2010

CommonJS - A YAGNI Based "require"

I am very busy these days with my last (hopefully) moving into my new and completely empty rented flat ... and while I am building home utilities and preparing my last post about A Better JS Class, with some extra case where common libraries fail with their parent implementations, I would like to quickly share this require function I wrote days ago but just recently came back in front of my eyes.

Why require

In CommonJS we have different techniques and agreement about how developers should structure/organize their namespaces and libraries.
The first common function adopted from CommonJS "followers" is the require one. This function aim is to load once, and runtime, a namespace, allowing scripts loaded via this function to define exported properties/variables or methods/functions.

// file main.js
var $ = require("jquery").$;

// file jquery.js
// let's imagine jQuery library has this piece of code inside
if ( typeof exports != "undefined" ) {
// export the library as dollar function
exports.$ = jQuery;
}


The require Function


var require = (function (
global, // global scope object (not necessary window)
cache, // object with loaded namespace to avoid reloads
exports, // variable name (better compression)
ActiveXObject // property name (better compression)
) {
/*!WebReflection:MitStyle*/
function request(namespace) {
// create the xhr object, no need to optimize
// this check is nothing compared with the time
// required to load and evaluate the resource
var xhr = new global[global[ActiveXObject] ? ActiveXObject : "XMLHttpRequest"]("Microsoft.XMLHTTP");
xhr.open(
// method
"GET",
// path replaced
(require.root || "") + "/" + namespace.replace(/\./g, "/") + ".js",
// synchronous
false
);
// send request
xhr.send(null);
// assign the runtime created export object
// with the required namespace
return (cache[namespace] = Function(
exports,
// the text will be evaluated in a global function
// it can register exported variables/methods
// simply writing:
// export.$ = {};
// so that require("mylib").$; will always
// point to the correct property
xhr.responseText + ";return " + exports
// be sure the function is executed
// with a global context (the this reference)
).call(global, {}));
}
// if namespace has been loaded already
// the associated export object will be returned
// otherwise the precedent function will be
// executed
function require(namespace) {
return cache[namespace] || request(namespace);
}
// the exposed function
return require;
}(this, {}, "exports", "ActiveXObject"));

Above piece of code fit into about 350 bytes once minified, less than 260 bytes gzipped ... sweet, isn't it?
Bear in mind if we need a root, we can simply add it via require.root = "./my/js/path"; without last slash after the final folder (e.g. require.root="." to load from the current one).

YAGNI In Details

For those unable to understand the acronym, YAGNI simply means "Ya ain't gonna need it".
The YAGNI behind my proposal could be summarized in this way:
  • server side frameworks have their own native require, no need to fully replicate it, it's already there
  • simply Ajax, if the browser does not load via other protocols and you are testing, enable file: via about:confing/strict or the local support for XHR
  • the most used case is the one we all know, require function will be there, as global one, and module object is not yet important (CommonJS is constantly updated as well)
  • A well organized namespace will improbably affect object properties (e.g. Object.prototype["my.name.space"] does not make much sense). No need to use hasOwnProperty, specially not the object itself one, since Object.prototype.hasOwnProperty could be redefined without problems and most probably this is an edge case more dangerous than the prototype["my.long.namespace"]

All these points are better considered in another version I did not know, the one from David Flanagan.
In any case, we should consider this valid point of view about require and JavaScript client, from Lucas Smith.
At least now we have more alternatives in the field, with this one that should simply bite others at least for size, and for common case reliability.
Enjoy!

8 comments:

Azat Razetdinov said...

The synchronous require is completely useless on the web: why on earth should I freeze browser interface in order to load a module?

Also, there should be a way to set the exports to any variable.

We use the following form of require:

require(['jQuery', 'Lego'], function (jQuery, Lego) {
// code
});

Both modules are loaded asynchronously. When they are ready, the callback is fired with modules already in a closure.

kentaromiura said...

It reminds me my proof of concept :p

http://gist.github.com/314797

I know, mine doesn't handle the subdirectories,
yours is in vanilla js an my is in mootools and uses an "not so useful wrapper" around the cache object (that is a mootools hash only for readability), but eventually does the same thing :P

Andrea Giammarchi said...

@Azat Razetdinov I have posted a similar solution in Ajaxian few months ago, following similar logic. Moreover, Lab.js does what you are looking for and much more.
Unfortunately, sometimes the order is essential for dependencies and AFAIK there is no lib able to handle multiple inclusion with ordered evaluation (it shuold not be difficult to implement tho).
However, the aim of this function is to replicate the CommonJS behavior and trust me, many libraries use a similar strategy (dojo.require + dojo.provide as example)

@kentaromiura you define a local scope variable, the module one, but it is local and not shared across other requires. I am not sure this is what happens in other require but nice 1 ;)

Kevin Dangoor said...

I think we need to start a FAQ at CommonJS.org... require() is synchronous in appearance and is synchronous in execution on the server. No one in their right mind would implement it synchronously in the browser. There are ways to implement it asynchronously.

Last summer, I implemented one browser-based CommonJS module loader. Right now, for Bespin we're using Tiki which loads "packages" of modules asynchronously and works great. Both of those loaders use <script> tags, so they're async and provide great debugging. The key is a small amount of server side or build tool support.

You can use async XHR without requiring any server side support.

Andrea Giammarchi said...

@Kevin Dangoor the key would be a different behavior when require is called via more than an argument, rathen than a single one.
In this case we can use/wrap a method rather than load it synchronously.
This suggestion aim is to work cross browser and cross platform, maintaining the same code style when used in the browser since the server side works just like that.
If one of the best benefits to use JS in the server as well is the possibility to share code, I wonder why require has been created with synchronous load in mind.

Possible API/signatures I can think about:
require("nmsp"); // sync
require("nmsp", function(){}); // async
require(["nm", "sp"], function(){}); // async but evaluation order is supported (async load, sync evaluation)

Above implementations will make life easier and the basic function and its behavior shareable between client and server.

My 2 cents

Andrea Giammarchi said...

P.S. the "async" part would be async only in the browser, still sync in the server (no need for timeout with 0 delay then, if this means the user has to wait a nanosecond more the response time)

khs4473 said...

require("inClient"); exports="bullshit";

: )

Seriously, though, no async require, PLEASE! Require solves the dependency and sandboxing problems. It isn't meant to provide lazy loading. Lazy loading is a completely different problem with a different set of issues.

Unknown said...

Take a look on http://requirejs.org
It is async with script tag implementation.
May help.