渐进式Web应用(PWA)系列一

简介

2014提出这个思想,2015年被chrome支持,到ios12的发布,使他可在ios中添加主屏幕;PWA是个集合,包括App Manifest、Service Worker、Web Push等,通过这些新技术对web app改进,在安全、性能、体验等方面都有很大提升


特点优势

可靠 - 即使在不稳定的网络环境下,也能瞬间加载并展现

当用户打开我们站点时(从桌面 icon 或者从浏览器),通过 Service Worker 能够让用户在网络条件很差的情况下也能瞬间加载并且展现。
Service Worker 是用 JavaScript 编写的 JS 文件,能够代理请求,并且能够操作浏览器缓存,通过将缓存的内容直接返回,让请求能够瞬间完成。开发者可以预存储关键文件,可以淘汰过期的文件等等,给用户提供可靠的体验。

体验 - 快速响应,并且有平滑的动画响应用户的操作

如果站点加载时间超过 3s,53% 的用户会放弃等待。页面展现之后,用户期望有平滑的体验,过渡动画和快速响应。
内容请求之前,优先app shell的渲染,做到native app一样的体验

粘性 - 像设备上的原生应用,具有沉浸式的用户体验,用户可以添加到桌面

  • 添加到桌面、定制图标、url Web App Manifest
  • 给用户发送离线通知,用户回流 Push Notification

开发者可以通过 PWA Checklist 查看现有特征并进行核对;


Service Worker(以下简称SW)

由来:

  • 说到性能优化其实就是尽量降低一个页面的网络请求成本从而缩短页面加载资源的时间并降低用户可感知的延时。当然和浏览器渲染效率、代码质量等也有关系。
  • 由于js运行在单一主线程,web业务的复杂,增加更多的耗时、耗资源的运算。W3C很早洞察并造了 Web Worker的api,复杂的工作交给他,通过postMessage告诉主进程,主线程通过onMessage,临时的。
  • SW出现了,最初的提出主要是用来做持久的离线缓存。SW今天提供离线体验、后台同步、推送通知的技术基础(定期同步、地理围栏后续推出)。
  • SW出现之前 AppCache(缓存更新,失败全败,二次更新,大小限制)
  • SW就是让缓存做到优雅和极致,让 Web App 相对于 Native App 的缺点更加弱化,也为开发者提供了对性能和体验的无限遐想。

特点:

  • 1 独立的worker线程
  • 2 被install永远存在
  • 3 用到才被唤醒
  • 4 可编程链接代理请求和返回
  • 5 离线内容开发者可控
  • 6 向客户端push消息
  • 7 不能直接操作dom
  • 8 Https
  • 9 Promise异步实现

使用:

前提:

  • Https
  • Cache Api:暴露在window下,不支持http Cache Api
  • Fetch Api
  • Promise

1.注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js', {scope: '/'})
.then(function (registration) {
// 注册成功
})
.catch(function (err) {
// 注册失败
});
});
}

scope:指定网域目录上所有事项的 fetch 事件
是否注册成功:chrome://inspect/#service-workers
查看服务工作线程详情:chrome://serviceworker-internals

2.静态资源缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 监听 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'
]);
})
);
});

cache API 一个Service Worker上的全局对象
和浏览器的标准的缓存工作原理很相似,但是它只对应你站点的域,它会一直持久存在,直到你告诉它不再存储,你拥有全部的控制权
localStorage 的用法和 Service Worker cache 的用法很相似,但是由于 localStorage 是同步的用法,所以不允许在 Service Worker 中使用。 IndexedDB 也可以在 Service Worker 内做数据存储。
第一次不会有什么不同

每次这些资源被请求都会触发scope 和 fetch事件,

