Archive for the 'performance' Category

Installing pngcrush at dreamhost

Thursday, September 4th, 2008

pngcrush is an excellent optimizer for png images, simple and fast, highly recommended. Basically any time before you post a PNG on the web, you should run it through PNGCrush. It's a command line utility, there's a quick way to integrate pngcrush in windows explorer. (note to self: I actually wrote a wordpress plugin to do crush uploaded images, gotta find and post it)

I'm working (together with Nicole) on an online image optimizer, using the free command line tools I talked about at PHP Quebec, Montreal, O'Reilly's Velocity and the Yahoo! Performance pages. The tool will be free, of course, and hosted on Dreamhost. OK, finally getting to the point, am I?

Here's what I did to install pngcrush on Dreamhost, hope it helps anyone out there who's sweating with the same task.

  1. ssh to your dreamhost box, you need to give yourself ssh privileges from the dreamhost control panel
  2. mkdir tmp - make a temp directory
  3. cd tmp - enter the temp directory
  4. wget http://voxel.dl.sourceforge.net/sourceforge/pmt/pngcrush-1.6.10.tar.gz - download latest version of the pngcrush source code, this is the sourceforge mirror, closest to me, yours might be different and the pngcrush version might be newer at the time you install it, you can check here
  5. tar zxvf pngcrush-1.6.10.tar.gz - uncompress the code. This creates a new directory, enter it like so: cd pngcrush-1.6.10
  6. now all you need to do is run make, but didn't work for me, so I edited the Makefile file first, like so vi Makefile. In the vi editor, press i to enter INSERT mode and once you're done with the changes, press ESC, then type :wq (meaining write, quit). Here I removed the version information from the gcc compiler, so the two lines I edited became
    CC = gcc
    LD = gcc
  7. make - this compiles the pngcrush code and creates a binary called pngcrush
  8. cp pngcrush ~ - copy the new binary to your home directory
  9. chmod 0755 ~/pngcrush - make the binary executable
  10. All set, try if everything is ok ~/pngcrush, this should display the pngcrush's help information

Crush'em PNGs!

Like so:
~/pngcrush image.png -rem alla -reduce -brute result.png

Need hosting?

[advertisement]Dreamhost is a really good host, use this code MAXDI for $50 off [/advertisement] ;)

 

Vote for my SXSW 2009 panel

Wednesday, August 13th, 2008

Stuart Colville has put up a proposal for the South By Southwest Interactive 2009 conference, or SXSWi 2009 for short. This is a panel with me and Nicole from Yahoo!'s performance team and Stuart and Ed (ex-UK-Yahoo!s, currently in GCAP Media). In Stuart's own words:

... a panel on practical performance techniques featuring Stoyan Stefanov and Nicole Sullivan of Yahoo!’s Exceptional performance team alongside Ed Eliot and myself. In this panel we'd like to talk about all aspects of the practical things that can be applied to any site to improve the front-end performance.

To vote for this panel please visit "Practical Website Performance"

The way SXSWi works is the community votes for the proposals they consider worthy. And the selected proposals get to go to the conference.

This was the long version.

Short version: vote for me!

 

When PNG8 is not the right choice

Saturday, July 26th, 2008

Dave Artz has a nice post about PNG8 and alpha transparency, my extensive comments on his article are awaiting moderation right now :)

Dave has a very cool blog btw, only a few posts but they are all really good, there's a video tutorial of AOL's Pagetest tool and a discussion on non-blocking javascripts, something I just talked about over at the YUI blog.

PNG8
(Illustration by Dave)

 

fun: front-end performance vs back-end performance

Friday, July 25th, 2008

I'm explaining to .. let's call him "This guy" what I work at Yahoo!:

[2:10:32 AM] stoyanstefanov says: research, experiments, building tools
[2:10:38 AM] stoyanstefanov says: all around performance
[2:10:45 AM] stoyanstefanov says: and focused on the front-end
[2:11:33 AM] This guy says: front-end performance sounds so rude
[2:11:58 AM] stoyanstefanov says: haha :D
[2:12:10 AM] This guy says: but back-end performance sounds even worse

 

My online footprint lately

Wednesday, July 23rd, 2008

