Reputation: 5836
The application cache is now deprecated and browsers like Chrome are removing support.
We have an application that can work 100% offline while storing data in the indexeddb and syncing later when the user is back online. We need to transition this site from using application cache to service worker. We will be using Workbox for our service worker.
There are three main sections of our cache manifest that we must covert.
CACHE Section This is a list of assets to precache. This is probably the most straight forward to transition as we are using workbox to precache these files.
NETWORK Section We are using * here (probably most common) so that's probably not going to be an issue.
FALLBACK Section We have quite a few entries in the fallback section. Basically they are redirect to the login page and are there in case someone refreshes the page offline.
Example:
FALLBACK:
/search /login
/customer-edit /login
/foo-bar-baz /login
...
My question:
Is there either 1) a general guide to converting application cache/cache manifest to service workers or 2) some specific guidance for converting the FALLBACK section to the equivalent functionality in a service worker.
Google and Duck Duck Go have not been extremely helpful.
There are existing projects to upgrade the app cache to service workers but most appear very beta, example from Google Chrome Labs: github.com/GoogleChromeLabs/sw-appcache-behavior
Upvotes: 1
Views: 1981
Reputation: 5836
This is the solution I came up with using Google's workbox.
Sidenote: Workbox appears to have a has a solution for most common service worker use cases and has a very flexible distribution model that makes it quite easy to work with, either in a vanilla js environment or with your framework of choice.
We ended up converting our server side AppCache (cach manifest code) to generate a service worker. (How to precaching an url list coming from a json file?)
Depending on your server side language you're code will vary, but this is the end product that worked for us:
service-worker.js (generated server side)
const productVersion = "3.01";
importScripts('/assets/workbox/workbox-sw.js');
workbox.setConfig({
modulePathPrefix: '/assets/workbox/'
});
const { precacheAndRoute, createHandlerBoundToURL } = workbox.precaching;
const { NavigationRoute, registerRoute, setCatchHandler } = workbox.routing;
precacheAndRoute([
// cache index html
{url: '/', revision: '3.01' },
// web workers
{url: '/assets/some-worker.js?ver=3.01', revision: '' },
{url: '/assets/other-worker.js?ver=3.01', revision: '' },
// other js files
{url: '/assets/shared-function.js', revision: '3.01' },
// ... removed for brevity
// css
{url: '/assets/site.css', revision: '3.01' },
{url: '/assets/fonts/fonts.css', revision: '3.01' },
// svg's
{url: '/assets/images/icon.svg', revision: '3.01' },
{url: '/assets/images/icon-2.svg', revision: '3.01' },
// png's
{url: '/assets/images/img-1.png', revision: '3.01' },
{url: '/assets/images/favicon/apple-touch-icon-114x114.png', revision: '3.01' },
// ...
// ...
// fonts
{url: '/assets/fonts/lato-bla-webfont.eot', revision: '3.01' },
{url: '/assets/fonts/lato-bla-webfont.ttf', revision: '3.01' },
// sounds
{url: '/assets/sounds/keypress.ogg', revision: '3.01' },
{url: '/assets/sounds/sale.ogg', revision: '3.01' },
]);
// Routing for SPA
// This assumes DEFAULT_URL has been precached.
const DEFAULT_URL = '/';
const handler = createHandlerBoundToURL(DEFAULT_URL);
const navigationRoute = new NavigationRoute(handler, {
denylist: [
new RegExp('/ping'),
new RegExp('/upgrade'),
new RegExp('/cache.manifest'),
],
});
registerRoute(navigationRoute);
// This allows the main window to signal the service worker that
// it should go ahead and install if it's waiting.
addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
skipWaiting();
}
});
addEventListener
code. We actually call that from an upgrade page to get a smooth upgrade process. It goes something like this:A) Upgrade script detects new version is available (many ways to do this, api call polling, etc)
B) If user accepts or timer expires redirect user to an upgrade
page. This step is vital b/c you can't update a service worker with an app still running. So navigate to the upgrade page, wait for the service worker to get installed then tell it to skip waiting and the redirect to main (login) screen.
C) User is happily running new version of app.
Upgrade page code: (this is a good page to show an "updating" UI of some type)
<script type="module">
import { Workbox } from '/assets/workbox/workbox-window.prod.mjs';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/serviceworker');
// This code exists b/c a service worker can't update with just a refresh/reload in the
// browser. This is b/c on a reload, the old and new page exist simultaneously and the old MUST
// unload before the new service worker can automatically assume control. Also if multiple pages
// are open, this blocks the service worker from taking control (multiple pages should not an issue with this app).
// This code activates a waiting service worker and _then_ redirects back to the app.
// Add an event listener to detect when the registered
// service worker has installed but is waiting to activate.
wb.addEventListener('waiting', (event) => {
// Set up a listener that will reload the page as soon as the previously waiting
// service worker has taken control.
wb.addEventListener('controlling', (event) => {
window.location.replace('/login');
});
// Send a message telling the service worker to skip waiting.
// This will trigger the `controlling` event handler above.
wb.messageSW({type: 'SKIP_WAITING' });
});
wb.register();
}
// set a timeout in case the service worker has already installed.
setTimeout(function () {
window.location.replace('/login');
}, 30000);
</script>
Main page (index.html, etc) (Handles if the user is coming to the app with a server worker ready to activate, so it needs a refresh to get the right assets/code loaded)
<script type="module">
import { Workbox } from '/assets/workbox/workbox-window.prod.mjs';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/serviceworker');
wb.addEventListener('activated', (event) => {
// `event.isUpdate` will be true if another version of the service
// worker was controlling the page when this version was registered.
if (!event.isUpdate) {
// service worker was updated and activated for the first time.
// If your service worker is configured to precache assets, those
// assets should all be available now.
// this will only happen if the browser was closed when a new version was made available
// and it will only happen once per service worker install.
// Reload to so all libs are correct version.
window.location.reload(true);
}
});
wb.register();
}
</script>
Upvotes: 3