Posted by Sir Four at 3:30pm Nov 21 '09
You must sign in to send Sir Four a message
You must sign in to send Sir Four a message
Stitch Together Multiple Javascript/CSS Files Server-Side
Level: Advanced
A key way to speed up your web site is to reduce the number of files each page includes. But what if your page needs to use multiple css and/or javascript files? It's not unsual to see this in a page's HTML code:
<link rel="stylesheet" type="text/css" href="/css/main.css">
<link rel="stylesheet" type="text/css" href="/css/menu.css">
<link rel="stylesheet" type="text/css" href="/css/overlay.css">
<link rel="stylesheet" type="text/css" href="/css/features.css">
<script src="/js/main.js" type="text/javascript"></script>
<script src="/js/polls.js" type="text/javascript"></script>
<script src="/js/menu.js" type="text/javascript"></script>
This code above will result in seven requests to your server every time the page is loaded, just to grab each CSS and javascript file. Each request adds precious milliseconds to your page's load time and creates additional work for your server as well.*
One way to solve this problem is to stitch the files together on the server before sending them to the browser. You can combine the four CSS files into one file. Likewise, the three javascript files can become one.
Here is an example PHP script that will combine the javascript files:
jscacher.php:
<?php
// set the Content-Type header
header('Content-Type: text/javascript');
// 'files' is a dash-separated list of javascript filenames
$filename = $_GET['files'];
if (!$filename)
exit();
// separate the filenames into an array
$files = explode('-', $filename, 15);
// open a file named with the supplied filename list, in subdirectory 'cache' for writing
$fw = @fopen('cache/'. $filename, 'w');
// loop through each filename
foreach ($files AS $file) {
$fcontents = null;
// open this file (located in current directory) for reading, and read contents
if ($fr = fopen($file, 'r')) {
$fcontents = fread($fr, filesize($file)) ."\n";
fclose($fr);
}
if ($fcontents) {
// write contents to file we opened for writing
fwrite($fw, $fcontents, strlen($fcontents));
// also echo out the contents
echo $fcontents;
}
}
fclose($fw);
?>
// set the Content-Type header
header('Content-Type: text/javascript');
// 'files' is a dash-separated list of javascript filenames
$filename = $_GET['files'];
if (!$filename)
exit();
// separate the filenames into an array
$files = explode('-', $filename, 15);
// open a file named with the supplied filename list, in subdirectory 'cache' for writing
$fw = @fopen('cache/'. $filename, 'w');
// loop through each filename
foreach ($files AS $file) {
$fcontents = null;
// open this file (located in current directory) for reading, and read contents
if ($fr = fopen($file, 'r')) {
$fcontents = fread($fr, filesize($file)) ."\n";
fclose($fr);
}
if ($fcontents) {
// write contents to file we opened for writing
fwrite($fw, $fcontents, strlen($fcontents));
// also echo out the contents
echo $fcontents;
}
}
fclose($fw);
?>
What this script does:
Takes a dash-separated list of filenames, loops through each, reads the contents of each, writes those contents to a file in subdirectory "cache" and echos them out to the browser.
Say you have all your javascript files in a directory called "/js". You would put the above script in that directory (save it as "jscacher.php"), and create a subdirectory "cache" under "/js".
Then add this to your .htaccess file (assuming you are using the Apache web server):
RewriteCond %{REQUEST_URI} ^/js/cache
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^js/cache/(.*)$ /js/jscacher.php?files=$1 [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^js/cache/(.*)$ /js/jscacher.php?files=$1 [L]
What this does:
The first line says, "if the request is for something in the '/js/cache' directory..."
The second line says, "...and the requested file does not exist..."
The third line says, "route the request to our jscacher.php script and pass the requested filename as a parameter called 'files'"
Then here is where the cool part comes...
Instead of coding your HTML pages to include the three separate javascript files listed up above, you would include one javascript file like so:
<script src="/js/cache/main.js-polls.js-menu.js" type="text/javascript"></script>
The first time a user hits your page, what will happen is: the .htaccess conditions will be triggered, since the request is for something in the "/js/cache" directory and the file named "main.js-polls.js-menu.js" does not exist there (yet). So the request will be routed to our jscacher.php script, which will read the contents of main.js, polls.js, and menu.js and write them out to a file named "main.js-polls.js-menu.js" in the "/js/cache" directory. Apache will serve all subsequent requests for "main.js-polls.js-menu.js" straight from that file.
Congratulations, you've now swapped three separate file requests for one single request...very painlessly! You can now use this in any HTML page to request any combination of javascript files.
You could do the exact same technique to turn the four separate CSS files into one single CSS file. In total you would take seven requests down to two requests, improving the user's experience and reducing the load on your server.
Points to Consider
We used a dash to separate file names. What if your javascript files contain dashes? You could consider using a different character as a separator.
We did not perform any validation of the filenames in our jscacher.php script. This is dangerous, because hackers could try to expose hidden files. Always validate!
*On servers that are not dealing with a high load, the additional work of handling these extra requests is not noticed. But under high load conditions, these extra requests can easily tie up enough resources to cause a slow-down in performance.
added on 3:46pm Nov 21 '09:
Another point to consider:If you make changes to your javascript or CSS files, these changes will not be reflected in your cached copies. You can handle this several ways, by either renaming your files, manually clearing the "cache" subdirectory to force a refresh, or using a cron job to periodically clear the "cache" subdirectory.