A better application cache manifest

24 February 2011

I've recently been messing around with offline application caching.  At a simple static level it is pretty basic, but once you start thinking about what might actually happen in real life it gets a lot more ugly.

The following sources got me started with offline caching in the first place.  If you need more background to what application cache is, you should read these first.

My main problem with it is that every time I make a change to my files, I have to go into my cache.manifest file and update a comment somewhere.  Why? Because the application cache is checked for updates first.  If there are no updates, it just stops.  It doesn't check any of the underlying files for updates.

That's efficient in terms of client-server communication by reducing the amount of calls to check whether cached versions can be used or need updated, but its also really dumb, and a nuisance to work with.  You start relying on people to remember on these little jobs, and that introduces a risk of human error.  It's not an acceptable risk because it is an avoidable risk.

Luckily we can use server-side technology to generate our cache manifests and grab the file update timestamp.  Here's what I've learned thus far:

  1. the manifest attribute on the html element doesn't have to point to a *.manifest file.  It can point to a manifest.php file (for example).
  2. in the dynamic page, set your header to serve up the right mime type.  In PHP, this is <? php header("Content-Type: text/cache-manifest;charset=utf-8"); ?>
  3. find the most recently updated file date and echo it out in a comment. If there's not been a change, the comment will remain the same in the generated code as it is in the cached version.   In my example below I'm statically setting which files I want to be in the cache. Jonathan Stark shows a way of going through the file system to cache all files if you'd rather.  You'd just add the timestamp bits in where required.

<?php
	header("Content-Type: text/cache-manifest;charset=utf-8");
	$cache_files = array(
					"testhtml5.htm",
					"testinvisdyn.php",
					"testhtml5.css",
					"testhtml5.js",
					"jquery.js",
					"json.js"
					);
	$fallback_files = array(
						"/" => "fallback/testhtml5fall.htm"
					   );
?>
CACHE MANIFEST
NETWORK:
*
CACHE:
<?php
	$cache_mod = 0;
	foreach($cache_files as $f){
		echo "$f \n";
		$last_mod = filemtime($f);
		if($last_mod > $cache_mod){
			$cache_mod = $last_mod;
		}
	}
	unset($f);
	
	echo "# $cache_mod \n";
?>

FALLBACK:
<?php
	$cache_mod = 0;
	foreach($fallback_files as $d => $f){
		echo "$d $f \n";
		$last_mod = filemtime($f);
		if($last_mod > $cache_mod){
			$cache_mod = $last_mod;
		}
	}
	unset($f);
	unset($d);
	
	echo "# $cache_mod \n";
?>