Service-worker force更新新资产
Service-worker force update of new assets
我一直在阅读 html5rocks Introduction to service worker 文章并创建了一个基本的服务工作者来缓存页面、JS 和 CSS,它按预期工作:
var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
'/'
];
// Set the callback for the install step
self.addEventListener('install', function (event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
// IMPORTANT: Clone the request. A request is a stream and
// can only be consumed once. Since we are consuming this
// once by cache and once by the browser for fetch, we need
// to clone the response
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function(response) {
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// IMPORTANT: Clone the response. A response is a stream
// and because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have 2 stream.
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
当我对 CSS 进行更改时,由于服务工作人员正确地从缓存中返回 CSS,因此未获取此更改。
我被卡住的地方是如果我要更改 HTML、JS 或 CSS,我将如何确保服务人员从服务器加载更新版本,如果它可以而不是从缓存中?我试过在 CSS 导入中使用版本标记,但似乎没有用。
使缓存无效的一种方法是在缓存文件中更改任何内容时更改 CACHE_NAME
的版本。由于该更改会更改 service-worker.js
浏览器将加载更新的版本,您将有机会删除旧缓存并创建新缓存。您可以在 activate
处理程序中删除旧缓存。这是 prefetch sample 中描述的策略。
如果您已经在 CSS 文件上使用了某种版本标记,请确保它们能够进入服务工作者脚本。
这当然不会改变 CSS 文件上的缓存 headers 需要正确设置的事实。否则 service worker 只会加载已经缓存在浏览器缓存中的文件。
一个选择就是使用 service worker 的缓存作为后备,并始终尝试通过 fetch()
进入 network-first。但是,您会失去缓存优先策略提供的一些性能提升。
另一种方法是使用 sw-precache
生成您的服务工作者脚本作为您网站构建过程的一部分。
它生成的服务工作者将使用文件内容的哈希值来检测更改,并在部署新版本时自动更新缓存。它还将使用缓存破坏 URL 查询参数来确保您不会意外地使用来自 HTTP 缓存的过时版本填充您的 Service Worker 缓存。
在实践中,您最终会得到一个使用性能友好的缓存优先策略的服务工作者,但缓存将在页面加载后更新 "in the background",以便下次访问它,一切都很新鲜。如果需要,可以让用户 possible to display a message 知道有可用的更新内容并提示他们重新加载。
浏览器缓存问题
这里的主要问题是,当您的新服务工作者正在安装时,他会获取由以前的服务工作者处理的请求,并且他很可能正在从缓存中获取资源,因为这是您的缓存策略。然后,即使您正在使用新代码、新缓存名称更新您的服务工作者,调用 self.skipWaiting()
,他仍然将缓存中的旧资源放入缓存中!
这就是我完全更新 Service Worker 的方式
需要知道的是,每次您的代码脚本更改时,Service Worker 都会触发 install 事件,因此您不需要使用版本戳或其他任何东西,只需保持相同的文件名是可以的,甚至是推荐的。 There are other ways the browser will consider your service worker updated.
1.重写您的 install 事件处理程序:
我不使用 cache.addAll
因为它坏了。实际上,如果无法获取您要缓存的一个资源,则整个安装将失败,甚至不会将一个文件添加到缓存中。现在假设要缓存的文件列表是从存储桶自动生成的(这是我的情况),并且您的存储桶已更新并删除了一个文件,那么您的 PWA 将无法安装,它应该不会。
sw.js
self.addEventListener('install', (event) => {
// prevents the waiting, meaning the service worker activates
// as soon as it's finished installing
// NOTE: don't use this if you don't want your sw to control pages
// that were loaded with an older version
self.skipWaiting();
event.waitUntil((async () => {
try {
// self.cacheName and self.contentToCache are imported via a script
const cache = await caches.open(self.cacheName);
const total = self.contentToCache.length;
let installed = 0;
await Promise.all(self.contentToCache.map(async (url) => {
let controller;
try {
controller = new AbortController();
const { signal } = controller;
// the cache option set to reload will force the browser to
// request any of these resources via the network,
// which avoids caching older files again
const req = new Request(url, { cache: 'reload' });
const res = await fetch(req, { signal });
if (res && res.status === 200) {
await cache.put(req, res.clone());
installed += 1;
} else {
console.info(`unable to fetch ${url} (${res.status})`);
}
} catch (e) {
console.info(`unable to fetch ${url}, ${e.message}`);
// abort request in any case
controller.abort();
}
}));
if (installed === total) {
console.info(`application successfully installed (${installed}/${total} files added in cache)`);
} else {
console.info(`application partially installed (${installed}/${total} files added in cache)`);
}
} catch (e) {
console.error(`unable to install application, ${e.message}`);
}
})());
});
2。激活(新)服务工作者时清理旧缓存:
sw.js
// remove old cache if any
self.addEventListener('activate', (event) => {
event.waitUntil((async () => {
const cacheNames = await caches.keys();
await Promise.all(cacheNames.map(async (cacheName) => {
if (self.cacheName !== cacheName) {
await caches.delete(cacheName);
}
}));
})());
});
3。我每次更新资产时都会更新缓存名称:
sw.js
// this imported script has the newly generated cache name (self.cacheName)
// and a list of all the files on my bucket I want to be cached (self.contentToCache),
// and is automatically generated in Gitlab based on the tag version
self.importScripts('cache.js');
// the install event will be triggered if there's any update,
// a new cache will be created (see 1.) and the old one deleted (see 2.)
4。在缓存中处理 Expires
和 Cache-Control
响应 headers
我在 service worker 的 fetch 事件处理程序中使用这些 headers 来捕获当资源 expired/should 时是否应该通过网络请求资源刷新了。
基本示例:
// ...
try {
const cachedResponse = await caches.match(event.request);
if (exists(cachedResponse)) {
const expiredDate = new Date(cachedResponse.headers.get('Expires'));
if (expiredDate.toString() !== 'Invalid Date' && new Date() <= expiredDate) {
return cachedResponse.clone();
}
}
// expired or not in cache, request via network...
} catch (e) {
// do something...
}
// ...
对我来说最简单:
const cacheName = 'my-app-v1';
self.addEventListener('activate', async (event) => {
const existingCaches = await caches.keys();
const invalidCaches = existingCaches.filter(c => c !== cacheName);
await Promise.all(invalidCaches.map(ic => caches.delete(ic)));
// do whatever else you need to...
});
如果您有不止一次的缓存,您只需修改代码以使其具有选择性即可。
在我的主页中,我使用一些 PHP 从 mySQL 获取数据。
为了让 php 数据在您有互联网的情况下始终保持新鲜,我使用以毫秒为单位的日期作为我的服务人员的版本。
在这种情况下,当您连接到互联网并重新加载页面时,兑现页面将始终更新。
//SET VERSION
const version = Date.now();
const staticCacheName = version + 'staticfiles';
//INSTALL
self.addEventListener('install', function(e) {
e.waitUntil(
caches.open(staticCacheName).then(function(cache) {
return cache.addAll([
我一直在阅读 html5rocks Introduction to service worker 文章并创建了一个基本的服务工作者来缓存页面、JS 和 CSS,它按预期工作:
var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
'/'
];
// Set the callback for the install step
self.addEventListener('install', function (event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
// IMPORTANT: Clone the request. A request is a stream and
// can only be consumed once. Since we are consuming this
// once by cache and once by the browser for fetch, we need
// to clone the response
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function(response) {
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// IMPORTANT: Clone the response. A response is a stream
// and because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have 2 stream.
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
当我对 CSS 进行更改时,由于服务工作人员正确地从缓存中返回 CSS,因此未获取此更改。
我被卡住的地方是如果我要更改 HTML、JS 或 CSS,我将如何确保服务人员从服务器加载更新版本,如果它可以而不是从缓存中?我试过在 CSS 导入中使用版本标记,但似乎没有用。
使缓存无效的一种方法是在缓存文件中更改任何内容时更改 CACHE_NAME
的版本。由于该更改会更改 service-worker.js
浏览器将加载更新的版本,您将有机会删除旧缓存并创建新缓存。您可以在 activate
处理程序中删除旧缓存。这是 prefetch sample 中描述的策略。
如果您已经在 CSS 文件上使用了某种版本标记,请确保它们能够进入服务工作者脚本。
这当然不会改变 CSS 文件上的缓存 headers 需要正确设置的事实。否则 service worker 只会加载已经缓存在浏览器缓存中的文件。
一个选择就是使用 service worker 的缓存作为后备,并始终尝试通过 fetch()
进入 network-first。但是,您会失去缓存优先策略提供的一些性能提升。
另一种方法是使用 sw-precache
生成您的服务工作者脚本作为您网站构建过程的一部分。
它生成的服务工作者将使用文件内容的哈希值来检测更改,并在部署新版本时自动更新缓存。它还将使用缓存破坏 URL 查询参数来确保您不会意外地使用来自 HTTP 缓存的过时版本填充您的 Service Worker 缓存。
在实践中,您最终会得到一个使用性能友好的缓存优先策略的服务工作者,但缓存将在页面加载后更新 "in the background",以便下次访问它,一切都很新鲜。如果需要,可以让用户 possible to display a message 知道有可用的更新内容并提示他们重新加载。
浏览器缓存问题
这里的主要问题是,当您的新服务工作者正在安装时,他会获取由以前的服务工作者处理的请求,并且他很可能正在从缓存中获取资源,因为这是您的缓存策略。然后,即使您正在使用新代码、新缓存名称更新您的服务工作者,调用 self.skipWaiting()
,他仍然将缓存中的旧资源放入缓存中!
这就是我完全更新 Service Worker 的方式
需要知道的是,每次您的代码脚本更改时,Service Worker 都会触发 install 事件,因此您不需要使用版本戳或其他任何东西,只需保持相同的文件名是可以的,甚至是推荐的。 There are other ways the browser will consider your service worker updated.
1.重写您的 install 事件处理程序:
我不使用 cache.addAll
因为它坏了。实际上,如果无法获取您要缓存的一个资源,则整个安装将失败,甚至不会将一个文件添加到缓存中。现在假设要缓存的文件列表是从存储桶自动生成的(这是我的情况),并且您的存储桶已更新并删除了一个文件,那么您的 PWA 将无法安装,它应该不会。
sw.js
self.addEventListener('install', (event) => {
// prevents the waiting, meaning the service worker activates
// as soon as it's finished installing
// NOTE: don't use this if you don't want your sw to control pages
// that were loaded with an older version
self.skipWaiting();
event.waitUntil((async () => {
try {
// self.cacheName and self.contentToCache are imported via a script
const cache = await caches.open(self.cacheName);
const total = self.contentToCache.length;
let installed = 0;
await Promise.all(self.contentToCache.map(async (url) => {
let controller;
try {
controller = new AbortController();
const { signal } = controller;
// the cache option set to reload will force the browser to
// request any of these resources via the network,
// which avoids caching older files again
const req = new Request(url, { cache: 'reload' });
const res = await fetch(req, { signal });
if (res && res.status === 200) {
await cache.put(req, res.clone());
installed += 1;
} else {
console.info(`unable to fetch ${url} (${res.status})`);
}
} catch (e) {
console.info(`unable to fetch ${url}, ${e.message}`);
// abort request in any case
controller.abort();
}
}));
if (installed === total) {
console.info(`application successfully installed (${installed}/${total} files added in cache)`);
} else {
console.info(`application partially installed (${installed}/${total} files added in cache)`);
}
} catch (e) {
console.error(`unable to install application, ${e.message}`);
}
})());
});
2。激活(新)服务工作者时清理旧缓存:
sw.js
// remove old cache if any
self.addEventListener('activate', (event) => {
event.waitUntil((async () => {
const cacheNames = await caches.keys();
await Promise.all(cacheNames.map(async (cacheName) => {
if (self.cacheName !== cacheName) {
await caches.delete(cacheName);
}
}));
})());
});
3。我每次更新资产时都会更新缓存名称:
sw.js
// this imported script has the newly generated cache name (self.cacheName)
// and a list of all the files on my bucket I want to be cached (self.contentToCache),
// and is automatically generated in Gitlab based on the tag version
self.importScripts('cache.js');
// the install event will be triggered if there's any update,
// a new cache will be created (see 1.) and the old one deleted (see 2.)
4。在缓存中处理 Expires
和 Cache-Control
响应 headers
我在 service worker 的 fetch 事件处理程序中使用这些 headers 来捕获当资源 expired/should 时是否应该通过网络请求资源刷新了。
基本示例:
// ...
try {
const cachedResponse = await caches.match(event.request);
if (exists(cachedResponse)) {
const expiredDate = new Date(cachedResponse.headers.get('Expires'));
if (expiredDate.toString() !== 'Invalid Date' && new Date() <= expiredDate) {
return cachedResponse.clone();
}
}
// expired or not in cache, request via network...
} catch (e) {
// do something...
}
// ...
对我来说最简单:
const cacheName = 'my-app-v1';
self.addEventListener('activate', async (event) => {
const existingCaches = await caches.keys();
const invalidCaches = existingCaches.filter(c => c !== cacheName);
await Promise.all(invalidCaches.map(ic => caches.delete(ic)));
// do whatever else you need to...
});
如果您有不止一次的缓存,您只需修改代码以使其具有选择性即可。
在我的主页中,我使用一些 PHP 从 mySQL 获取数据。
为了让 php 数据在您有互联网的情况下始终保持新鲜,我使用以毫秒为单位的日期作为我的服务人员的版本。
在这种情况下,当您连接到互联网并重新加载页面时,兑现页面将始终更新。
//SET VERSION
const version = Date.now();
const staticCacheName = version + 'staticfiles';
//INSTALL
self.addEventListener('install', function(e) {
e.waitUntil(
caches.open(staticCacheName).then(function(cache) {
return cache.addAll([