Tommy Ku's Method Stub

Reading and thinking

AppCache revisited

Posted on 2017.06.10

Two years ago, I wrote about the use of Application Cache (AppCache) in the post “Adding HTML5 application cache to speed up your web app in 5 minutes”.

The AppCache feature is deprecated as a web standard, so newer version of browsers may not support it.

In comparison to its alternative Service Worker, AppCache is more difficult to use because the web server has to serve the manifest files in text/cache-manifest type while service worker is an ordinary JavaScript file. No tinkering with the web server required except for the HTTPS. Service worker has a shortcoming of unable to run on browser with JavaScript disabled while AppCache doesn’t need JavaScript to run.

The title of this post is “AppCache revisited”, but I am not going to implement it again. Instead, I am migrating an old app that has been sitting around since 2015 from AppCache to Service Worker.

Removing AppCache from an app

Removing manifest MIME type

The web server was set up to serve *.manifest files with MIME type text/cache-manifest. It is no longer needed as we are removing AppCache.

# .htaccess
# ...
# Remove this line from .htaccess file
AddType text/cache-manifest .manifest
# ...

Removing references to manifest files from source

Remove the manifest attribute from <html> tag.

<!DOCTYPE html>
<html manifest="/manifest/app.cache.manifest">
<!-- ... -->

The app.cache.manifest file should also be removed from the project, note that the file looks like this at the moment.

(yes, this is the same app from that old AppCache post)

CACHE MANIFEST

CACHE:
/js/app.js
/js/angular/angular.min.js
/js/angular-resource/angular-resource.min.js
/js/angular-sanitize/angular-sanitize.min.js
/css/base.css
/css/animate.css

NETWORK:
*

Later when we migrate to service worker, the list of files to be cached is useful because like AppCache, you can specifiy exactly what to cache in service worker.

Now the app is free of the old deprecated AppCache. To achieve the same caching effect, we will have to use add service worker to the app.

Adding service worker to an app

Requirements of service worker

HTTPS is required to run service worker because service worker can serve modified responses to network request, increasing the risk of man in the middle attack. According to an MDN article, Firefox disables service worker API while in private browsing mode.

To enable HTTPS on your server, Let’s Encrypt is a good source of free certificate. If your app is hosted on GitHub Pages, it is already being served over HTTPS (and it’s free!). Cloudflare also provides HTTPS at free-tier if you are being lazy. (though the connection between Cloudflare and your server may not be private, see this article for details).

The service-worker.js

If you build your app with build tools such as webpack or gulp, Workbox is a great tool for integrating service worker into your app. The app I am maintaining wasn’t built with any build tool but plain CSS and JavaScript. Workbox provides options to manually select what kinds of file to cache.

To show you how to add service worker to a project, I decided to write one up manually.

// service-worker.js
var cacheName, filesToCache;

cacheName = 'link-201706101730';

filesToCache = [
  '/',
  'index.html'
  'js/app.js',
  'js/angular/angular.min.js',
  'js/angular-resource/angular-resource.min.js',
  'js/angular-sanitize/angular-sanitize.min.js',
  'css/base.css',
  'css/animate.css',
];

At the top of service-worker.js, the cacheName and filesToCache are specified. Caching each asset with individual cache name is possible and preferred. To keep the example simple, we use a single cache name for all files here.

// service-worker.js
var cacheName, filesToCache;

cacheName = 'link-201706101730';

filesToCache = [
  '/',
  'index.html'
  'js/app.js',
  'js/angular/angular.min.js',
  'js/angular-resource/angular-resource.min.js',
  'js/angular-sanitize/angular-sanitize.min.js',
  'css/base.css',
  'css/animate.css',
];

self.addEventListener('install', function(e) {
  // [ServiceWorker] Install
  return e.waitUntil(caches.open(cacheName).then(function(cache) {
    console.log('[ServiceWorker] Caching app shell');
    return cache.addAll(filesToCache);
  }));
});

self.addEventListener('fetch', function(e) {
  console.log('[ServiceWorker] Fetch', e.request.url);
  return e.respondWith(
    fetch(e.request).catch(function() {
      return caches.match(e.request);
    })
  );
});

self.addEventListener('activate', function(e) {
  console.log('[ServiceWorker] Activate');
  return e.waitUntil(caches.keys().then(function(keyList) {
    return Promise.all(keyList.map(function(key) {
      if (key !== cacheName) {
        console.log('[ServiceWorker] Removing old cache', key);
        return caches["delete"](key);
      }
    }));
  }));
});

This service worker set up will install and activate itself to cache new assets. The fetch event uses network first instead of cache first approach when dealing with asset request, meaning that it always try to fetch through the network before falling back to using caches.

The code in this service worker example was heavily influenced by this article.

Installing and updating service worker

Service worker doesn’t just work on its own, your app has to explicitly register the service worker for it to work.

To achieve this, add the following code to the end of your app’s JavaScript file.

// app.js
// ...

if(typeof navigator['serviceWorker'] != 'undefined') {
  window.addEventListener('load', ()=> {
    navigator.serviceWorker
      .register('/service-worker.js')
      .then(()=> {
        console.log('Service Worker Registered')
      });
  });
};

Lastly, deploy the app as usual. The app should load fast and work offline like how it was with AppCache. Of course, background sync and push notifications are also available thanks to service worker, but they are out of the scope of this post.

Closing thoughts

One important thing to note is that despite deprecated, AppCache is still supported by most of the major browsers (as of June 2017), while service worker is either under development or under consideration on Edge and Safari. The momentum of service worker is strong, with notable sites such as Aliexpress, Flipboard, Financial Timesm and more already using service worker to provide offline usability.

Browser support for AppCache (source: caniuse.com)
Browser support for service worker (source: caniuse.com)

Of course, service worker isn’s just about caching, it enables for app-link features such as push notification, background sync and install to homescreen that will continue to fill the gap between native apps and web apps without the need for software like Cordova and PhoneGap.

Looking back, not only AppCache, many browser APIs have been developed to empower the web. I am excited to see as the web continues to evolve, what will emerge to bridge the gap between native apps and web apps (Firefox OS was a bummer), or even better, how web can surpass native app in portability, availability and user experience.