When it comes to javascript, there is one discussion that is very old: to use jQuery or to use prototype?
Opponents of jQuery will tell you that it’s really weird that jQuery has a function called inArray, that instead of a boolean value returns the index where the element was found, and -1 if it’s not found. It also sucks that jQuery messes with the this pointer when iterating over an array.
But still, jQuery feels more fluent to me. It doesn’t matter whether you handle one element, or an entire set. So usually you won’t even need to iterate over a set. In the edge case you have to, it’s much better to use a for loop! Prototype also hacks into existing constructions, possibly causing naming collisions. jQuery is somewhat smaller at 93.6kb vs 134.8kb. Even had to minify prototype myself to make a fair comparison (would have been 180.8kb otherwise).
Anyway, for the sake of argument, lets assume that jQuery is awesome, and people that like prototype are just wrong. The other premesis is that having two frameworks in a project, both solving essentially the same problem, means there are multiple ways to solve a problem. Which means that problems are actually solved in different ways.
This month I was allowed to improve the client side performance of an application that was including both of the libraries. This is, of course, bad for the understandability of the view layer. It also costs time to retrieve and parse those files. So it was clear: prototype had to leave.
Performing the migration
This leaves the interesting question, how to perform the migration? The application I was performing this task in is a really big one. To give an indication, searching for Element.hide/show/toggle gave a whopping 2500 search results. Most of the javascript in it was fairly simple. Here is a rough summary of the steps I took to remove prototype bit by bit.
- First I wanted to replace some of the prototype related libraries. For example, I included the jQuery UI calendar instead of a prototype based one. Also made a quick popup window in five lines to replace the prototype window class.
- The second target was to remove Scriptaculous. The only part that was actively used was the animation part, it was pretty easy easy to port. Most of the animation code was generated from a PHP function, so that was fixed by replacing that to generate jQuery code instead.
- For the third step I inserted the sfJqueryReloadedPlugin. It turns out that the remote_function does not, as opposed to the prototype version, include a post parameter for the clicked submit button. This caused some problems where this value was checked in the controller layer, but nothing that was impossible to fix. This plugin is pretty good, but it does not port all the methods. Some of the unported methods were never used, so I removed those. For the rest I implemented a simplified version that was good enough to solve the problem.
- As the fourth step I checked for usages of the Form class. These could also be counted on one hand, so replaced those as well.
- Then the fifth, biggest step came, to replace references to $(. There were a smashing 1000 search results, of which I managed to search / replace about 600. Think like replacing $(‘a-Z’).hide() to jQuery(‘#a-Z’).hide(). The rest of the matches were too complicated to replace automatically, so there was still a lot of work involved here.
- For the last step I searched for usages of the Effect class. As it turns out only the show, hide and toggle functions were used, but it was too much work to replace this. So I created a small adapter:
var Element = { hide: function(elem) { jQuery('#' + elem).hide(); }, show: function(elem) { jQuery('#' + elem).show(); }, toggle: function(elem) { jQuery('#' + elem).toggle(); } }
Please note that it is also possible to pass a html element to the hide/show/toggle functions, this will not work with this simplified adapter. I replaced the calls where this happens because of that.
And once that was done, all of prototype was removed!
There are a couple of things to note here:
- This was done on an internal system that we will have to maintain for the coming years.
- The risk of breaking stuff when doing this is very high. Everything that breaks is easy to fix though.
- Because of this risk, I added a new Javascript error handler at
window.onerror
. This way I found out errors before the users did. Be sure to limit the amount of feedback that can be sent! - A new version of the application went live every single day, and because of this I got excellent user feedback.
- A week later it was time for some cleanup, where I replaced all the calls to jQuery with a $.
So that was one of the weeks in this month. For the rest I’ll be minifying and combining javascript and css. Also I’ll be looking at YSlow and Google Page Speed, that will give a even bigger speed bump!