简介
pwa的技术层面
- 安全
- 性能
- 体验
pwa的特性
- 渐进式:开发宗旨,兼容所有浏览器
- 连接无关性:利用service worker开另一个线程进行运行复杂逻辑,可以在网络较差的地方进行
- 安全:通过https协议进行服务或者在本地的localhost 127.0.0.1进行
- 可索引:
- 粘性:可以利用manifest.json进行离线存储,并且可以离线通知
- 可安装
- 可链接
- 持续更新
- 类似应用:Native app的交互和导航,有Native app的体验
离线和缓存
service worker
简介
常用优化性能手段
- cdn
- css sprite雪碧图
- 文件合并压缩
- 异步加载
- 资源缓存
- 减少dns请求(将图片存于同一个域名下)
- 进行cookie隔离
- 避免重定向
关于解放主线程
对于复杂的逻辑,可以放在另一线程,不放在主线程,这与web worker操作极其相似,但是存在一些区别
service worker | web worker |
---|---|
必须在https环境下进行工作 | 不需要 |
一旦install就一直存在 | |
用的时候直接唤醒 | 不用的时候自动睡眠 |
离线内容开发者可控 | |
可编程拦截代理请求和返回,缓存的文件可以被网页进程取到,包括离线状态 | |
不能直接操作dom不能用alert这些全局函数 | 同样 |
异步实现,内部大部分是通过promise实现 | 同样 |
如何使用service worker
依赖
- 必须是https或者是本地localhost 127.0.0.1
- 缓存机制是Cache API
- 请求是fetch API
- 基于promise
注册
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/server.js', {scope: '/'}) // 定义相关文件的位置,scope为子目录文件夹名称
.then(function (registration) {
// 注册成功
})
.catch(function (err) {
// 注册失败:
});
});
}
在 chrome://inspect/#service-workers 进行查看是否已经注册
安装
一般是用来填充浏览器的离线缓存能力,可以存储网络发来的资源,并且根据请求来生成key,
// 监听 service worker 的 install 事件
this.addEventListener('install', function (event) {
// 如果监听到了 service worker 已经安装成功的话,就会调用 event.waitUntil 回调函数
event.waitUntil(
// 安装成功后操作 CacheStorage 缓存,使用之前需要先通过 caches.open() 打开对应缓存空间。
caches.open('my-test-cache-v1').then(function (cache) {
// 通过 cache 缓存对象的 addAll 方法添加 precache 缓存
return cache.addAll([
'/',
'/index.html',
'/main.css',
'/main.js',
'/image.jpg'
]);
})
);
});
由于localstorage是同步的做法,所以不允许在service worker上使用
请求响应
每次service worker控制的资源被请求到时,就会触发fetch事件,这些包括scopr指定的所有html文档,和这个HTML引用的所有任何资源。
this.addEventListener('fetch', function (event) {
event.respondWith(
// 劫持HTTP响应
caches.match(event.request).then(function (response) {
// 如果 Service Worker 有自己的返回,就直接返回,减少一次 http 请求
if (response) {
return response;
}
// 如果 service worker 没有返回,那就得直接请求真实远程服务
var request = event.request.clone(); // 把原始请求拷过来
return fetch(request).then(function (httpRes) {
// http请求的返回已被抓到,可以处置了。
// 请求失败了,直接返回失败的结果就好了。。
if (!httpRes || httpRes.status !== 200) {
return httpRes;
}
// 请求成功的话,将请求缓存起来。
var responseClone = httpRes.clone();
caches.open('my-test-cache-v1').then(function (cache) {
cache.put(event.request, responseClone);
});
return httpRes;
});
})
);
});
- install 的优点是第二次访问即可离线,缺点是需要将需要缓存的 URL 在编译时插入到脚本中,增加代码量和降低可维护性
- fetch 的优点是无需更改编译过程,也不会产生额外的流量,缺点是需要多一次访问才能离线可用。
缓存需要更新
- 在install中执行self.skipWaiting(),然后进入activate阶段,执行self.client.claim(),更新客户端所有的service worker
// 安装阶段跳过等待,直接进入 active
self.addEventListener('install', function (event) {
event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', function (event) {
event.waitUntil(
Promise.all([
// 更新客户端
self.clients.claim(),
// 清理旧版本
caches.keys().then(function (cacheList) {
return Promise.all(
cacheList.map(function (cacheName) {
if (cacheName !== 'my-test-cache-v1') {
return caches.delete(cacheName);
}
})
);
})
])
);
});
如果删除了cache上的键,但是浏览器却缓存了,那么就对这些文件设置更短的缓存期或者过滤规则
手动更新sw
使用Registration.update()更新这个页面
var version = '1.0.1';
navigator.serviceWorker.register('/sw.js').then(function (reg) {
if (localStorage.getItem('sw_version') !== version) {
reg.update().then(function () {
localStorage.setItem('sw_version', version)
});
}
});
如果该文件已 24 小时没有更新,当 Update 触发时会强制更新。这意味着最坏情况下 Service Worker 会每天更新一次
生命周期
- 安装
- 此时可以对静态资源进行缓存
- 回调方法
- event.waitUntil() 传入一个Promise为参数,等到promise为resolve为止
- self.skipWaiting() 强制waiting进入activate状态
- 安装后 等待其他的service worker线程被关闭
- 激活
- 已经清除了其他worker或者关联缓存的旧缓存资源
- 回调函数
- event.waitUntil()
- self.clients.claim()取得页面控制权。每次打开这个页面都会去使用,旧版本不能控制
- 激活后 会对activate事件进行回调,可以更新缓存,可以处理fetch,sync(后台同步),push(推送)的事件
- 废弃
这里我有一个问题:service worker里针对不同注册安装的文件有不同的worker,这个worker是为所有注册这个的页面服务的,那一个页面进行注册安装之后,其他的页面在注册过后,还需要安装吗,
调试sw
- 首先在开发环境需要跳过Install的等待状态,利用self.skipWaiting()
- CHROME浏览器的application有service worker的选项
- 也可以查看cache storage的缓存内容
- fetch 请求都会记录在network之上的