Updating service worker to Workbox 5 without bundler

Viewed 632

I'm running a pretty straight forward PWA with Workbox 3, primarily for caching and offline purpose. The web page is a forum were users can install a PWA. Planning a upgrade were the current workbox 3 gives some errors when testing and therefor I had to re-build the service worker. Thought of giving workbox 5 a chance.

The code below is what I'm testing with today and purpose should be (pretty straight forward):

  1. Give the user a chance to "install" the new service worker with a button (taken from: https://redfin.engineering/how-to-fix-the-refresh-button-when-using-service-workers-a8e27af6df68)

  2. Cache static assets but not html (except offline.html).

  3. Give navigation preload a chance to increase performance.

  4. Create a service worker based on Workbox 5 that is easy to update in the future (push messages).

service-worker.js:

// Load WB locally, skip preload of googleapis in header. (Version 5.1.3)
importScripts('/workbox/workbox-sw.js');

workbox.setConfig({
  modulePathPrefix: '/workbox/'
});

// How are we doing?
if (workbox) {
  console.log('Workbox loaded correctly');
} else {
  console.log('Workbox did not load, check log');
}
/*
// Debug on or off, off in production
workbox.setConfig({
  debug: true
});
*/

// A new SW is waiting, user clicks button that activates the new SW
addEventListener('message', e => {
  if (e.data === 'skipWaiting') {
  skipWaiting();
  clientsClaim();
  }
});

// Cache Offline page
const CACHE_NAME = 'offline-html';
const FALLBACK_HTML_URL = '/offline.html';

addEventListener('install', async (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then((cache) => cache.add(FALLBACK_HTML_URL))
  );
});

// Start navigation preload to speed things up a bit.
workbox.navigationPreload.enable();

const networkOnly = new workbox.strategies.NetworkOnly();
const navigationHandler = async (params) => {
  try {
    // Attempt a network request.
    return await networkOnly.handle(params);
  } catch (error) {
    // If it fails, return the cached HTML and log the error
    console.log(error);
    return caches.match(FALLBACK_HTML_URL, {
      cacheName: CACHE_NAME,
    });
  }
};

// Register this strategy to handle all navigations.
const navigationRoute = new workbox.routing.NavigationRoute(navigationHandler);
workbox.routing.registerRoute(navigationRoute);

// Cache static assets
const {StaleWhileRevalidate} = workbox.strategies;
const {CacheFirst} = workbox.strategies;
const {CacheableResponsePlugin} = workbox.cacheableResponse;

  workbox.routing.registerRoute(
    ({request}) => request.destination === 'script' || request.destination === 'style' || request.destination === 'font' || request.destination === 'manifest',
      new StaleWhileRevalidate({
    // Use a custom cache name.
    cacheName: 'static-cache2',
  })
  );

// Cache image files.
workbox.routing.registerRoute(
  ({request}) => request.destination === 'image',
  // Use the cache if it's available.
  new CacheFirst({
    // Use a custom cache name.
    cacheName: 'image-cache2',
    plugins: [
      new workbox.expiration.ExpirationPlugin({
        // Cache only 100 images.
        maxEntries: 100,
        // Cache for a maximum of two weeks.
        maxAgeSeconds: 14 * 24 * 60 * 60,
        purgeOnQuotaError: true,
      })
    ],
  })
);

// Try to cache opaque from CDN
workbox.routing.registerRoute(
  ({url}) => url.origin === 'https://cdn.mycdn.com' &&
             url.pathname.startsWith('/static/'),
  new CacheFirst({
    cacheName: 'cdn-cache',
    plugins: [
      new CacheableResponsePlugin({
        statuses: [0, 200],
      })
    ]
  })
);

Client JS:

function showRefreshUI(registration) {
  // TODO: Display a toast or refresh UI.

  // This demo creates and injects a button.

  var button = document.createElement('button');
  button.style.position = 'absolute';
  button.style.bottom = '24px';
  button.style.left = '24px';
  button.textContent = 'A new version of the web app is waiting, click here to install';

  button.addEventListener('click', function() {
    if (!registration.waiting) {
      // Just to ensure registration.waiting is available before
      // calling postMessage()
      return;
    }

    button.disabled = true;

    registration.waiting.postMessage('skipWaiting');
  });

  document.body.appendChild(button);
};

function onNewServiceWorker(registration, callback) {
  if (registration.waiting) {
    // SW is waiting to activate. Can occur if multiple clients open and
    // one of the clients is refreshed.
    return callback();
  }

  function listenInstalledStateChange() {
    registration.installing.addEventListener('statechange', function(event) {
      if (event.target.state === 'installed') {
        // A new service worker is available, inform the user
        callback();
      }
    });
  };

  if (registration.installing) {
    return listenInstalledStateChange();
  }

  // We are currently controlled so a new SW may be found...
  // Add a listener in case a new SW is found,
  registration.addEventListener('updatefound', listenInstalledStateChange);
}

window.addEventListener('load', function() {
  var refreshing;
  // When the user asks to refresh the UI, we'll need to reload the window
  navigator.serviceWorker.addEventListener('controllerchange', function(event) {
    if (refreshing) return; // prevent infinite refresh loop when you use "Update on Reload"
    refreshing = true;
    console.log('Controller loaded');
    window.location.reload();
  });

  navigator.serviceWorker.register('/service-worker.js')
  .then(function (registration) {
      // Track updates to the Service Worker.
    if (!navigator.serviceWorker.controller) {
      // The window client isn't currently controlled so it's a new service
      // worker that will activate immediately
      return;
    }
    registration.update();

    onNewServiceWorker(registration, function() {
      showRefreshUI(registration);
    });
  });
});

This code works on my dev server, my questions to SO:s workbox gurus are if there are any pitfalls with it and maybe someone could share suggestions on how to make it better?

Service workers and Workbox are complex and my main concern is that I've built the service worker so it does not optimize against workbox and maybe uses wrong or bad code/functions/order of functions.

0 Answers
Related