DreamingWell Logo

Speed web page rendering by combining JavaScript and CSS files, with PHP and Memcached

Posted by Travis Collins at March 8, 2009 9:29 PM

Web browsers have built-in logic that determines when linked resources such as JavaScript and CSS are downloaded. Because of the nature of JavaScript execution and CSS rendering, the browser may delay it's request for each resource, even when that's detrimental to speed at which the page is rendered. Tools such as Yahoo! YSLow for Firebug will help you determine when this is a problem. You can simply combine all of the JavaScript and/or CSS files into a single .js or .css file at the server. The browser will then need only download that single combined JavaScript or CSS file. This article will show you how to use Memcached, in PHP, to automatically combine files in an efficient method.

The following image shows resource downloads as individual javascript files. As you can see, the requests for individual .js files are delayed, even though that may not be necessary.

View image

By combining the sources of JavaScript files, the we can reduce the total time to load the JavaScript code, and thereby dramatically decrease the total rendering time. The following image shows the same time graph after combining the JavaScript files.

View image

While speed is our goal in production environments, the ability to debug efficiently is a goal in development environments. Combining JavaScript files into a single long file makes debugging extremely difficult. Therefore we must make some logical choices and implement a system that allows for both.

The following PHP code takes an array of relative path's to desired JavaScript files, and creates a simple Memcached buffer of the combined result, only in the production environment. An MD5 hash of the concatenated filenames is used as the unique key.

/* An array of JavaScript files to be included in our page */
$javaScriptFiles[] = "/v1/yui/yahoo-dom-event/yahoo-dom-event.js";
$javaScriptFiles[] = "/v1/yui/element/element-beta-min.js";
$javaScriptFiles[] = "/v1/yui/datasource/datasource-min.js";
$javaScriptFiles[] = "/v1/yui/datatable/datatable-min.js";
$javaScriptFiles[] = "/v1/yui/element/element-beta-min.js";
$javaScriptFiles[] = "/v1/yui/button/button-min.js";
$javaScriptFiles[] = "/v1/yui/connection/connection-min.js";
$javaScriptFiles[] = "/v1/yui/yahoo/yahoo-min.js";
$javaScriptFiles[] = "/v1/yui/event/event-min.js";
$javaScriptFiles[] = "/v1/yui/json/json-min.js";

/* Determine whether this is the DEV server. If so, just write the javascript includes out as normal. */
if($isDevServer) {
/* Include individual files on DEV server */
foreach($javaScriptFiles as $javaScriptFile) {

echo ''."\r\n";
}
} else {

/* create an MD5 hash of the file names */
$md5Str = "";
foreach($javaScriptFiles as $javaScriptFile) {
$md5Str .= $javaScriptFile;
}

$md5Value = md5($md5Str);

/* Connect to the Memcached server */
$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die('unable to memcache');

if($buffer = $memcache->get('javaScript'.$md5Value)) {
/* Already in buffer, nothing to do */
} else {

/* scripts arn't in memcached, so add them */
$buffer = "";
foreach($javaScriptFiles as $javaScriptFile) {
$buffer .= file_get_contents($_SERVER["DOCUMENT_ROOT"].$javaScriptFile);
}

$memcache->set('javaScript'.$md5Value,"".$buffer,0,1800);
}

/* Write out the javascript file name */
echo ''."\r\n";

} ?>

Now create a "/js/collected" directory on your server and create the following .htaccess file. This redirects all HTTP requests for anything below /js/collected to an index.php file that we will create.

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI} !index\.php$
RewriteRule .* ./index.php [L,QSA]
</IfModule>

Place the following code in an index.php file in the /js/collected directory. This file will be called, because of the .htaccess file. This file determines the Memcached key to use to find the requested javascript files and returns the combined output.

/* Return a Javascript mime header */
header('Content-type: application/javascript');

/* Determine the md5 file name from the URI requested */
$urlParts = parse_url($_SERVER['REQUEST_URI']);

/* Find the mime type based on file extension */
if(strrpos($urlParts["path"],".") === false)
$extension = "html";
else
$extension = substr($urlParts["path"],strrpos($urlParts["path"], ".") +1);

$mimeType = $mimeTypes[$extension];
$baseURI = "/v1/rest/";

$relativeURI = str_replace($baseURI,"",strtolower(str_replace(".".$extension,"",$urlParts["path"])));

$pathParts = split("/",$relativeURI);

$md5Value = $pathParts[4];

/* Connect to memcached */
$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die('unable to memcache');

/* Get the file contents out of memcached */
if($buffer = $memcache->get('javaScript'.$md5Value)) {

echo $buffer;

} else {

echo "/* Memcached did not return a javascript file for the requested file */";
}

 

Post a comment




Remember Me?

(you may use HTML tags for style)

Twitter Status

Travis flying into LAX today for #AdobeMax through Wednesday.

Last Seen in

Reston, Virginia

 

Copyright DreamingWell.com 2010