This is a sort of a catch-up post for listing what I've been up to lately.

  • YUI Blog just published my first article, I'm so proud. It's about loading JavaScript in non-blocking fashion, because JavaScripts, they, you know, like, block downloads. Luckily, there's an easy fix - DOM includes, which I've previously discussed, discussed and discussed.
  • SitePoint published an update to my older article that introduces AJAX, ok, Ajax, by creating a command-line-like interface with PHP on the server side. The updated article features improved code, jQuery example, YUI example, JSON discussion and example. Check it out, bookmark and recommend to your friends that keep asking you "What's this AJAX (they are new, don't know it's now spelled "Ajax") thing? Do you know of a good article?"
  • YDN (developer.yahoo.com) published a video presentation of me and my lovely teammate Nicole Sullivan where we talk about some new and cool front-end performance techniques. So if you wandered how I look and are eager to hear my fabulous Balkan peninsula accent, give it a shot. The talk is called "After YSlow 'A'" and is targeted at those of you who have reached performance nirvana, but are still hungry for more. We talk about preloading components, post-loading, javascript, images, using flush() in PHP to send first byte early on and other fun stuff.
  • Last, not least, I decided to try and find some time to update my JavaScript patterns site. Unfortunately I got sidetracked (yep, I'm easily distracted by shiny objects) and played with a not-so-javascript pattern. The post I published (includes a pretty lame screencast! and) demonstrates how you can use animated background position to indicate loading progress.

Whew, c'est tout pout ce moment, expect a lot more now that the JavaScript book is out of the way. Ah, yep, if you feel like it, join me on Facebook, I created a JS book page.

 

Image optimization - 7 mistakes

Thursday, June 26th, 2008

The slides from my talk at the Velocity conference

» Download

 

www vs no-www and cookies

Tuesday, May 13th, 2008

One of Yahoo's performance rules says: Use cookie-free domains for static components. This is good because the server has no use for cookie information when serving a JPEG or another static component, so all this cookie information creates network traffic for no reason.

One of the implications of following the rule is related to the whole www vs no-www question. Basically you should always use www if you're planning to use any other sub-domains and you want them cookie-free. This is because you have no way to set a cookie only to the top-level domain. So for example you cannot write a cookie only to phpied.com. If you load component from img.phpied.com, the cookie from phpied.com will be sent again. In Firefox this doesn't seem to be the case, but in IE it is.

Here are two test scripts that demonstrate the behavior:

Load both of these pages and then reload them to see what cookies are sent. They both try to set cookies in all possible ways:

  1. omitting the domain name
  2. .domain.com
  3. domain.com
  4. www.domain.com

Here's the source code of the files:

nowww.php [test]

<?php
setcookie('no0', 'no www, no domain');
setcookie('no1', 'no www, .phpied.com', 0, '/', '.phpied.com');
setcookie('no2', 'no www, phpied.com', 0, '/', 'phpied.com');
setcookie('no3', 'no www, www.phpied.com', 0, '/', 'www.phpied.com');

echo '<pre>';
print_r($_COOKIE);
?>

yeswww.php [test]

<?php
setcookie('yes0', 'yes www, no domain');
setcookie('yes1', 'yes www, .phpied.com',    0, '/', '.phpied.com');
setcookie('yes2', 'yes www, phpied.com',     0, '/', 'phpied.com');
setcookie('yes3', 'yes www, www.phpied.com', 0, '/', 'www.phpied.com');

echo '<pre>';
print_r($_COOKIE);
?>

Loading the two pages twice shows how in IE, no0, no1, and no2 are all visible when using www as well as when not using it. In Firefox it's almost the same, only that no0 is not visible when using www.

As a take-home:

  • use www
  • write cookies to the appropriate domain level (e.g. don't write to *.domain.com)
 

Rules for fast web pages

Monday, April 14th, 2008

... just updated.

http://developer.yahoo.com/performance/rules.html

The list (used to be 13 items, then 14, now 34) is becoming too overwhelming so it's now split into categories:

  • content
  • server
  • cookie
  • css
  • javascript
  • images
  • mobile

Act now to improve you page loading speed, your user will thank you later ;)

 

Background repeat and CSS sprites

Thursday, April 3rd, 2008

CSS sprites are a great way to improve the loading speed of your pages. One of the problems you might face with sprites is how to deal with cases when the background repeats.

The rule is pretty simple: if you want the background to repeat vertically (top to bottom), place the images in the sprite horizontally (left to right) and make sure the individual images in the sprite have the same height. For the opposite case: when you want to repeat horizontally, sprite vertically.

Example

» Demo

You have three divs and you want to repeat their background vertically, so that the background takes the whole height of the div, according to the content.

Source images:

  1. 1.png
  2. 2.png
  3. 3.png

Singe image sprite:

sprite8.png

CSS definitions to make this work:

    div {
        background-image:url(sprite8.png);
        background-repeat: repeat-y;
        width: 160px;
        padding: 20px;
        margin: 20px;
        float: left;
    }

    #my {
        background-position: 0px;
    }
    #my2 {
        background-position: -200px;
    }
    #my3 {
        background-position: -400px;
    }

Every image is 200px wide. Because of the 2x20px margin left and right, the width is 160px (=200 - 20 - 20)

