PWA - Offline Cache

Implementing Pre-Cache

In sw.js file, you can precache the resources.

// listening to install event
self.addEventListener("install", function (event) {
    cosnole.log("[SW] Installing Service worker ...", event);
    
    // 'static' is the name that we given to store our cached files in 'cache storage' in the browser
    event.waitUntil(caches.open("static").then(function (cache) {
        console.log("[SW] Precaching App Shell ...");
        cache.add("/"); // cache the root
        cache.add("/index.html"); // cache the index file
        cache.add("/src/js/app.js"); // cache the app js file
    }));
});

// listening to activate event
self.addEventListener("activate", function (event) {
    cosnole.log("[SW: ] Activating Service worker ...", event);
    return self.clients.claim(); // ensure the SW is loaded & activated correctly
});

// listening to fetch event
self.addEventListener("fetch", function (event) {
    // listening to all fetch events.
    // 'caches.match' looks at the cache storage first, if not found then web server
    event.respondWith(caches.match(event.request).then(function (response) {
        if (response) {
            // return a response from cache storage
            return response;
        }
        else {
            // return a server response
            return fetch(event.request);
        }
    }));
});

addAll

You can add all the resources that you wanted to precache.

self.addEventListener("install", function (event) {
    event.waitUntil(caches.open("static").then(function (cache) {
        cache.addAll([
            "/",
            "/index.html",
            "/src/js/app.js"
        ]);
    }));
});

Implementing Dynamic Cache

When user browser the page while they have internet, the page they visted will dynamically cached the page resources.

In sw.js file

self.addEventListener("fetch", function (event) {
    event.respondWith(caches.match(event.request).then(function (response) {
        if (response) {
            return response;
        }
        else {
            // this is where you implement the dynamic caching
            return fetch(event.request).then(function (res) {
                // you'll need to place a return here as well so that it return to the html
                return caches.open("dynamic").then(function (cache) {
                    // Note: response only used once, here we need to clone it then return it.
                    cache.put(event.request.url, res.clone()); 
                    return res;
                }).catch(function () {
                    // without this catch, you might get a lot error. Just leave it empty.
                });
            });
        }
    }));
});

Add Cache Version

Cache storage usually for images, js, css but not really for json file that change so frequently from the server.

Your pre-cached files only get updated if the service worker file get updated. Let say, you updated your feed.js, the pre-cached feed.js won’t get updated because the service worker file didn’t get updated. And using the different cache won’t mess up the old cache that is being used in the application. To fix this, you must use cache version. Implement it is pretty simple. Just add a -v2 to your service worker file to trigger the update.

self.addEventListener("install", function (event) {
    event.waitUntil(caches.open("static-v2").then(function (cache) {
        cache.addAll([
            "/",
            "/index.html",
            "/src/js/app.js",
            "/src/js/feed.js" // change was made to this file
        ]);
    }));
});

Cache Version Clean-up

Now, by adding the version, your cache storage should cached both static and static-v2. However, when you reload the page this won’t get updated as your service worker is detecting two caches, hence, it uses the static resources instead of static-v2. To fix this, in your activate event code.

// This is where you update the cache version
var CACHE_STATIC_NAME = "static-v3";
var CACHE_DYNAMIC_NAME = "dynamic-v2";

self.addEventListener("activate", function (event) {
    // Wait for the clean up to be done before activate
    event.waitUntil(caches.key().then(function (keylist) {
        return promise.all(keylist.map(function (key) {
            if (key !== CACHE_STATIC_NAME && key !== CACHE_DYNAMIC_NAME) {
                return caches.delete(key);
            }
        }));
    }));

    // Ensure service worker loaded & activated correctly
    return event.clients.claim();
});