Saturday, January 19, 2008

Site performance - javascript & css aggregation, and CSS sprites

About a month ago we started working hard on improving the performance of our Traxtuff website. After some research we came to the conclusion that it was our many css and javascript files that were causing most of the performance problems. Also - every css and javascript file were called many times on each page, causing very strange firebug problems. So we decided to add a javascript and css aggregation mechanism to our project.

Basically how it should work is by replacing all the calls to javascript and css by one call to a file containing all the concatenated javascript and css files. It should recognize when the cache is out of date and regenerate it. It should include each javascript and css only once inside of the cache.

Now, I am very loath to write "frameworks". Why? Because I would much rather find someone else's framework and use it. Most likely they already went through all the debugging and testing of the framework. Still, I couldn't find someone else's php code that does this. Drupal 5 has css aggregation, and Drupal 6 has css and js aggregation, but since we don't use Drupal in Traxtuff, I wanted to write clean code that would do this for me.

So after a long and arduous debugging process where almost every day I received an emotional email from Yonatan about another bug in the caching mechanism, I finished writing our very own javascript and css aggregator for PHP (Amazing how many bugs 175 lines of code can contain).

I released it in google code here. There are many problems with this project, it is not really a "framework" yet, but more ugly patchy code. But I feel it is good enough to let other programmers look at, and maybe copy paste relevant portions into their projects. I hope to "componentize" this code in the near future.

I can safely say our site now loads about 5 times faster than it did before we introduced this caching mechanism.

However, as our site's design gets more complex, we bumped into another problem - the site is starting to slow down again because of having too many images (many of them quite small). The best solution for this kind of problem is to use sprites (basically a technique of slapping all of your images into one large image and using CSS to display only the appropriate portions). You can read about it in A List Apart. We haven't done this yet, but I found a really cool sprite and css generator we can use.

To learn about improving your site's performance, I really recommend reading Yahoo's best practices for speeding up your website, and also installing the really really cool YSlow firefox plugin to check how well you applied those practices to your site.

Friday, January 18, 2008

hurrah Modalbox

I needed a modal dialog for Traxtuff (www.traxtuff.com). I've searched far and wide, and came up with this one:
http://www.wildbit.com/labs/modalbox/
aka "Andrew Okonetchnikov's modalbox".
I found some other good stuff like ThickBox, but I use Prototype and Scriptaculous already and Thickbox is based on jQuery. jQurey is great. Prototype/Scriptaculous is also great.
there's also "Lightbox" and "Lightbox gone wild" (you can see links to others on the modalBox page).
for me modalbox worked great, and since it made such a good impression I am considering also adopting their tooltips lib.
browsing around their site I read the blog and was also happy to find some interesting content and almost no BS :)
http://www.wildbit.com/blog
so, I'll keep you posted if I have any trouble or need to tweak it. so far it worked great out of the box.

UPDATE 25/1:
I ran across some encoding problem in IE7, (didn't test on IE6) - I don't know if it's in modalBox, but I will describe my solution here anyway. if anybody can shed more light on the subject please do.
so: I use modalBox to do invitation previews. before inviting users to the site the users see a preview of the invitation about to be sent. I used modalBox to display the preview - just pass the invitation preview URL + a couple of parameters to modalBox and it does the rest very nicely.
the problem I had was that the personal message, which I also sent by GET, appeared garbled in the preview modalbox. this only happened on IE7.
I added the line
header('Content-type: text/html;charset=UTF-8');
to the preview code, hoping it's a header issue, but no such luck.
then I decided to try another approach and just base64ed the message field and decoded it in the preview script. this solved the problem.
shame on me, I didn't dive deeper inside to find the reason for this. but if you know it or have suggestions - thanks in advance.

Tuesday, January 15, 2008

Gettext for PHP and Javascript

I feel like a spy returning from the cold. I haven't visited this blog in quite a few months, mostly because writing blogs is difficult. I must say after writing my first 2 posts I have a whole new appreciation for bloggers our there, who I mocked and dissed over years. But as a web programmer I bump into weird problems almost every week, and after I solve them I always tell myself "someone should write about this." So I finally picked up the courage to return here again. But enough of that lets get to business.

Recently we have been internationalizing (aka i18n, l10n) our website Traxtuff. Like most programmers we began by producing a detailed design of our dream translation framework for PHP, but a quick search in google made us choose gettext. There are many good articles about using gettext with PHP(Here's one and here's another, Pablo Hoch also posted a benchmark of gettext). Even though it's far from perfect, it's widely used in many projects, so we decided to use it. We added some nice helper functions to wrap _() (aka gettext()):


function _L($str)
{
echo _($str);
}

function _S()
{
if (func_num_args() == 0) {return;}
if (func_num_args() == 1) {_L(func_get_arg(0));return;}
$args = func_get_args();
$args[0] = _(func_get_arg(0));
echo call_user_func_array('sprintf', $args);
}

function _T()
{
if (func_num_args() == 0) {return false;}
if (func_num_args() == 1) {return _(func_get_arg(0));}
$args = func_get_args();
$args[0] = _(func_get_arg(0));
return call_user_func_array('sprintf', $args);
}


Don't ask what L, S & T stand for, I just chose letters randomly :) .

We then had to decide what to do with internationalized strings inside our javascript files. I bumped (completely by accident while researching something else) upon a cool implementation of gettext for Javascript. Here is a link to it. I admit it's not very well documented (major understatement), but it's pretty simple to use. To get the code you need to go into the code repository here. This code depends upon the ubiquitous prototype javascript framework. Basically what you need to do is add in your HTML a link to your po (POT) file:

<link rel="gettext" lang="de" href="[path to your po file]"></link>

And after it in your scripts:
Gettext.lang = 'de';
Gettext.gettext('hello %s','world');
_('hello %s','world');


Yonatan couldn't make this work, so he added before this the code:

Gettext.include.apply(Gettext, Gettext.links[Gettext.linksPointer]);
Gettext.linksPointer++;

Hope this works for you. Let us know if you find something more elegant.

Everything was working peachy, until we upgraded to prototype 1.6. I spent a few hours today trying to figure out why gettext.js stopped working. Finally it turned out that the prototype function Array.indexOf doesn't work in the same way (hope I'm not saying something stupid). Previously it supported finding elements in an array like this:

var arr = [["a"], ["b"]];
arr.indexOf("a");


But now it only supported flat arrays (I think this is because prototype.js first checks if the browser already has an implementation of Array.indexOf). So I fixed gettext.js by adding flatten() call before calling indexOf for the message arrays. It's just a small change in 2 places, so it's not too scary :).

That's all, happy hacking for all of you.

p.s. We recommend creating a separate po files for your javascripts, so it will be as small as possible. After all this gettext implementation isn't very efficient.