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.
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.