/appcache-sw-polyfill

Initial commit

Primary LanguageJavaScript

Note: this has been superseded by scirra-offline-sw. Please use that instead!

appcache-sw-polyfill

For background, see the blog post Service Workers are a pain in the ass.

Chrome and Firefox are phasing out AppCache in favour of Service Worker. In the absence of any convincing polyfill to do the same thing AppCache does, I've tried to write my own. It didn't go too well. Note the code here is not yet production ready and has some bugs that need fixing before it will be useful.

Despite the name this isn't really a polyfill, it intentionally works differently to AppCache, e.g. using a JSON format data file instead of that unusual plaintext format AppCache uses.

Goals

This is basically an attempt to reproduce what AppCache does but with Service Worker. This includes:

  • a JSON file of the static resources to cache (basically the AppCache manifest file but in JSON format)
  • implicit caching of the index page
  • always load from cache first (so offline support works)
  • check for update in background on page load, updates identified by incrementing "version" field in the JSON file
  • updates are written to new (separate) cache, not used until next page load
  • avoid mixing resources from different cache versions in the same page load

This is surprisingly difficult to achieve with Service Worker.

How to use it

Copy sw.js to the same folder as your index page, and install the service worker from a script like this:

navigator.serviceWorker.register("sw.js", { scope: "./" });

(You'll want to check navigator.serviceWorker is supported first, or use a try-catch around it, for browsers that don't support Service Worker.)

offline.js format

This is basically a JSON file with a list of static resources to cache. It's basically the AppCache manifest but in JSON format and looks like this:

{
	"version": 1,
	"files": [
		"info.txt",
		"image.png"
	]
}

To issue an update, update the files as normal then increment the "version" field (which can be any arbitrary number, e.g. a timestamp).

Note the file is called offline.js instead of offline.json, since almost all servers have a MIME type set for .js, but not all have one set for .json. So if we named the file .json, in some cases the server will return 404 Not Found.

Testing

A minimal test case is provided in the test-files folder. Basically run a local server to display the v1 files. This includes a HTML file, a text file which is AJAX requested and contents shown, and a PNG image that is displayed. Load the index page in a browser. Note you'll want to either use a new Incognito window or clear browser data and stop service workers between every test, to be able to check how it works from a fresh install. Keep an eye on the console (loads of diagnostic messages are printed in the current version). Reload the page and you should see it load from cache. Paste the contents of the v2 folder over the files; this represents an update being made available. Reload the page again. At this point it is expected that the v1 files display again, but the console should log that a new version was downloaded and is ready (note this is similar to how AppCache works). Press reload one more time and it should start showing the v2 files. Except it doesn't work right now.

Bugs

  • Chrome: does not support cache control options, so after the upgrade you can get stale files. In the given test case, you get a mix of v1 and v2 files. This is a showstopper, because mixing versions basically results in a corrupt page for non-trivial web apps. See: crbug.com/453190
  • Firefox: does not allow JS to run outside of events. This appears to cause the background update check to be terminated so it never updates. The workaround is to implement some kind of postMessage dance with a real client, but is complicated by the fact there is no client available when the main page is requested which is when we want to do an update check, and that the extendable message event that this needs hasn't landed in Chrome Stable yet.
  • During an update, there is a race between the update check and the first resource being fetched, also a consequence of there being no client available when the main page is requested. If the update check completes first, it is possible the main page will be returned from a different version cache than the rest of the page's fetches. This is very hackily "solved" by delaying the update check by 1 second!
  • Cache updates are probably not atomic. Although the update waits for all requests to complete before opening the cache, the put() calls themselves are not atomic so could result in partial caches being written. This seems unlikely but is technically a problem, and I don't know how to fix it.
  • I think it is possible for a cache to be deleted while it is in use by another window.
  • probably some other issues I haven't found yet

In other words, this doesn't work properly in any browser yet.

License

MIT