先说PWA
- 概念:渐进式 Web 应用会在桌面和移动设备上提供可安装的、仿应用的体验
- 实现条件
再说service worker
service worker原理
service worker是浏览器在后台独立于网页运行的脚本,它打开了通向不需要网页或用户交互的功能的大门。现在,他们已提供包括推送通知、后台同步的功能。将来,还会支持如定期同步或者地理围栏等其他功能。
我们本次研究的是拦截和处理网络请求,包括通过它来管理缓存中的响应。
因为上述功能,它使得web app可以支持离线体验。
service worker相关注意事项:
- 它是一种 JavaScript Worker,无法直接访问 DOM。 Service Worker 通过响应 postMessage 接口发送的消息来与其控制的页面通信,页面可在必要时对 DOM 执行操作。
- Service Worker 是一种可编程网络代理,让您能够控制页面所发送网络请求的处理方式。
- Service Worker 在不用时会被中止,并在下次有需要时重启,因此,您不能依赖 Service Worker
onfetch
和onmessage
处理程序中的全局状态。 如果存在您需要持续保存并在重启后加以重用的信息,Service Worker 可以访问 IndexedDB API。 - Service Worker 广泛地利用了 promise
- 在开发过程中,可以通过
localhost
使用 Service Worker,但如果要在网站上部署 Service Worker,则需要在服务器上设置HTTPS
。
service worker生命周期
要为网站安装service worker,你需要先在页面的js中进行注册。注册service worker将会使浏览器在后台启动service worker安装步骤。
在安装过程中,我们通常需要缓存某些静态文件。如果静态文件均已被缓存成功,那么service worker就安装完毕,等待被激活;否则则安装失败,service worker将无法激活(如果发生这种情况,它下次会重试)。
激活之后,Service Worker 将会对其作用域内的所有页面实施控制,不过,首次注册该 Service Worker 的页面需要再次加载才会受其控制。 service worker实施控制后,它将处于以下两种状态之一:服务工作线程终止以节省内存,或处理获取和消息事件,从页面发出网络请求(fetch)或消息(postMessage)后将会出现后一种状态。
service worker实际应用
1. 注册service worker
使用api:
- serviceWorkerContainer, 即navigator.serviceWorker,ServiceWorkerContainer接口为 service worker提供一个容器般的功能,包括对service worker的注册,卸载 ,更新和访问service worker的状态,以及他们的注册者
- ServiceWorkerContainer.register(scriptURL, scope[, base]), 返回一个promise对象,值是ServiceWorkerRegistration。 The register() method of the ServiceWorkerContainer interface creates or updates a ServiceWorkerRegistration for the given scriptURL.
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
2. 监听自身的install事件, 添加需要缓存的文件
使用api:
- event.waitUntil, 作为extendableEvent的一个api,ExtendableEvent是service worker生命周期的一部分,可以延长在全局范围上install和activate事件的生命周期,确保在删除过时的caches之前,不会调度任何函数事件。
- caches.open(CACHE_NAME), 表示Cache对象的存储,在全局可以访问。通过caches.open方法能获取到Cache实例,同样是通过promise方式返回。
- cache.addAll(urlsToCache), cache为缓存的Request/Response对象提供存储机制,addAll抓取一个url数组,检索并把返回的response对象添加到给定的cache对象里。
以上,当install事件发生时,service worker会开始抓取需要缓存的文件并将其添加到对应的cache对象里。缓存结束则该install事件结束。
var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js'
];
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);
})
);
});
3. 监听fetch事件, 从缓存中返回请求内容
situation: 当用户转至其他页面或刷新当前页面后,service worker将开始接收fetch事件(刷新或者转至其他页面时,用户利用fetch请求静态资源)
使用api:
- event.respondWith, 为fetch event的api,阻止浏览器的默认fetch handler,允许用户自行提供一个promise作为fetch事件的响应。
- caches.match,检查给定的Request是否是CacheStorage对象跟踪的任何Cache对象的键,并返回一个resolve为该匹配的promise.
- caches.open, 通过caches.open方法获取到对应cacheName的Cache实例。
- cache.put,同时抓取一个请求及其响应,并将其添加到给定的cache。
详细说明:service worker监听到fetch事件以后,检查当前request是否是CacheStorage对象跟踪的任何Cache对象的键,是的话返回匹配到的请求的响应,如果未找到响应的话,则重新fetch当前的request,并且在fetch到后将其响应添加到cache实例中。
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
if (response) {
return response;
}
var requestToCache = event.request.clone();
return fetch(requestToCache).then(function(response) {
if (!response || response.status !== 200) {
return response;
}
var responseToCache = response.clone();
caches.open(cacheName).then(function(cache) {
cache.put(requestToCache, responseToCache);
});
return response;
})
})
)
})
4. 更新 service worker
situation: 在某个时间点,您的 Service Worker 需要更新。 此时,您需要遵循以下步骤:
- 更新您的服务工作线程 JavaScript 文件。 用户导航至您的站点时,浏览器会尝试在后台重新下载定义 Service Worker 的脚本文件。 如果 Service Worker 文件与其当前所用文件存在字节差异,则将其视为_新 Service_ Worker。
- 新 Service Worker 将会启动,且将会触发 install 事件。
- 此时,旧 Service Worker 仍控制着当前页面,因此新 Service Worker 将进入 waiting 状态。
- 当网站上当前打开的页面关闭时,旧 Service Worker 将会被终止,新 Service Worker 将会取得控制权。
- 新 Service Worker 取得控制权后,将会触发其
activate
事件。
我们希望在 activate 回调中执行此任务的原因在于,如果在安装步骤中清除了任何旧缓存,则继续控制所有当前页面的任何旧 Service Worker 将突然无法从缓存中提供文件。
使用api:
- event.waitUntil, 作为extendableEvent的一个api,ExtendableEvent是service worker生命周期的一部分,可以延长在全局范围上install和activate事件的生命周期,确保在删除过时的caches之前,不会调度任何函数事件。
- caches.keys,使用该方法迭代所有 Cache 对象的列表
- caches.delete,查找匹配 cacheName 的 Cache 对象,如果找到,则删除 Cache 对象并返回一个resolve为true的 Promise 。如果没有找到 Cache 对象,则返回 false.
详细说明:service worker被激活后,检查当前的所有Cache对象,如果并不在此service worker的缓存列表中,则认为是老旧版本的Cache对象并将其从cacheStorage中删除。
self.addEventListener('activate', function(event) {
var cacheAllowlist = ['pages-cache-v1', 'blog-posts-cache-v1'];
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (cacheAllowlist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
动手实践
1. 启动文件中引入并注册
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- manifest文件可以自己看着配哦 -->
<link rel="manifest" href="./manifest.json">
<!-- 这是我要缓存的css -->
<link rel="stylesheet" type="text/css" href="/cache1.css">
<title>pwa</title>
</head>
<body>
<div id="app" class="test">test1</div>
<!-- 这是我要缓存的js -->
<script src='/cache1.js'></script>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js').then((registration) => {
console.log('Service worker registration', registration);
}, (err) => {
console.log(err);
})
})
}
</script>
</body>
</html>
2. service worker文件大概长这样
const cacheName = 'my-cache';
const cacheList = ['cache1.css', 'cache1.js'];
self.addEventListener('install', function(event) {
console.log('installing', caches, event);
event.waitUntil(
caches.open(cacheName).then(function(cache) {
console.log('opening', cache);
// 抓取url数组, 检索并把最终返回的response对象添加到cache对象
return cache.addAll(cacheList);
})
)
})
self.addEventListener('fetch', function(event) {
console.log('fetch', caches.keys());
event.respondWith(
caches.match(event.request)
.then(function(response) {
if (response) {
return response;
}
var requestToCache = event.request.clone();
return fetch(requestToCache).then(function(response) {
if (!response || response.status !== 200) {
return response;
}
var responseToCache = response.clone();
caches.open(cacheName).then(function(cache) {
cache.put(requestToCache, responseToCache);
});
return response;
})
})
)
})
3. 最终效果是……
没错,他被激活了, 手动刷新一下发现
你也可以勾上offline试试看
4. 时间对比
当我没有用service worker
当我有了service worker
更多
1. vue-cli 3中的pwa使用的google团队提供的worbox webpack plugin
2. service worker还具备推送消息的功能
参考文章:
你的第一个pwa应用:developers.google.com/web/fundame…
service worker基础使用:googlechrome.github.io/samples/ser…
web worker使用:www.html5rocks.com/en/tutorial…
service worker 推送消息:juejin.cn/post/684490…
@vue/cli-plugin-pwa: github.com/vuejs/vue-d…