PWA简介
Progressive Web Apps,简称PWA.
渐进式提升Web App原生体验的技术方案,能给用户原生应用的体验。
PWA能做到原生应用的体验不是靠特指某一项技术,而是经过应用一些新技术进行改进,在安全、性能和体验三个方面都有很大提升,PWA本质上是WebApp,借助一些新技术也具备NativeApp的一些特性,兼具WebApp和NativeApp的优点。
PWA具有的一些特点及特性
PWA特点:
- 可靠 即使在不稳定的网络环境下,也能瞬间加载并展现
- 体验 快速响应,并且有平滑的动画响应用户的操作
- 粘性 像设备上的原生应用,具有沉浸式饿得用户体验,用户可以添加到桌面
PWA具有特性
- 渐进式 - Progressive 适用于所有浏览器,因为它以渐进式增强作为宗旨开发
- 连接无关性 - 能够借助Service Worker在离线或这网络较差情况下正常访问
- 类似应用 - 由于在App Shell模型基础上开发,因为应具有NativeApp的交互和导航,给用户NativeApp的体验
- 持续更新 - 始终是最新的,无版本和更新问题
- 安全 - 通过HTTPS协议提供服务,防止窥探和确保内容不被篡改
- 可索引 - 应用清单文件和ServiceWorker可以让搜索引擎引到,从而将其识别为应用
- 粘性 - 通过推送离线通知等,可以让用户回流
- 免安装 - 用户可以添加常用的webapp到桌面,免去应用商店下载的麻烦
可链接 - 通过链接即可分享内容,无需下载安装
Web App Manifest(主屏图标)
- ServiceWorker(离线可用)
- Notification API & Push API(离线通知)
- App Shell & App Skeleton设计模型
- PRPL Pattern(Push, Render, Pre-cache, Lazy-load)
- 安全HTTPS
- 交互 & 动画
- PWA通常是SPA 通常采用AppShell设计模型
ServiceWorker
什么是Service Worker
W3C 组织早在 2014 年 5 月就提出过 Service Worker 这样的一个 HTML5 API ,主要用来做持久的离线缓存。
浏览器中的 javaScript 都是运行在一个单一主线程上的,在同一时间内只能做一件事情。随着 Web 业务不断复杂,我们逐渐在 js 中加了很多耗资源、耗时间的复杂运算过程,这些过程导致的性能问题在 WebApp 的复杂化过程中更加凸显出来。
W3C 组织早早的洞察到了这些问题可能会造成的影响,这个时候有个叫 Web Worker 的 API 被造出来了,这个 API 的唯一目的就是解放主线程,Web Worker 是脱离在主线程之外的,将一些复杂的耗时的活交给它干,完成后通过 postMessage 方法告诉主线程,而主线程通过 onMessage 方法得到 Web Worker 的结果反馈。
Service Worker 在 Web Worker 的基础上加上了持久离线缓存能力。
Service Worker 有以下功能和特性:
- 一个独立的 worker 线程,独立于当前网页进程,有自己独立的 worker context。
- 一旦被 install,就永远存在,除非被 uninstall
- 需要的时候可以直接唤醒,不需要的时候自动睡眠(有效利用资源,此处有坑)
- 可编程拦截代理请求和返回,缓存文件,缓存的文件可以被网页进程取到(包括网络离线状态)
- 离线内容开发者可控
- 能向客户端推送消息
- 不能直接操作 DOM
- 出于安全的考虑,必须在 HTTPS 环境下才能工作
- 异步实现,内部大都是通过 Promise 实现
Service Worker生命周期
MDN 给出了详细的 Service Worker 生命周期图:
生命周期状态:
安装( installing ):这个状态发生在 Service Worker 注册之后,表示开始安装,触发 install 事件回调指定一些静态资源进行离线缓存。
install 事件回调中有两个方法:
- event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。
- self.skipWaiting():self 是当前 context 的 global 变量,执行该方法表示强制当前处在 waiting 状态的 Service Worker 进入 activate 状态。
- 安装后( installed ):Service Worker 已经完成了安装,并且等待其他的 Service Worker 线程被关闭。
- 激活( activating ):在这个状态下没有被其他的 Service Worker 控制的客户端,允许当前的 worker 完成安装,并且清除了其他的 worker 以及关联缓存的旧缓存资源,等待新的 Service Worker 线程被激活。
activate 回调中有两个方法: - event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。
- self.clients.claim():在 activate 事件回调中执行该方法表示取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面,之后会被停止。
- 激活后( activated ):在这个状态会处理 activate 事件回调 (提供了更新缓存策略的机会)。并可以处理功能性的事件 fetch (请求)、sync (后台同步)、push (推送)。
- 废弃状态 ( redundant ):这个状态表示一个 Service Worker 的生命周期结束。
进入废弃 (redundant) 状态的原因可能为这几种:
- 安装 (install) 失败
- 激活 (activating) 失败
- 新版本的 Service Worker 替换了它并成为激活状态
Service Worker支持的事件
MDN 也列出了 Service Worker 所有支持的事件:
怎么使用Service worker
- 注册
安装serviceWoker ,通常需要在js主线程(常规页面的js)注册SeriveWorker来启动安装,这个过程将会通知浏览器我们的Service Worker线程的javascript文件在什么地方呆着。
1 | if('serviceWorker' in navigator) { |
注:查看是否注册成功,可以用chrome浏览器,输入 chrome://inspect/#service-workers
- 安装
1 | // 监听 service worker的install事件 |
- 自定义请求响应
任何被Service Worker控制的资源被请求时,都会触发fetch事件,这些资源包括了指定的scope内的html文档,和这些html文档内引用的其他任何资源。
实现思路: Serivice Worker代理服务,给Service Woker添加一个fetch的事件监听器,接着调用event上的respondWith()
方法来劫持HTTP响应,然后来更新他们。
1 | this.addEventListener('fetch', function(event) { |
Service Worker版本更新
自动更新所有页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// 安装阶段跳过等待,直接进入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);
}
})
);
})
])
);
});手动更新Service Worker
在页面中可以手动借助Registration.update()
更新。
1 | var version = '1.0.1'; |
debug时更新
Service Worker被载入后立即激活可以保证每次
/sw.js
为最新。1
2
3self.addEventListener('install', function(){
self.skipWaiting();
});意外惊喜
Service Worker的特殊之处除了由浏览器触发更新之外,还应用了特殊的缓存策略:如果该文件已24小时没有更新,当update触发时会强制更新。意外着最坏情况下Service Worker会每天更新一次。
ServiceWorker如何更新?
- 浏览器每天至少更新一次ServiceWorker
- 注册新的Service Worker,带上版本号,如: /sw.js?t=201709091920
- 手动更新resistration.update()
- 逐字节对比新的sw文件和旧的sw,有区别才更新
1 | // index.html |
PWA SEO可以服务器端渲染Server Side Rendering(SSR).
SSR中如何正确使用Service Worker