Combining CSS or JavaScript Files on the Fly with PHP

No Gravatar

In pursuit of creating excellent performing web pages, and inspired by YUI’s combo system, I decided to see what would be required to combine CSS or JavaScript files with PHP. My goal in doing so is to reduce the number of HTTP requests to our servers which should result in a speed up in page load times and a decrease in the load on our servers.

With performance in mind I set the following criteria for my solution:

  • It should cache combined files to increase performance.
  • When a CSS or JavaScript file is modified the cache should be expired.
  • It should send a 304 when appropriate.
  • It should perform more requests a second than requesting the multiple files individually.
  • It should decrease load on the server.

Here’s an example of a link tag for including multiple CSS files into one request:

 <link rel="stylesheet" href="/combine.php?test1&test2" type="text/css">

The combine.php script will infer what files to include from $_GET. In this case it will try to combine a test1.css file and a test2.css file. Let’s have a look at the code I’ve written to meet the first 3 criteria and then we’ll find out if there are any performance benefits.

 
// Attempt to enable gzip compression
 
ini_set('zlib.output_compression', 1);
 
header('Content-type: text/css');
 
$cssDir = dirname(__FILE__).DIRECTORY_SEPARATOR.'css';
$cacheDir = dirname(__FILE__).DIRECTORY_SEPARATOR.'cache';
 
$cssFiles = array();
$lastModified = false;
 
// Find valid css files, note the most recently modified one.
 
foreach(array_keys($_GET) as $candidate)
{
  $path = $cssDir.DIRECTORY_SEPARATOR.basename($candidate, '.css').'.css';
 
  if(!is_file($path))
    continue;
 
  $cssFiles[] = $path;
 
  $mtime = filemtime($path);
 
  if($mtime > $lastModified)
    $lastModified = $mtime;
}
 
if($lastModified === false)
  $lastModified = filemtime($cssDir);
 
// Determine whether to send a 304 header
 
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']))
  $clientIsCurrent = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $lastModified;
else
  $clientIsCurrent = false;
 
// Setup the cache
 
$cacheKey = md5(implode('', $cssFiles).$lastModified);
$cachePath = $cacheDir.DIRECTORY_SEPARATOR.$cacheKey;
$cacheExists = is_file($cachePath);
 
if($cacheExists && $clientIsCurrent)
{
  header('HTTP/1.1 304 Not Modified');
  exit;
}
 
$contents = '';
 
if($cacheExists) // Use the cached css
{
  $contents = file_get_contents($cachePath);
}
else // Create the combined css and cache it
{
  foreach($cssFiles as $filePath)
  {
    $contents .= file_get_contents($filePath);
  }
 
  file_put_contents($cachePath, $contents);
  touch($cachePath, $lastModified);
}
 
header(sprintf(
  'Last-Modified: %s GMT',
  gmdate('D, d M Y H:i:s', $lastModified)
));
 
echo $contents;

Note that the above could easily be modified to work for JS files as well. It may have some technical deficiencies but it’s adequate for a proof of concept like this. It was tested and working with Firefox, Internet Explorer 8 and Chrome.

Performance Analysis

In the test below I attempt to determine if there’s any benefit from combining 4 CSS files totalling around 55KB. I enabled mod_deflate for Apache to gzip CSS files. All tests were performed with the Apache ab tool. The first test was performed against localhost with the following parameters:


ab -n 10000 -c 5 http://test/url

Requesting a single CSS file I got 1128.22 requests per second, from that I can infer that we can request the 4 CSS files ~282 times a second. Requesting the combined CSS file I got 396.17 requests per second.

Wanting to throw network latency into the picture I also ran the benchmark against a server many hops away. On this server gzip wasn’t working for combine.php or mod_deflate. For the following tests I decreased the number of requests to 50.

Requesting a single CSS file I got 7.87 requests per second, or the 4 CSS files 1.9675 times per second. Requesting the combined CSS file I got 4.5 requests per second.

Conclusion

Combine your CSS and JS files! Even with the overhead involved in running a PHP script to do the combining I saw a significant improvement in performance for the server and the browser.

3 Responses to “Combining CSS or JavaScript Files on the Fly with PHP”

  1. FreakensteinNo Gravatar says:

    Interesting, I’m gonna test this aswell.

  2. Gerry MckoyNo Gravatar says:

    Hiya, I am really glad I’ve found this information. Nowadays bloggers publish just about gossips and web and this is really irritating. A good blog with interesting content, that’s what I need. Thank you for keeping this site, I’ll be visiting it. Do you do newsletters? Cant find it.

  3. Leone MuggeNo Gravatar says:

    Heya i’m for the primary time here. I found this board and I to find It really helpful & it helped me out a lot. I’m hoping to present something back and aid others like you aided me.

Leave a Reply