To enable my app running offline. During installation the service worker should:
- fetch a list of URLs from an async API
- reformat the response
- add all URLs in the response to the precache
For this task I use Googles Workbox in combination with Webpack.
The problem: While the service worker successfully caches all the Webpack assets (which tells me that the workbox basically does what it should), it does not wait for the async API call to cache the additional remote assets. They are simply ignored and neither cached nor ever fetched in the network.
Here is my service worker code:
importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.1.0/workbox-sw.js');
workbox.skipWaiting();
workbox.clientsClaim();
self.addEventListener('install', (event) => {
const precacheController = new workbox.precaching.PrecacheController();
const preInstallUrl = 'https://www.myapiurl/Assets';
event.waitUntil(fetch(preInstallUrl)
.then(response => response.json()
.then((Assets) => {
Object.keys(Assets.data.Assets).forEach((key) => {
precacheController.addToCacheList([Assets.data.Assets[key]]);
});
})));
});
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
workbox.routing.registerRoute(/^.*\.(jpg|JPG|gif|GIF|png|PNG|eot|woff(2)?|ttf|svg)$/, workbox.strategies.cacheFirst({ cacheName: 'image-cache', plugins: [new workbox.cacheableResponse.Plugin({ statuses: [0, 200] }), new workbox.expiration.Plugin({ maxEntries: 600 })] }), 'GET');
And this is my webpack configuration for the workbox:
new InjectManifest({
swDest: 'sw.js',
swSrc: './src/sw.js',
globPatterns: ['dist/*.{js,png,html,css,gif,GIF,PNG,JPG,jpeg,woff,woff2,ttf,svg,eot}'],
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
}),
It looks like you're creating your own
PrecacheController
instance and also using theprecacheAndRoute()
, which aren't actually intended to be used together (not super well explained in the docs, it's only mentioned in this one place).The problem is the helper methods on
workbox.precaching.*
actually create their ownPrecacheController
instance under the hood. Since you're creating your ownPrecacheController
instance and also callingworkbox.precaching.precacheAndRoute([...])
, you'll end up with twoPrecacheController
instances that aren't working together.From your code sample, it looks like you're creating a
PrecacheController
instance because you want to load your list of files to precache at runtime. That's fine, but if you're going to do that, there are a few things to be aware of:Your SW might not update Service worker updates are usually triggered when you call
navigator.serviceWorker.register()
and the browser detects that the service worker file has changed. That means if you change what/Assets
returns but the contents of your service worker files haven't change, you're service worker won't update. This is why most people hard-code their precache list in their service worker (since any changes to those files will trigger a new service worker installation).You'll have to manually add your own routes I mentioned before that
workbox.precaching.precacheAndRoute([...])
creates its ownPrecacheController
instance under the hood. It also adds its ownfetch
listener manually to respond to requests. That means if you're not usingprecacheAndRoute()
, you'll have to create your own router and define your own routes. Here are the docs on how to create routes: https://developers.google.com/web/tools/workbox/modules/workbox-routing.I realised my mistake. I hope this helps others as well. The problem was that I did not call
precacheController.install()
manually. While this function will be executed automatically it will not wait for additional precache files that are inserted asynchronously. This is why the function needs to be called after all the precaching happened. Here is the working code: