const SW_VERSION = '1.2.3' // update it when the app changes: invalidates `resourcesToCache` const resourcesCacheKey = `cache-v${SW_VERSION}` const microfrontendCacheKey = `mfe` // not sure versioning is needed const resourcesToCache = [ '/', 'site.webmanifest', 'css/app.css', 'js/app.js', // Standard icons 'android-chrome-192x192.png', 'android-chrome-512x512.png', 'android-chrome-maskable-192x192.png', 'android-chrome-maskable-512x512.png', 'favicon.svg', // Apple icons 'apple-touch-icon.png', 'safari-pinned-tab.svg', // Old stuff 'favicon-32x32.png', 'favicon-16x16.png', // whatever else… ] const createCaches = () => Promise.all([ caches.open(resourcesCacheKey).then(cache => cache.addAll(resourcesToCache)), ]) const flushOldCaches = () => caches.keys().then(keys => Promise.all( keys .filter(key => key != resourcesCacheKey) .map(key => caches.delete(key).then(() => { // App has been updated if (key.startsWith('cache-v')) { notifyClients({ appUpdate: true }) } })) )) const removeCachedMicrofrontend = name => caches.keys().then(keys => Promise.all( keys .filter(key => key !== microfrontendCacheKey) .filter(key => key.url.includes(`/mfe/${name}/`)) .map(key => caches.delete(key)) )) const putToCache = (cacheKey, request, response) => caches.open(cacheKey).then(cache => cache.put(request, response)) const respondWith = (e, url) => e.respondWith(caches.match(url, { ignoreSearch: true }) .then(response => response || fetch(e.request).then(response => response)) ) const notifyClients = data => self.clients.matchAll().then(clients => clients.forEach(client => client.postMessage(data)) ) self.addEventListener('install', e => e.waitUntil(createCaches().then(() => self.skipWaiting())) ) self.addEventListener('activate', e => e.waitUntil(flushOldCaches().then(() => self.clients.claim())) ) self.addEventListener('fetch', e => { // Assuming microfrontend hosted on /mfe/{mfeName}/1.0.0/index.js let url = new URL(e.request.url) if (url.pathname.startsWith('/mfe/')) { caches.match(url).then(response => { // Respond with exact same version already cached if (response != undefined) { return respondWith(e, e.request) } // Get microfrontend name and version from path const [name, version] = url.pathname.split('/mfe/') // Check for an already cached version of the wanted micro-frontend caches.open(microfrontendCacheKey) .then(cache => cache.keys().then(keys => { const cachedMicrofrontend = keys.find(key => key.url.includes(`/${name}/`)) // Other version not cached yet, so fetch it, cache it, returns it… technologit! if (!cachedMicrofrontend) { fetch(e.request).then(response => { if (response.status == 200) { // Cache microfrontend. putToCache(microfrontendCacheKey, e.request.clone(), response.clone()) // Send data date to app. return response } }) } // Other version cached: returns the higher one const cachedMicrofrontendUrl = new URL(cachedMicrofrontend.url) const paths = [cachedMicrofrontendUrl.pathname, url.pathname] paths.sort() // Cached one is higher version, we return it if (cachedMicrofrontend == paths[1]) { return respondWith(e, cachedMicrofrontend) } // Requested one should be fetched, cached, then returned. But before, delete the current cached one. removeCachedMicrofrontend(`${name}/${version}/index.js`) // delete currently cached version // Now fetch and reply (code repetition: same as in ~30 lines before… in `if (!cachedMicrofrontend) {`…) fetch(e.request).then(response => { if (response.status == 200) { // Cache microfrontend. putToCache(microfrontendCacheKey, e.request.clone(), response.clone()) // Send data date to app. return response } }) })) }) } // Return from cache or fallback to network. respondWith(e, e.request) })