Search code examples
service-workersw-precacheservice-worker-events

How to precache assets with names that change at every deploy using service workers?


We're working with Workbox to precache some assets of the "next" page, but our assets URL contains the date and the hash of the deploy. E.g. "/css/2019-05-15/f00ba5/home.css".

The problem we're facing is how to pass the changing portion of URL ("2019-05-15/f00ba5") to the service worker, and make Workbox to precache the list of assets.

We're using Workbox and we tried workbox-window to send a message from the web page to the SW and until here... it worked!

But now that the SW knows the hash, we don't know how to take advantage of it, because if we call the workbox.precaching.precacheAndRoute() method from inside the event listener, the import of the workbox.precaching module fails somehow.

In the page.html

<script type="module">
    import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/4.0.0/workbox-window.prod.mjs';

    if ('serviceWorker' in navigator) {
        window.addEventListener('load', () => {
        const wb = new Workbox('/service-worker.js');
        wb.messageSW({type: 'hash', payload: "12345"});
        wb.register();
    })
}
</script>

In the service-worker.js

const file1 = "/css/{hash}/home.css";
const file2 = "/js/{hash}/home.js";
const file3 = "/img/{hash}/hero.jpg";

addEventListener('message', (event) => {
    if (event.data.type === 'hash') {
        // TODO: replace {hash} with event.data.payload in file1, 2, 3
        const precacheManifest = [ file1, file2, file3 ];
        workbox.precaching.precacheAndRoute(precacheManifest);
    }
});

I get the error

workbox-sw.js:1 Unable to import module 'workbox-precaching' from 'https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-precaching.prod.js'.
loadModule @ workbox-sw.js:1
get @ workbox-sw.js:1
(anonymous) @ service-worker.js:39
workbox-sw.js:1 Uncaught DOMException: Failed to execute 'importScripts' on 'WorkerGlobalScope': The script at 'https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-precaching.prod.js' failed to load.
at Object.loadModule (https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js:1:954)
at Object.get (https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js:1:727)
at https://brioni.f31.lcl/service-worker.js:39:13
loadModule @ workbox-sw.js:1
get @ workbox-sw.js:1
(anonymous) @ service-worker.js:39
framework.js:1441 STORE::LazyLoad.Init

I expect that to work and precache the files in the precacheManifest array.


Solution

  • The specific error that you're running into is due to a restriction around callimg importScripts() after service worker installation has taken place, which happens under the hood via the workbox-sw library. It's described in more detail in the Workbox documentation, along with alternative ways of accomplishing the same thing.

    That being said, using workbox-precaching "dynamically" isn't a good choice. As described in the workbox-precaching documentation, you should use a build tool, like workbox-cli or workbox-webpack-plugin, to generate this list and automatically insert the precache manifest into your service worker file. Changes to your service worker file due to an updated precache manifest will trigger the service worker update lifecycle events, and that will, in turn, make sure that your precache assets are kept up to date.

    If you don't want to use a build tool and generate a precache manifest in advance, then setting up routing rules and using a runtime caching strategy is more appropriate than precaching. You can use workbox-window to pass in a list of URLs that you want cached at runtime.