Supercharging Javascript, Part 4: Caching on the Server

Previous: Minify Everything

So far we've developed a reasonably decent dynamic Javascript packing script. The problem is that the packing is done every request so we should do store the result. To do this our PHP application will need to be able to write the result to the filesystem. Typically for security reasons PHP scripts have no write permissions anyway except the temp directory. There are several issues that must be addressed when implementing this:

Caching in the temporary directory is inherently insecure. Other users can view and possibly modify your cache, presenting a big potential security hole. This is particularly an issue if you're using shared hosting where other sites will have the same access to your files that you do, which is already a well-known issue with PHP session security on shared hosting.

If you use shared hosting--or any environment where security from other users is a potential issue--I would advise you to err on the side of caution and not use this part of the script.

My preferred method is to use filemtime(). Basically you compare the last modified time of the most recently modified Javascript file to the last modified time of the cache file. If it's newer, the cache needs to be rebuilt.

// These no longer even need to be under the document root
define('SCRIPT_DIR', $_SERVER['DOCUMENT_ROOT'] . '/script/');

$bundles = array(
  'site' => array(

$site = $_GET['site'];
if (!isset($bundles[$site])) {
  error_log("javascript.php: Unknown bundle '$site' requested");

// determine if we need to rebuild the cache
$cache_file = CACHE_DIR . $site . '.js';
// Error is suppressed here because otherwise it'll send an error to the user and this is
// a valid case before the cache has initially been generated
$cache_mtime = @filemtime($cache_file);
$build_cache = false;
if ($cache_mtime === false) {
  $build_cache = true;
} else {
  $mtime = 0;
  foreach ($bundles[$site] as $file) {
    $file_mtime = filemtime($file);
    if ($file_mtime !== false && $file_mtime > $mtime) {
      $mtime = $file_mtime;
  if ($mtime > $cache_mtime) {
    $build_cache = true;

// build the cache if required
header('Content-Type: text/javascript');
if ($build_cache) {
  require 'jsmin-1.1.1.php';
  $scripts = '';
  foreach ($bundles[$site] as $file) {
    $contents = @file_get_contents(SCRIPT_DIR . $file);
    if ($str === false) {
      error_log("javascript.php: Error reading file '$file'");
    } else {
      $scripts .= $contents;
  $min_content = JSMin::minify($scripts);
  file_put_contents($cache_file, $min_content);
  echo $min_content;
} else {

Now we're getting somewhere. But we can do even better than this.

Next: Caching on the Client


Kieran Hall said...

This is a very sensible series of articles you're putting together. Bravo.

Post a Comment