Saturday, July 12, 2008

An alternative JavaScript development pattern

A common pattern to develop JavaScript libraries is to use different behaviours inside methods, and based on browser, or browser plus its version.
For about 80% of cases, these behaviours are Internet Explorer dedicated.
This approach is good enough, otherwise we could not have a wide variety of libraries to choose, but is this way to develop libraries the best one?

These are some pro concepts:

  • libraries could be usually merged into a single file, so we can add only one script tag, and for every browser

  • libraries maintenance or improvements are focused into one, or more, constructor, function, or method



The expected result is, usually, a method that is capable to understand which browser is running them, and what to do for that specific case.

// common libraries development pattern
function myGorgeusLib(){};
myGorgeusLib.prototype.sayHello = function(){
if(self.attachEvent)
attachEvent("onload", function(){alert("Hello")});
else if(self.addEventListener)
addEventListener("load", function(e){alert("Hello")}, false);
else
onload = function(onload){return onload ?
function(){onload();alert("Hello")} :
function(){alert("Hello")}
}(self.onload);
};

new myGorgeusLib().sayHello();

Is above code clear enough? If a browser implements attachEvent, use them, otherwise if it implements addEventListener, use them, otherwise use manual onload implementation.

If we would like to modify, for some reason, that method, there is only one "place" to do it, the method itself.
At the same time, every time we would like to use that method, the browser has to check 1 or 2 times which case is suited for its engine.

This concept introduces these side effects:

  • every method size is generally increased for every browser, and often only to make some task IE compatible

  • every method speed execution, is generally increased, because of useless, or necessary, checks to perform each time the method is called

  • every change inside every method, could cause side effects for other browsers, so we have to fix 3 to 4 times instead of once, because of possible problems

  • every changed method should be tested into every library supported browser, even if it worked perfectly with every one, IE a part



Lazy, or direct, method assignment


Some library implements lazy prototype assignment, so that method will be the best one, only after the first time we will use them.

// lazy method assignment
function myGorgeusLib(){};
myGorgeusLib.prototype.sayHello = function(){
myGorgeusLib.prototype.sayHello =
self.attachEvent ? function(){
attachEvent("onload", function(){alert("Hello")});
}:(
self.addEventListener ? function(){
addEventListener("load", function(e){alert("Hello")}, false);
}:
function(){
onload = function(onload){return onload ?
function(){onload();alert("Hello")} :
function(){alert("Hello")}
}(self.onload);
}
);
this.sayHello();
};

new myGorgeusLib().sayHello();

Since we could perform that kind of check directly during prototype assignment, in this case, above pattern, is completely useless.
On the other hand, doing lazy or direct dedicated assignment, we are using a better approach because:

  • method is specific for this, or that, browser, so its execution speed will be the fastest possible

  • if we need to fix that method, we can focus only into specific browser version. Accordingly, we do not need to test with every supported browser, every change we made, but only with one, or more, specific version


Code minifier maniacs, could think that in this way, and for each method that requires that strategy, the final size of the library could increase about 30%, and this is, basically, true.
So, at this point, we have the best method ever to perform that specific task, but not the best size. How could we solve this last problem?

An alternative library development pattern


Before I will talk about my proposal, we should focus on a simple, as useful, thing about libraries: modularity
What we do like about this, or that, library, is the possibility to include only what we need, to obtain the final result with smallest possible footprint.
In other words, we chose exactly what we need, and we load or use only them, without useless piece of code that could only increase our page size, with or without cache help.
A lot of libraries use this concept runtime, like Dojo, or directly during library generation, like MooTools.
This way to develop, mantain, and use libraries, is loads of benefits for both developers, and users.
At the same time, and as far as I know, nobody though to port this development pattern, to create each library "portion" a part.
This is an example of what I am talking about:

// alternative library development pattern
function myGorgeusLib(){};
myGorgeusLib.prototype.sayHello = function(){
addEventListener("load", function(e){alert("Hello")}, false);
};

// IE dedicated changes, separated file
myGorgeusLib.prototype.sayHello = function(){
attachEvent("onload", function(){alert("Hello")});
};

