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

Monday, February 01, 2010

CommonJS - Why Server Side Only?

There is one single thing I don't like about CommonJS Idea, the fact nobody is thinking about the client side!!!

CommonJS Why

Since everybody would like to use JavaScript as Server Side Programing Language, where right now I can count there about 50 implementations, somebody decided that at least basic stuff such IO operations, streams or sockets, and much more, should have a common behavior, namespace, API, across all different implementations.
In few words, these guys are trying to create their own WSW Consortium, and this is absolutely OK.
So what am I complaining about?

Client CommonJS

If we, as developers and libraries authors, would have adopted a similar strategy ages ago, rather than fight with each other about natives prototypes pollutions, web development would be probably even easier for everybody.
We failed, while those server side guys started correctly.
The problem, for both language usage and its environment, is that JavaScript on server has different problems than a bloody "attachEvent VS addEventListener" but while common practices are in any case appreciated and widely adopted world wide, CommonJS is not friendly at all with Client Side JavaScript.
The basic example?

require

This function aim is to retrieve from a namespace, where it is basically represented via dot notation, translated into folders paths, a generic variable, object, function, whatever.
The power a dedicated build could have over ouw miserable secured/sandboxed version of browser JavaScript engines is endless.
As example, the only way I could think about to implement a client side require, is this one:

if (typeof require === "undefined") {
// (C) WebReflection - Mit Style License
var require = (function (context, root, Function) {
function require(namespace) {
if (!hasOwnProperty.call(cache, namespace)) {
var xhr = new XMLHttpRequest;
xhr.open("GET", (root + "." + namespace.replace(/(^.*)(?:\.[0-9A-Za-z$_]+)$/, "$1")).replace(/\./g, "/") + ".js", false);
xhr.send(null);
cache[namespace] = Function(xhr.responseText + ";return function(){return eval(arguments[0])};").call(context);
}
return /(?:^.*\.|^)([0-9A-Za-z$_]+)$/.test(namespace) && cache[namespace](RegExp.$1);
};
var XMLHttpRequest = this.XMLHttpRequest || function () {
return new ActiveXObject("Microsoft.XMLHTTP");
};
var cache = {};
var hasOwnProperty = cache.hasOwnProperty;
return require;
})(this, "", this.Function);
}


How It Works

Let's say we have a file called mylib.js and let's say this file contains our libraries variables.
To obtain the base object, we could simply do something like this:

<script src="require.js"></script>
<script>
var base = require("mylib.base");
base.alert("hello");

var $alert = require("mylib.base").alert;
$alert("world");
</script>

Where mylib.js file is nothing different from:

var base = (function () {
var self = this;
return {
alert:function (msg) {
self.alert(msg);
}
};
}).call(this);

Easy? In few words if a file contains proper variable declarations, rather than global myvar = {} without var prefix, we can imagine we could have all our libraries automatically "sandboxed", at least the global namespace won't be polluted that much and via my implementation of require, each file will be loaded simply once and never again, and we can retrieve step after step just what we need.

P.S. please note that my implementation is just an imperfect proof of concept since require in CommonJS accepts syntax like require("mylib").base; but untile we won't have runtime cross browser resolved get/set, this is not possible to implement synchronously :(

6 comments:

Kris Zyp said...

There are a lot people thinking about CommonJS on the client. Narwhal has client side loading code, Dojo has been looking at what APIs it can pick up, and SproutCore uses CommonJS as well, and there are plenty of others as well. However, it is true that CommonJS is focused on the server side. Most JS work has been focused on the client side, so CommonJS is trying to fill unmet niche. And lowering all standards to a common denominator that will work on the client side dilutes the APIs to the point where they no longer leverage the capabilities of SSJS platforms.

Andrea Giammarchi said...

Kris, the problem is that unless every library will return an object as proper namespace, the require function itself is not really portable, we cannot extract a variable via "get" as a native implementation could do.
If basis are not well defined or not portable for both client and server, which is the coolest part of having JavaScript on both sides, I think we are doing already something wrong, specially because require could become a standard ;)

alcou said...

I have got the same interrogation about CommonJS.

I think javascript server side has a great potential, not because of javascript but because of the browser. Since we will have Database, Threads, whatever client side, there will be a lot of needs that will overlap (ORM for example) and a lot of code to reuse.

I think CommonJS guys had it right : the main problem with server side js was the lack of a standard API (to get a viable eco-system) and i think they have so far done a great job.

But, it seems to me, that they are too focused on how Ruby or Python framework works and not on what is specific to javascript : it runs both client and server.

I think this criticism it's true for the module spec (and it seems nodeJs guys have the same problem).

And i disagree with :
"If it's possible to interface with other types of databases using the same kind of interface, that is a win. However, the primary goal with this API is to provide access to relational databases."
http://wiki.commonjs.org/wiki/RDBMS

For me, the primary goal with this API is to be compatible with databases we find server side (mostly relationnal) and client side (and it seems we are moving to an ISAM-like Database).

Kevin Dangoor said...

A couple of comments:

ECMAScript itself will get modules (I'll be learning a bit more about some of those proposals later this week, so I can't comment directly on the new syntax). The TC39 working group *is* paying attention to CommonJS and our goals will likely be aligned, even if syntaxes end up not being aligned.

Secondly, unless you're doing things like accessing files and such, modules can work between client and server no problem. The main drawback, if you can call it that, is that you can't point a script tag at a CommonJS module. But, with a tiny bit of server-side help, or a build step, or using XHR as you did, you can load CommonJS modules in the browser unmodified.

There's a trick to it though: you have to manage your dependencies properly and build up a sandbox of modules. Client side loaders tend to be a little more complex than what you have here. But they're not too bad (I wrote one myself last summer). SproutCore's loader (tiki) adds a nice abstraction (the "package") which is a collection of modules that are loaded asynchronously as a group.

Our goal has been to make CommonJS competitive with other general scripting platforms (Python, Ruby, etc) and able to run in the browser. If we push the balance toward making CommonJS just another browser platform that has been made to run server side, then it will be less competitive with Python, etc.

Thomas Aylott said...

I too believe that CommonJS will become an even more influential set of standards/conventions this year and into the future.

Many of us on the MooTools dev team have been working on bringing CommonJS conventions, ideas and compatibility into our code. Much of it is portable to the client-side.

I'm personally really enjoying a new focus on explicit references in some places. Makes maintainability easier when you can easily track down where all your variables came from.

Join us in the CommonJS mailing list. Some of the archived threads are very interesting in general too.

Brett said...

I wonder whether the approach could also be used to allow standardization (or at least extensibility with a potential for portability) for client-side privileged code.

The one difference might be that a specific exception could be thrown to indicate the user refused permission.

e.g.,

var db;
try {
db = require('com.example.xmldb');
}
catch(e) {
if (e instanceof UserDeniedPrivilegedAccess) {
try {
db = require('net.example.xmldb');
}
catch(e2) {
if (e instanceof UserDeniedPrivilegedAccess) {
alert('You have denied us access!');
}
}
}
}

Or...

var db = require('com.example.sqldb');

var fileSystem = require('com.example.fileSystemAccess');

var kinect = require('com.microsoft.kinect');

...or whatever...

Maybe nice to also allow specification of privilege level (e.g., requesting read-only access)

Firefox, for example, could be made to do this fairly quickly, by supporting, for example, their many Jetpack APIs.