如何确保 Workbox 缓存更新永不激活的更新
How to ensure Workbox cache update for updates that never activate
我一直致力于将我们必须的 Web 应用程序转换为 PWA,并且我正在使用 Google 的 Workbox (v3.6.1) 来预缓存资源。在大多数情况下,它运行良好,但似乎有一个特定的场景导致缓存文件不同步。
我正在使用基本 precacheAndRoute()
功能来设置文件以进行预缓存。
workbox.precaching.precacheAndRoute([]); //populated at build time via workbox-cli
在首次安装和大多数更新时,文件会按预期进行预缓存。但是,如果 Service Worker 实例当前 waiting
并且安装了新更新,则临时缓存中的所有待处理文件都将被删除并且不会安装最新版本。
似乎 workbox.precaching
的安装步骤在将文件添加到临时缓存时更新了包含所有文件版本的 IndexDB。因此,下一个 Service Worker 版本认为所有文件的先前最新版本当前都已缓存,即使它们仍然仅在临时缓存中。然后新安装在插入它自己的文件之前从临时缓存中删除所有内容。因此,前一个 waiting
实例的未决缓存文件将永远丢失。
我的想法是,在安装新版本时,我可以强制临时缓存同步到永久缓存(通过使用 PrecacheController 和 activate()
函数),然后再允许要预缓存的新实例,但我对在用户积极使用应用程序时更新永久缓存有一些担忧。
我正在寻找确认我的想法是一个合适的解决方案,或者关于如何处理这种情况的任何其他建议。
Workbox 最近发布了 4.0.0 版,该问题似乎已通过该升级得到解决。我将保留以下答案,因为它可能对目前无法升级到 4.0.0 的任何人仍然有用。
正如我在问题中提到的那样,我使用 PrecacheController 或多或少地实现了这个功能。一个令人讨厌的部分是我必须自己实现 fetch
和 activate
监听器,因为我不再使用标准 workbox.precaching
。如果有人有任何其他想法,请随时 post 其他选项。
const precacheController = new workbox.precaching.PrecacheController();
precacheController.addToCacheList([]); //populated at build-time with workbox-cli
self.addEventListener('fetch', (event) => {
var url = event.request.url.endsWith('/') ? event.request.url + 'index.html' : event.request.url;
event.respondWith(new Promise(function (resolve) {
if (precacheController.getCachedUrls().indexOf(url) > -1) {
resolve(caches.open(workbox.core.cacheNames.precache)
.then((cache) => {
return cache.match(url);
})
.then((cachedResponse) => {
return cachedResponse || fetch(url);
}));
} else {
resolve(fetch(event.request));
}
}));
});
self.addEventListener('activate', (event) => {
event.waitUntil(precacheController.activate());
self.clients.claim();
});
self.addEventListener('install', function (event) {
var timeoutId = null;
event.waitUntil((new Promise(function (resolve, reject) {
if (self.registration.waiting) {
var channel = new MessageChannel();
channel.port1.onmessage = function (event) {
resolve();
};
//tell the current 'waiting' instance to cleanup its temp cache
self.registration.waiting.postMessage({
action: 'cleanupCache'
}, [channel.port2]);
} else {
resolve();
}
}))
.finally(function () {
//once temp cache is cleaned up from any 'waiting' instance, begin my install
return precacheController.install();
}));
});
self.addEventListener('message', function (event) {
if (event.data.action === 'cleanupCache') {
//move files from temp cache to permanent cache
precacheController.activate().finally(function () {
if (event.ports[0]) {
event.ports[0].postMessage('cleanupComplete');
}
});
}
});
我一直致力于将我们必须的 Web 应用程序转换为 PWA,并且我正在使用 Google 的 Workbox (v3.6.1) 来预缓存资源。在大多数情况下,它运行良好,但似乎有一个特定的场景导致缓存文件不同步。
我正在使用基本 precacheAndRoute()
功能来设置文件以进行预缓存。
workbox.precaching.precacheAndRoute([]); //populated at build time via workbox-cli
在首次安装和大多数更新时,文件会按预期进行预缓存。但是,如果 Service Worker 实例当前 waiting
并且安装了新更新,则临时缓存中的所有待处理文件都将被删除并且不会安装最新版本。
似乎 workbox.precaching
的安装步骤在将文件添加到临时缓存时更新了包含所有文件版本的 IndexDB。因此,下一个 Service Worker 版本认为所有文件的先前最新版本当前都已缓存,即使它们仍然仅在临时缓存中。然后新安装在插入它自己的文件之前从临时缓存中删除所有内容。因此,前一个 waiting
实例的未决缓存文件将永远丢失。
我的想法是,在安装新版本时,我可以强制临时缓存同步到永久缓存(通过使用 PrecacheController 和 activate()
函数),然后再允许要预缓存的新实例,但我对在用户积极使用应用程序时更新永久缓存有一些担忧。
我正在寻找确认我的想法是一个合适的解决方案,或者关于如何处理这种情况的任何其他建议。
Workbox 最近发布了 4.0.0 版,该问题似乎已通过该升级得到解决。我将保留以下答案,因为它可能对目前无法升级到 4.0.0 的任何人仍然有用。
正如我在问题中提到的那样,我使用 PrecacheController 或多或少地实现了这个功能。一个令人讨厌的部分是我必须自己实现 fetch
和 activate
监听器,因为我不再使用标准 workbox.precaching
。如果有人有任何其他想法,请随时 post 其他选项。
const precacheController = new workbox.precaching.PrecacheController();
precacheController.addToCacheList([]); //populated at build-time with workbox-cli
self.addEventListener('fetch', (event) => {
var url = event.request.url.endsWith('/') ? event.request.url + 'index.html' : event.request.url;
event.respondWith(new Promise(function (resolve) {
if (precacheController.getCachedUrls().indexOf(url) > -1) {
resolve(caches.open(workbox.core.cacheNames.precache)
.then((cache) => {
return cache.match(url);
})
.then((cachedResponse) => {
return cachedResponse || fetch(url);
}));
} else {
resolve(fetch(event.request));
}
}));
});
self.addEventListener('activate', (event) => {
event.waitUntil(precacheController.activate());
self.clients.claim();
});
self.addEventListener('install', function (event) {
var timeoutId = null;
event.waitUntil((new Promise(function (resolve, reject) {
if (self.registration.waiting) {
var channel = new MessageChannel();
channel.port1.onmessage = function (event) {
resolve();
};
//tell the current 'waiting' instance to cleanup its temp cache
self.registration.waiting.postMessage({
action: 'cleanupCache'
}, [channel.port2]);
} else {
resolve();
}
}))
.finally(function () {
//once temp cache is cleaned up from any 'waiting' instance, begin my install
return precacheController.install();
}));
});
self.addEventListener('message', function (event) {
if (event.data.action === 'cleanupCache') {
//move files from temp cache to permanent cache
precacheController.activate().finally(function () {
if (event.ports[0]) {
event.ports[0].postMessage('cleanupComplete');
}
});
}
});