3.动态资源缓存

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
26
27
28
29
30
31
32
33
// 监听 service worker 的 fetch 事件
this.addEventListener('fetch', function (event) {
event.respondWith(
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 的时候进行静态资源缓存,也可以通过 fetch 事件处理回调来代理页面请求从而实现动态资源缓存。
  • on install 的优点是第二次访问即可离线,缺点是需要将需要缓存的 URL 在编译时插入到脚本中,增加代码量和降低可维护性;
  • on fetch 的优点是无需更改编译过程,也不会产生额外的流量,缺点是需要多一次访问才能离线可用。

4.sw更新
逐字节对比,会触发新的sw并触install事件,旧的依然运行直到所有旧的都停止运行。
在 Web Server 不缓存或设置较短的有效期。

5.自动更新所有
// 安装阶段跳过 waiting,直接进入 active

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}
})
);
})
])
);
});

6.手动更新sw

1
2
3
4
5
6
7
8
9
10
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 会每天更新一次。

7.debug时更新

1
2
3
self.addEventListener('install', function () {
self.skipWaiting();
});

生命周期

  • 安装( installing ): event.waitUntil()、self.skipWaiting()
  • 安装后( installed )
  • 激活( activating ): event.waitUntil()、self.clients.claim()、caches.delete()
  • 激活后( activated ): fetch (请求)、push (推送)、sync (后台同步)。
  • 废弃状态 ( redundant )

事件

  • install
  • activate
  • message:postMessage api
    功能性事件:
  • fetch (请求):代理缓存
  • push (推送):PUSH API
  • sync (后台同步:background sync 不在w3c api 需开启 chrome://flags/#enable-experimental-web-platform-features

SW调试

  • self.skipWaiting();
  • Application: Update on reload;
  • Cache Storage;

添加到主屏幕

  • manifest.json配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "short_name": "短名称",
    "name": "这是一个完整名称",
    "icon": [
    {
    "src": "icon.png",
    "type": "image/png",
    "sizes": "48x48"
    }
    ],
    "start_url": "index.html"
    }

html头部

1
<link rel="manifest" href="path-to-manifest/manifest.json">

网络推送通知

  • 推送到通知栏 pushManager 需要服务端获取 applicationServerKey
  • Service Worker 监听 push 事件,显示通知
  • 可借助库:web-push

PWA改造

  • 应该是安全,将全站 HTTPS 化,因为这是 PWA 的基础,没有 HTTPS,就没有 Service Worker
  • 应该是 Service Worker 来提升基础性能,离线提供静态文件,把用户首屏体验提升上来
  • App Manifest,前面同时进行
  • 后续,再考虑其他的特性,离线消息推送等
  • 资源:sw-toolbox(针对动态/运行时请求的离线缓存)、sw-precache(针对静态资产/Application Shell 的离线预缓存)

支持力度

基于Can i use,截止2019-05-10的最新支持度(pc && mobile);

API 整体被支持度 完全支持 部分支持
App Manifest 80.44% 58.82% 21.62%
Service Worker 90.63% 90.36% 0.27%
Push API 79.2% 78.35% 0.85%
Background Sync API 71.35% 71.35% 0

相关知识

  1. 数据模型
  • 结构化:SQL、indexedDB
  • 键/值:Cache api、local storage
  • 字节流:文件系统、云存储
  1. 持久化
  • 会话持久化:session storage api
  • 设备持久化:cache api
  • 全局持久化:google云存储
  1. 缓存逐出
  • chrome55之后“持久化”
  1. indexedDB库
  • 流行库:localForage(8k)、Dexie(16k)…
  • 可能存在的问题:写入失败、数据被用户修改或删除、存储的数据过期处理、indexedDB的读写、结构化克隆会在主线程进行
    百度整理的API支持列表
    最新API支持列表

应用:

PWA的优势
AppCache
Web Worker
App Shell
是否为完整渐进式Web应用核对表

引用参考

饿了么的PWA升级实践
提供降级方案、错误监控以及数据统计

https://lavas.baidu.com/pwa/architecture/the-app-shell-model 待续
https://segmentfault.com/a/1190000015330282
https://kailian.github.io/2017/03/01/service-worker#
https://developers.google.cn/web/fundamentals/architecture/app-shell
https://juejin.im/post/5a6c86e451882573505174e7
https://huangxuan.me/2017/07/12/upgrading-eleme-to-pwa/
https://huangxuan.me/2017/02/09/nextgen-web-pwa/
https://jakearchibald.com/2014/offline-cookbook/#cache-then-network 缓存策略