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 :)

No comments:

Post a Comment