一个简单的ServiceWorker应用
目标
实现在离线状态
下能正常加载页面以及本域的图片等资源
下载素材包
注册sw.js文件
新建sw.js文件
创建后目录结构如下:
├─index.html
├─sw.js
├─img
└1.jpg
<!-- index.html -->
...
<script>
window.addEventListener('load', () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js').then(registration => {
console.log('ServiceWorker成功注册在域: ', registration.scope);
}).catch(err => {
console.log('ServiceWorker注册失败: ', err);
}
);
}
}
);
</script>
...
先缓存文件
// sw.js
const VERSION = 0;
// 缓存版本
const CACHE_NAME = 'sw-cache-v' + VERSION;
// 缓存空间名字
const CACHE_URLS = [
'./index.html',
'./img/1.jpg',
];
// 需要缓存的url
addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
return cache.addAll(CACHE_URLS);
})
)
skipWaiting();
// 加上这句话,可以让sw立即生效,从而立刻删除旧的sw文件
})
这样就将文件缓存在浏览器了,接下来就是监听网络请求了,如果是缓存的请求,就直接返回缓存的文件,如果是网络请求,就返回网络请求的文件。
监听网络请求,实现离线访问功能
// sw.js
...
addEventListener('fetch', event => {
// fech事件用于接收fetch请求来选择走缓存还是走网络
if(new URL(event.request.url).origin != location.origin) {
// 过滤非本站的资源
return;
}
event.respondWith(
caches.match(event.request)
)
})
更新缓存
在说明更新缓存的时候,我需要先说明它的书写规范
- 不要给 service-worker.js
设置不同的名字
- 不要给 service-worker.js 设置缓存。
Cache-control: no-store
是比较安全的。浏览器的更新机制
SW的waiting状态
注册navigator.serviceWorker.register()时有两种情况:
- 如果没有活跃(activate)的SW,那就直接安装且激活。
- 如果已经有SW安装着,那么就和新的SW比较。如果没有差别则不做任何操作,如果有差别,那么就安装新的SW(执行
install
阶段),之后进入waiting
状态。等待老的SW所有页面被关闭后,才会执行activate
阶段,从而接管页面。由于浏览器是等待新的页面渲染之后才销毁旧的页面,
因此简单的切换页面或者刷新是不能使得SW进行更新
,只能关闭页面刷新。默认的更新是一种比较温和的做法,但让用户关闭页面并不是程序员能控制的。
假设我们提供了一次重大升级,希望新的 SW 尽快接管页面,应该怎么做呢?方法一:skipWaiting()
在遭遇突发情况时,很容易想到通过“插队”的方式来解决问题,现实生活中的救护车消防车等特种车辆就采用了这种方案。SW 也给程序员提供了实现这种方案的可能性,那就是在 SW 内部的
skipWaiting()
方法。addEventListener('install', event => { self.skipWaiting() // 预缓存其他静态内容 })
虽然这个方法显得很简单,但是它的实现还是有很多缺点的
比如有文件sw.v1.js和sw.v2.js。浏览器默认先异步加载sw.v1.js,缓存了前半部分文件。但是假如sw.v2.js应用了skipWaiting方法,那么sw.v1.js就会被强制退休,sw.v2.js立马接管页面。从而导致
一个页面前半部分在sw.v1.js后半部分在sw.v2.js,在脱机状态浏览器就会找不到某些文件而报错
。
除非你能保证同一个页面在两个版本的 SW 相继处理的情况下依然能够正常工作,才能使用这个方案。
SW的其它更新机制
- 浏览器每
24小时自动更新
一次Service Worker
写个js自己试试就知道了 - 注册新的 Service Worker,带上版本号,如:/sw.js?v=201807021920
const VERSION = 1; // 修改上面的缓存版本 const CACHE_NAME = 'sw-cache-v' + VERSION; // 缓存空间名字 ... addEventListener('activate', event => { // Service Worker 激活时,删除旧的缓存 event.waitUntil( caches.keys().then(function (keys) { keys.forEach(function (key) { if (key !== CACHE_NAME) { // 遍历所有缓存,如果不是当前版本,则删除 return caches.delete(key); // 删除旧的缓存 } }) }) ) }) ...
- 手动更新registration.update()
- 逐字节对比新的sw文件和旧的sw文件,有区别才更新
Service Worker更新后通知用户
Service Worker更新过程
完整sw.js代码
const VERSION = 0;
// 缓存版本
const CACHE_NAME = 'sw-cache-v' + VERSION;
// 缓存空间名字
const CACHE_URLS = [
'./',
'./index.html',
'./img/1.jpg',
];
// 需要缓存的url
addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
return cache.addAll(CACHE_URLS);
})
)
skipWaiting();
// 加上这句话,可以让sw立即生效,从而立刻删除旧的sw文件
})
addEventListener('activate', event => {
// Service Worker 激活时,删除旧的缓存
event.waitUntil(
caches.keys().then(function (keys) {
keys.forEach(function (key) {
if (key !== CACHE_NAME) {
// 遍历所有缓存,如果不是当前版本,则删除
return caches.delete(key);
// 删除旧的缓存
}
})
})
)
})
addEventListener('fetch', event => {
// fech事件用于接收fetch请求来选择走缓存还是走网络
if(new URL(event.request.url).origin != location.origin) {
// 过滤非本站的资源
return;
}
event.respondWith(
caches.match(event.request)
)
})