// if we would like to support really old browsers too, in a separated file
myGorgeusLib.prototype.sayHello = function(){
onload = function(onload){return onload ?
function(){onload();alert("Hello")} :
function(){alert("Hello")}
}(self.onload);
};


This is a benefits summary, about this proposal:

  • smallest library size ever, thanks to common standard methods used by the library itself (a sort of FireFox, Opera, and Safari dedicated version)

  • modular methods development, with separed files dedicated for one, or more, browser version (IE6, IE7, IE8), thanks to conditional IE standard HTML comments: <!--[if IE 7]><script type="text/javascript" src="library.IE7.js"></script><![endif]-->

  • "future proof", when IE will implement standard DOM or Events methods, and behaviours, we can reduce IE dedicated file size

  • single debug, modular updates. We changes only one, or more, file, instead of recompile, regenerate, redistribute, the entire library file

  • intranet friendly, update only library version for dedicated environment, if it uses only a specific browser


So why shoulld we use JavaScript to define library behaviour, instead of browser itself?
Anyway, this pattern completely brakes practices about script tags in a generic (x)?HTML page. But, at the same time, could be the key to solve a wide number of problems, starting from modern devices with possible memory limits for JavaScript files, up to library execution speed, the best possible, and maintenance, focused in a single browser, or a single browser version.
The best implementation could be a Dojo "progressive library load" style, where the core automatically knows which file should be loaded, depending on browser, or its version.
Finally, these are side effects about this pattern:

  • final and complete library size will be the biggest possible one, but there shouldn't be a case where a browser download every file, redefining N times one or more methods

  • more effort to develop an entire library using this way, if there are a lot of methods that are not compatible with every browser

  • probably something else, that I am not thinking about



Now, after this explanation, would you like to write your impressions? Cheers :)

9 comments:

alsanan said...

May be the library developers could answer that last question. It seems smart, anyway. But I think that some of them will prefer the compact block.

Andrea Giammarchi said...

It is probably true, but at the same time, my post talks about a way that will improve libraries speed from 0 to 60%, where 0 are every common functions or prototypes, while 60% are those methods with more if, else if, than clear code, to make the single method portable everywhere :)

kentaromiura said...

mmm it resemble a bit the approach i adopt here:
http://houseofhackers.ning.com/group/jsninjas/forum/topic/show?id=2092781%3ATopic%3A42332

andr3w what did you think?

Andrea Giammarchi said...

I think that it does not make sense to publish your stuff here, since I was talking about a development pattern for FF, Safari, Opera, moving IE away, and your link talks about Windows 98, and your IOC stuff, that is not related :D

Andrea Giammarchi said...

P.S. What about this?

var container = function(C, slice){
return {
byConstructor:function(id){
return C[id].apply(C[id], slice.call(arguments, 1))
},
bySetter:function(id){
return C[id].Extend.apply(C[id], slice.call(arguments, 1))
},
find:function(id){
return C[id]
},
register:function(id, component){
C[id] = component
}
}
}({}, Array.prototype.slice);

kentaromiura said...

andrea, that was only an example but in the end the approach is similar, i only load a function using a configuration file.

if instead of xml you use that library you develop for extend json using function, and using a similar approach you can load function externally using browser recognition (using Conditional comment) and for example using a schema like this

conf_FF
conf_IE5
conf_IE6

:p
if my idea it totally off topics, sorry i didn't understood your post :p

Michael (iSkitz) said...

Hmmmm...Andrea, I think you just inspired a new application for my Ajile library!

I hadn't really thought about using it's on-demand loading as a way to late load functionality based on user-agent detection. Ajile's AddImportListener() function can actually be used to late load functionality based on the existence of a specific object like document.all.

I've been doing some work to make that function more generic, as in useful outside the concept of namespaces. That should make exactly what you've described trivial to do...if it isn't already...I haven't tested the current Ajile release specifically for this type of use but it may already be possible to some extent...

Andrea Giammarchi said...

Michael, every time I think about some "fresh air", you arrive to tell me that Ajile implements that air :D

What could I say? Good stuff, as usual!

Regards

bunnyhero said...

this actually makes a lot of sense. when i look at your first example of the common libraries development pattern, the OOP alarm bells start ringing in my head :)