The tree divs have IDs: my, my2, my3

Result

Once again, the result

Tested on Windows with IE7, FF, O, S.

 

JoJo: Javascript Junk generator

Tuesday, March 25th, 2008

Ever needed to be able to generate random, but valid JavaScript code? Well, today is your lucky day: meet J.J. or JoJo, the Javascript Junk code generator.

All you need to specify is how many kilobytes of code you need. Available also as a web service.

» tools.w3clubs.com/jojo

If anyone can think of any application of JoJo, other than performance experiments, I'll be most intrigued to hear it.

 

sleep() in JavaScript

Monday, March 24th, 2008

PHP has a sleep() function, but JavaScript doesn't. Well, this is because it's useless, you might say, and you'll be right. But for simulating heavy processing and for misc performance measurements, it could be useful. So here's how you can go about creating a sleep() in JavaScript.

The code

function sleep(milliseconds) {
  var start = new Date().getTime();
  for (var i = 0; i < 1e7; i++) {
    if ((new Date().getTime() - start) > milliseconds){
      break;
    }
  }
}

Usage

Sleep for 1 second (1000 milliseconds):

console.log(new Date());
console.log('Dude!');
sleep(1000);
console.log(new Date());

Result in Firebug's console:

sleep.png

 

20 new performance best practices

Tuesday, March 18th, 2008

The slides from my PHP Quebec presentation in Montreal are up on slideshare. Roughly the content is divided into:

  1. a quick review/update to Yahoo's existing 14 best practices for improving performance, and
  2. a discussion of the 20 new ones

Enjoy responsibly and don't hesitate to send back questions and comments. And how about this slide #11: "The life of Mr.Page" 2.0 ;)

 

Simultaneuos HTTP requests in PHP with cURL

Tuesday, February 19th, 2008

The basic idea of a Web 2.0-style "mashup" is that you consume data from several services, often from different providers and combine them in interesting ways. This means you often need to do more than one HTTP request to a service or services. In PHP if you use something like file_get_contents() this means all the requests will be synchronous: a new one is fired only after the previous has completed. If you need to make three HTTP requests and each call takes a second, your app is delayed at least three seconds.

Solution

An improvement of course is to cache responses as much as possible, but at one point or another you still need to make those requests.

Using the curl_multi* family of cURL functions you can make those requests simultaneously. This way your app is as slow as the slowest request, as opposed to the sum of all requests. And that's something.

A function

Here's a little function I coded that will allow you do multi requests.

<?php

function multiRequest($data, $options = array()) {

  // array of curl handles
  $curly = array();
  // data to be returned
  $result = array();

  // multi handle
  $mh = curl_multi_init();

  // loop through $data and create curl handles
  // then add them to the multi-handle
  foreach ($data as $id => $d) {

    $curly[$id] = curl_init();

    $url = (is_array($d) && !empty($d['url'])) ? $d['url'] : $d;
    curl_setopt($curly[$id], CURLOPT_URL,            $url);
    curl_setopt($curly[$id], CURLOPT_HEADER,         0);
    curl_setopt($curly[$id], CURLOPT_RETURNTRANSFER, 1);

    // post?
    if (is_array($d)) {
      if (!empty($d['post'])) {
        curl_setopt($curly[$id], CURLOPT_POST,       1);
        curl_setopt($curly[$id], CURLOPT_POSTFIELDS, $d['post']);
      }
    }

    // extra options?
    if (!empty($options)) {
      curl_setopt_array($curly[$id], $options);
    }

    curl_multi_add_handle($mh, $curly[$id]);
  }

  // execute the handles
  $running = null;
  do {
    curl_multi_exec($mh, $running);
  } while($running > 0);

  // get content and remove handles
  foreach($curly as $id => $c) {
    $result[$id] = curl_multi_getcontent($c);
    curl_multi_remove_handle($mh, $c);
  }

  // all done
  curl_multi_close($mh);

  return $result;
}

?>

Consuming

The function accepts an array of URLs to hit and optionally an array of cURL options if you need to pass any. The first array can be a simple indexed array or URLs or it can be an array of arrays where the second has a key named "url". If you use the second way and you also have a key called "post", the function will do a post request.

The function returns an array of responses as strings. The keys in the result array match the keys in the input.

A GET example

Let's say you want to use some Yahoo search web services (consult YDN) to create a music artist band-o-pedia kind of mashup. Here's how you can search audio, video and images at the same time:

<?php

$data = array(
  'http://search.yahooapis.com/VideoSearchService/V1/videoSearch?appid=YahooDemo&query=Pearl+Jam&output=json',
  'http://search.yahooapis.com/ImageSearchService/V1/imageSearch?appid=YahooDemo&query=Pearl+Jam&output=json',
  'http://search.yahooapis.com/AudioSearchService/V1/artistSearch?appid=YahooDemo&artist=Pearl+Jam&output=json'
);
$r = multiRequest($data);

echo '<pre>';
print_r($r);

?>

This will print something like:

Array
(
    [0] => {"ResultSet":{"totalResultsAvailable":"633","totalResultsReturned":...
    [1] => {"ResultSet":{"totalResultsAvailable":"105342","totalResultsReturned":...
    [2] => {"ResultSet":{"totalResultsAvailable":10,"totalResultsReturned":...
)

A POST example

There's an interesting Yahoo search service called term extraction which analyses content. It accepts POST requests. Here's how to consume this service with the function above, making two simultaneous requests:

<?php
$data = array(array(),array());

$data[0]['url']  = 'http://search.yahooapis.com/ContentAnalysisService/V1/termExtraction';
$data[0]['post'] = array();
$data[0]['post']['appid']   = 'YahooDemo';
$data[0]['post']['output']  = 'php';
$data[0]['post']['context'] = 'Now I lay me down to sleep,
                               I pray the Lord my soul to keep;
                               And if I die before I wake,
                               I pray the Lord my soul to take.';

$data[1]['url']  = 'http://search.yahooapis.com/ContentAnalysisService/V1/termExtraction';
$data[1]['post'] = array();
$data[1]['post']['appid']   = 'YahooDemo';
$data[1]['post']['output']  = 'php';
$data[1]['post']['context'] = 'Now I lay me down to sleep,
                               I pray the funk will make me freak;
                               If I should die before I waked,
                               Allow me Lord to rock out naked.';

$r = multiRequest($data);

print_r($r);
?>

And the result:

Array
(
    [0] => a:1:{s:9:"ResultSet";a:1:{s:6:"Result";s:5:"sleep";}}
    [1] => a:1:{s:9:"ResultSet";a:1:{s:6:"Result";a:3:{i:0;s:5:"freak";i:1;s:5:"sleep";i:2;s:4:"funk";}}}
)
 

7 habits for a highly effective performance optimization effort

Monday, January 7th, 2008

... by Tenni Theurer.

Here's the piece on YDN (Yahoo Developer Network).

Read and adopt. :D

 

My performance article up on SitePoint

Thursday, October 25th, 2007

click

 

Quick CSSSprites.com stats

Wednesday, September 12th, 2007

Just deleted about 3 gigs of files uploaded to csssprites.com because I'm running out of disk space. I keep the .html results though, just for stats purposes. So ls | wc -l says that there are 3170 files meaning over 3000 sprite images were produced already, nice.

The tool is very primitive, I admit, its main purpose was to raise awareness of the CSS sprites technique. It's missing a lot of features (as any weekend project do), but I'd say it allows developers to start quickly with the sprites and to admire faster loading web sites in less seconds than it takes to say "OK, how I have open Photoshop and to create one big image of all my smaller images and write down the coordinates in a css file".

 

12 questions to ask your host-to-be

Saturday, September 1st, 2007

I was working on an article discussing how to implement Yahoo's 13 rules for front-end performance on a shared host, I hope the article will be out soon. Anyway I have access to accounts at a few hosts - icdsoft.com, site5.com, dreamhost.com, so I was trying out the stuff I wrote in the article and had different issues with the different hosts.

I know choosing a hosting company is not easy, mainly because of all affiliate sites that publish fake reviews selling the host. Just like most other things, it's pretty hard to find unbiased reviews. So here's a list of questions to ask your future host-to-be, hopefully if they say YES to all, they have a pretty good shot at being decent. Of course there are other non-technical questions that are equally important (customer service, speed, price, etc) but hopefully this list can get you something to ask when wondering which host to pick.

  1. Can I create any number of subdomains?
  2. When I create a subdomain can I pick the directory where it points to?
  3. Can I park several domains?
  4. Can I set up cron jobs?
  5. Can I create use symlinks?
  6. Can I create .htaccess files?
  7. Is mod_expires ON and can it be enabled/configured in .htaccess?
  8. Can I use per-directory php.ini files?
  9. Can I use php_value (and php_flag) directives in .htaccess?
  10. Is mod_gzip (or mod_deflate) installed?
  11. Is mod_headers ON and can I use Header directive in .htaccess?
  12. (bonus/trick question, nothing to do with performance) Is there monthly bandwidth limit? If the hosts says something like "unlimited" or "unmetered" bandwidth for under $99, you should most likely avoid them

Again, a good host should be able to answer all questions YES, except for the trick one. Note that not all these things are required to implement the performance rules, some are substitutes for each other. For example you might not need local php.ini files at all if the hosts allows all things to be setup in .htaccess. But anyway, your future host should have as many YES answers as possible.

 

YSlow performance extension for Firebug

Wednesday, July 25th, 2007

Steve Souders, performance architect at Yahoo, announced today the public release of YSlow.

What's YSlow?

It's an extension to Firebug (yes, correct, Firebug, not Firefox) that helps with performance optimization efforts. It scores your page on the scale A to F, based on compliance with Yahoo's performance rules. It's a tool that has been used internally at Yahoo and is now released to the world.

The score

Here's how YSlow scores Yahoo's homepage, gives it an A with 93 points out of 100
yslow.png

You can see on the screenshot how YSlow is just another panel within Firebug, and when you select the panel it gives you a few features. The main one is Performance (shown on the screenshot).

You get a list of 13 things YSlow has evaluated (they are based on the performance rule) and if you don't get an A, there is an arrow that gives you more explanations why. Every one of the 13 items on the list is linked to online documentation of the rule so you can figure out right then what could be improved.

Other features

Besides Performance, there's also the Stats tab which gives you comparison of how your page size for visitors coming with an empty cache vs the ones that have previously visited the page.

yslow2.png

The other tab is Components which lists every component on the page along with some information, relevant to performance, such as if the component as gzipped, what was the ETag if any, component size and expiration date.

yslow31.png

In the tools section you'll find a nice surprise - integration with the JSLint tool, the unforgiving JavaScript verifier by Douglas Crockford.

The score (revisited)

OK, I'm sure you'll find the tool invaluable, but you may frown upon the score. Well, the scoring system is made so that it suits Yahoo's purposes, but you can modify it so that it fits your specific needs. In order to customize the points system you can go about:config in Firebug and search for yslow. There you can specify points for the score. In addition to that you can find the file called yslowcontext.js in your Firefox extensions folder (should be somewhere in Documents and Settings/Application Data/Mozilla/extensions/steve@yahoo/, path abbreviated), if you can't find it, just search for it. In this file, there is an array that defines the weight of each of the 13 rules in the overall score, so you can tweak that as well. To find the array, search for lintweights

Have fun!

And happy performance tunning!

Links

 

phpBB front-end optimization - 1 hour workshop

Friday, July 13th, 2007

Let's go ahead and optimize our phpBB installation for front-end performance. I'll follow Yahoo's 14 optimization rules, but only implement the ones that apply for phpBB. During this short workshop there will be no changes to the phpBB code, we'll create a new template instead, so that in case something bad happens, your board will continue to work normally. As an example, I'll use the bgcanada.com/phpBB2, the board I volunteer to administrate.

The plan

1. creating two subdomains to host assets - images and css, maximizing parallel downloads (also following rule #9)
2. creating a new template (theme) based on the default subSilver template
3. moving CSS to an external file (rule #8), merging the two css files (rule #1)
4. copying images and css to the new subdomains
5. making sure css is served gzipped (rule #4) and also making sure php pages are served gzipped)
6. turning ETags off (rule #13)
7. setting the Expires header (rule #3)
There will be no explanations on the reasoning why these rules exists, but only how to implement the applicable ones in phpBB. It's a good idea though to go through the links above and read the detailed description of the rules. Maybe you'll find something that can be done in addition to the plan above. The suggested implementations were done and tested on a normal $60/year shared host, working around the limitations of such hosting. I'll speculate that at least 90% of the phpBB installations out there use shared host so I hope my implementation will be relevant to your phpBB install as well.

New subdomains

Typically, shared hosts allow subdomains and have their admin interface for doing so. In case your host is an exception, alternatively you can buy one or two extra domains to use for the same purpose - storing page assets.

So in my case, for the domain bgcanada.com I create two sub-domains - i1.bgcanada.com and i2.bgcanada.com (i as in image). Now the question would be how to divide assets between the two domains. I decided that more or less half of the images are multilingual (they don't contain text) so these go to i2. i1 will have all the rest - translatable images found in subSilver/images/lang_english, smilies and the stylesheet.

creating a new template

Let's go ahead and create a new template (theme), based on the default subSilver template. I'll call mine "sso" as in "subSilver optimized" (or the longer mirror-like version "ssoss" as in "stoyan stefanov, oh, so smart" ;) )

For this purpose, just take the contents of path/to/phpbb/templates/subSilver and copy it under the name "sso", so you'll have /templates/sso, an exact subSilver copy.

Now go ahead and modify templates/sso/subSilver.cfg, the theme configuration. First rename it to sso.cfg. Then at the top of the document, replace the line:

$current_template_images = $current_template_path . "/images";

with

//$current_template_images = $current_template_path . "/images";
$i1 = "http://i1.bgcanada.com/sso"; // language and smilies
$i2 = "http://i2.bgcanada.com/sso"; // the rest

use your domain, of course.

Then do a search/replace. All occurrences of:

$current_template_images/{LANG}/

should become

$i1/{LANG}/

And all occurrences of

$current_template_images/

become

$i2/

At the end of the file, just before ?> add a new line:

$board_config['smilies_path'] = 'http://i1.bgcanada.com/smilies';

This will overwrite the path to the smilies you set in the admin interface. This is optional, you can achieve the same by using the admin panel, but we said we wanted to have the board unaffected by the changes.

Now open templates/sso/theme_info.cfg and replace all occurrences of "subSilver" with "sso".

Now the bad news is that some of the template files (.tpl) still contain relative paths to images, we need to make these absolute and pointing to the subdomians. Since these paths are hardcoded in the templates we're sure that they are language independent, so they'll all go to the i2 subdomain. If you have a good text editor that can search/replace in multiple files, go ahead. Alternatively, use the php script I came up with. Download it, copy it to your /templates/sso folder, rename to replace.php and use it like:
C:\path\to\phpbb\templates\sso> php replace.php
It will report what it replaced.
It only searches for templates/subSilver/images and replaces with http://i2.bgcanada.com/sso

Yep, almost done with the template, one last step - the css.

Moving CSS to an external file, merging

In the default subSilver there are style definitions in overall_header.tpl. Remove them, the whole thing between the <style> tags and replace with:

<link rel="stylesheet" href="http://i1.bgcanada.com/sso.css" type="text/css">

Now rename subSilver.css to sso.css. Copy the contents of formIE.css and append it to the end of sso.css. Optionally, you can walk through the new file and strip all comments and white space. Or use my stripped version.

Note that as a side effect, the font of the board will look somewhat different, because the definitions in subSilver.css are not exactly the same as those in the overall_header.tpl. It's not a big difference, but if it's important to you, just ignore the original subSilver.css and create sso.css copying the styles from overall_header.tpl and formIE.css.

We're done with the files, the rest is sysadmin stuff.

copying files to the new subdomains

That's easy, just take everything from templates/sso/images (leave the lang_english or any other language folders) and copy to http://i2.bgcanada.com/sso

Than take all lang_* folders and copy to http://i1.bgcanada.com/sso, so you'll have http://i1.bgcanada.com/sso/lang_english/

Now copy all smilies from phpbbroot/images/smilies to http://i1.bgcanada.com/smilies/

Now take the sso.css you created and copy to http://i1.bgcanada.com/sso.css

Last, take the whole sso directory and copy to the main domain - http://www.bgcanada.com/phpBB2/templates/ so that it's in the same folder next to subSilver.

Login to the admin panel and activate the new theme as usual. Login to your account and change your theme preference to sso. You still want to test before making it a default theme for everyone.

At this point you should be able to browse your forum with the new theme and everything should look like as if you were using subSilver.

Serving gzipped content

The rule is that all served files should be gzipped, with exception for images, because gifs, jpgs, pngs are already compressed.

To serve the normal html (php) pages gzipped, just use the built-in phpBB feature, log on to the admin panel and enable gzip compression.

To serve sso.css gzipped, ideally you should only create an .htaccess file and have Apache do it for you (more info). Unfortunately my host won't allow it, so I took the alternative path - have PHP gzip and serve the css file. To do so I created an .htaccess file http://i1.bgcanada.com/.htaccess and put in it:

AddHandler application/x-httpd-php .css

This affects all CSS files (but we have only one) and makes them php scripts. If your host allows you to use php_value in .htaccess, you can do the rest of the job by only using .htaccess. Otherwise create a http://i1.bgcanada.com/php.ini file and put in it:

[PHP]
default_mimetype = "text/css"
zlib.output_compression = On
zlib.output_compression_level = 6
expose_php = Off
auto_prepend_file = "pre.php"

The first line makes php send text/css header instead of the default text/html. Some browsers won't like CSS files served with text/html header. The second line enables compression and the next line sets the compression level (could be up to 9). 4th line is absolutely optional, just removes an extra header that PHP sends. The last line sets that all served php files should include the file pre.php as if you used include "pre.php" inside every PHP script. This is actually used later for the Expires header.

turning ETags off

That's super easy. Just add .htaccess rules in both i1 and i2 to say:
FileETag none

Expires header

To set the expires header, add these lines to .htaccess on both i1 and i2

ExpiresActive On
ExpiresDefault "access plus 2 years"

This makes all files - images and CSS - on i1 and i2 expire in 2 years, if that's too much/not enough, feel free to change.

If it doesn't work for you (it didn't for me, because of the host), you can go the php auto_prepend route described above and add in the pre.php file
header('Expires: ' .gmdate("D, d M Y H:i:s",time() + (60 * 60)) . ' GMT');
Feel free to set 60 * 60 to whatever you think makes sense for you. Note that this will only affect the CSS file, images will be served "normally". You can also have PHP serve all the images, like we did for CSS, but I think it's probably too much.

Sysadmin summary

There was a lot of if-then above so let me summarize what worked for me, but try your other options first to see how "liberal" your host is in order to do a better job than me.

http://i1.bgcanada.com/.htaccess

AddHandler application/x-httpd-php .css
FileETag none

http://i1.bgcanada.com/php.ini

[PHP]
default_mimetype = "text/css"
zlib.output_compression = On
zlib.output_compression_level = 6
expose_php = Off
auto_prepend_file = "pre.php"

http://i1.bgcanada.com/pre.php

<?php
header('Expires: ' .gmdate("D, d M Y H:i:s",time() + (60 * 60)) . ' GMT');
?>

Done!

We're done! Did it take more than an hour? Hope not, although with these computers and stuff, everything takes more time than expected.

Do you see anything missing? Or something that can be improved? Or something didn't work for you? Please post a comment. The whole posting is a bit fast-paced and written in a rush (I really need to go to bed), so if there's something unclear please ask.

 

CSS Sprites generation tool

Wednesday, June 27th, 2007

Here's my last weekend's project - a web-based tool to generate images for CSS sprites: http://www.csssprites.com. Cool domain name, eh? I couldn't believe it was not taken.

CSS Spr...what?

This is a simple technique used for page load performance purposes. Since HTTP requests are the most expensive thing you can do in regards to front-end performance, you strive for making as little requests as possible. So instead of having the browser download 4 rounded corner images for example, you create one image that has all four. Then you use CSS' background-position to only show the part of the image you want. More on the subject in this ALA article

How does the tool work

You upload as many images as you want and the tool generates a mosaic of all images, gives you the result as PNG and gives you the coordinates you need to use in the background-position declaration. The tool also gives you an html page as an example, so you can save both the PNG and the html page for reference.

Image size

If you properly optimize the big image, you might actually have smaller size than all the individual images combined. In my tool, the PNG image generated is not optimized at all, I leave this to you to use PNGOUT or any other tool you know. Also you can convert the PNG into GIF if that's better for your purposes.

Implementation - PHP

The PHP code is fairly simple. The actual spriting (is that a word?) class takes a list of images and calls getimagesize() on each one to get the dimensions. The image with the biggest height is used as distance between images. The rest is just composing the imagemagick command that will to the work. Here's the important method:

<?php
function combine() {
    if ($this->distance === false) {
        $distance = $this->_biggest;
    } else {
        $distance = (int)$this->distance;
    }

    if ($this->output_dir === false) {
        $output_dir = $this->_dir;
    } else {
        $output_dir = $this->output_dir;
    }

    $half = ceil($distance / 2);

    $coord = array();
    $y = 0;

    foreach ($this->images as $i=>$data) {
        $this->images[$i]['x'] = $half;
        $this->images[$i]['y'] = $half + $y;
        $coord[] = '-page +0+' . $y . ' ' . $i;
        $y += $data[1] + $distance;
    }

    $cmd = 'convert ' . implode(' ', $coord)
         . ' -background none -mosaic -bordercolor none -border '
         . $half . 'x' . $half
         . ' ' . $output_dir . '/result.' . $this->output_format;
    system($cmd, $ret);

    return $ret === 0;

}
?>

Implementation - JS

In the spirit of web2 I couldn't afford a complete page reload :lol: although it would've been much simpler. I just had to get fancy. YUI to the rescue. On page load I set up the form for async request, using YAHOO.util.Connection. In case of file uploads YUI generates an iframe behind the scenes and uploads to the iframe. Then it takes whatever is in the body on the iframe and gives it to you instead of the XMLHttpRequest's responseText property.

So the files are uploaded to upload.php which calls the class that has the method mentioned above then loops through the $images property of the said class and writes the example html file as well as prints out a JSON string with the same image information.

YUI's Connection calls my callback function and I get the invaluable responseText. Parsing the JSON with the json.js, I get a JS object. Looping through it and DOM-generating a table of results is the semi-last step. The last is (we're fancy, remember?) to yellow-fade the background color of the result, using YAHOO.util.Animation.

BTW, I got fancy once again and combined and minified json.js with my JS file, so that there is one less request and a side effect impossible to read. The unminified version of the JS that does all the work is here for reference.

Comments

I hope this tool cane be useful for quickly generating those sprites, if only for prototyping purposes. Any comments, requests, bug reports are all very welcome.

And how do you like the version of the tool? Anyone n00b can do "beta", it takes a true h@x0r (or something) to do a better job :D

Ah, yeah, and the page badly needs a stylesheet, do you want to help?

 

Smart browsers don’t download unneeded images

Saturday, June 23rd, 2007

We ofter complain about browsers, browser inconsistencies, quirks, hickups, the list goes on. Let's say something nice to them, and hope for good karma :)

It happens as your web app grows in size and team members that some parts of the stylesheets become obsolete, no one remembers why they were there in the first place, but no one has cojones to remove them, because of fear that removing them might break something you forgot to take into consideration. Same case when you have the same stylesheet for 50 different pages and on some pages only some of the styles are actually used. I was wondering, do you pay performance penalty when you have style rules that refer to images, but the rules are never needed to render the page? Will the browser download all those unneeded images? The answer is No. The browser will try to avoid downloading the image assets as much as possible. And this behavior is pleasantly consistent across IE, FF, Safari (for Windows), Opera.

Test #1

I tried this stylesheet:

body {
    background: url(bg.png);
}
#some-id {
    background: url(ie.png);
}
.some-class-name {
    background: url(o.png);
}
p {
    background: url(image.php);
}

And applied it to a blank page that doesn't have any paragraphs, or elements with class name some-class-name, or ID some-id. The result was that only bg.png was downloaded, which is great.

> test #1

Test #2

I added a button which, when clicked, creates a new P element and sets a 3 seconds timeout before appending the new element to the body. (The background for the P element is the script image.php which only sleeps for 3 seconds for demonstration purposes, before redirecting to ff.png.) The result is that the image is requested only after the element is added to the document flow, not when it was created. Which is smart. It ain't added 'till it's added.

> test #2 (same file as test #1, btw)

Test #3

OK, modified test #2. A P element is created, its visibility is set to hidden and it's added to the document. It's officially in the document flow, although not currently displayed. My guess was that the browsers will download the image at this point. True for FF, IE7, Safari, Opera.

> test #3

Test #4

Here's the only inconsistence in the behavior of the browsers. A P element is created, but with display: none, then added to the document. So it's part of the DOM tree, but not really part of the document flow. Then another button changes display to block.

FF will wait for the display: block before downloading the image background
Opera will also wait until display: block
IE7 begins to crack under the pressure and downloads the image even with display: none
So is Safari

> test #4

So what?

Well, it's nice to know that the browsers won't download the images referred to from our stylesheets unless they are needed for rendering. There is a side effect though. If you happen to create dynamic elements that have some styles with background and no other similar elements already exists on the page, the user experience might suffer slightly as the image will generally be requested after the new element is added. In cases when this is important to you, you might need to take some extra care to make sure the image is downloaded before you add the new element. You might need to do for example a new Image() after initial page load. (reminds me of the good old days of image rollover buttons, those were the days, eh? Seems like no one does image rollover buttons and menus anymore. It's all css this, css that... ;) )

 

Delay loading your print CSS

Sunday, June 17th, 2007

So you have two stylesheets on your page, like this:

<link type="text/css" rel="stylesheet" href="screen.css"
      media="screen" />
<link type="text/css" rel="stylesheet" href="print.css"
      media="print" />

The first one is used to render the page on screen, the other one is used for printing the page and print previewing it. Good.

The thing is, when it comes to performance, the browser won't render any part of the page, until all stylesheets are downloaded (fiddled with here). That includes, unfortunately, stylesheets not designed for the currently rendered media. In other words, the browser won't display your page, until the print stylesheet is also downloaded, although it's not used at all for displaying the page. That sucks and should really be addressed in future browser versions.

Test

I did a test page to check this, it's here - print.php. It includes two stylesheets, the first one intentionally sleep()s for 5 seconds, the second one - for 10 seconds.

The result is that in both Firefox and IE it takes 15 seconds for this page to be rendered. Here's the Firebug picture:

media-print.png

In Safari on Windows, it only took 10 seconds the first time around, as both stylesheets were downloaded simultaneously. Good. The bad is that after refresh, the first CSS was not even requested, I tried it a few times, actually sometimes I got the error "The error was: “unknown error” ((null):10053) ", but hey, this is the first release of the browser, it can't be perfect. Actually after I shut down Fiddler, which is what I used to monitor the HTTP traffic, the page was back to normal, so it's not clear who's to blame.

So?

Well, in order to increase rendering performance, all stylesheets not absolutely needed to initially render a page should be loaded after the page load, in the background. Once the user has a fast rendered page to interact with, you can load the additional CSS (and JavaScripts for that matter) in the background, using script and style DOM includes.

Update: From my comment bellow - a better option is to include the print css as part of the main css:
@media print {…}