PWA全称Progressive Web App,即渐进式WEB应用,由谷歌2015年提出.明确的一点就是:PWA是一个网页, 可以通过web技术开发出一个网页应用. 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能.pwa 可以添加在用户的主屏幕上,不用从应用商店进行下载,他们通过网络应用程序 Manifest file 提供类似于 APP 的使用体验( 在 Android 上可以设置全屏显示,由于 Safari 支持度的问题,所以在 IOS 上并不可以 ),并且还能进行 ”推送通知” 。
register(注册) - installing -> installed(安装) -> actvating -> Activated(激活) -> Redundant(废弃)
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 的生命周期结束。 * 安装 (install) 失败,激活 (activating) 失败 都进入废弃 (redundant) 状态
下面是个简单的 PWA 页面, 准备一个 HTML 文件, 以及相应的 CSS 等。sw.js 文件需要在 HTML 当中引入:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>pwa beauty</title>
<link rel="stylesheet" href="index.css">
<link rel="manifest" href="manifest.json">
</head>
<body>
<p>hello world beauty</p>
</body>
<script>index.html
//判断serviceWorker是否可用
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('sw.js', {scope: '/'}) //启动安装,scope参数可选,指定控制内容的子目录,接收指定目录的所有fetch事件
.then(function(registration) {
//success
console.log('scope: ', registration.scope);
})
.catch(err => console.log('serviceWorker 失败'), err);
}
</script>
</html>
Service Worker 在网页已经关闭的情况下还可以运行, 用来实现页面的缓存和离线, 后台通知等等功能。
接下来看一下sw.js 主要做的这几件事情:
首先安装时会触发 install 事件,监听该事件可执行安装时要做的事情。示例中是缓存用于离线时使用的静态资源,这也是最常见的行为.
处理静态缓存:首先定义需要缓存的路径, 以及需要缓存的静态文件的列表:
var cacheStorageKey = 'manimal-pwa-1';var cacheList = [
'/',
'index.html',
'index.css',
'8.png'
]
//监听sw 的install 的事件
self.addEventListener('install', e => {
//安装成功,调用e.waitUtill这个回调
e.waitUtill(
//操作缓存,先通过打开这个应用的缓存空间
caches.open(cacheStorageKey)
.then(caches => caches.addAll(cacheList)) //cacheList就是你想缓存的数组
.then(() => self.skipWaiting) //skipWaiting install 之后强制进入激活状态,跳过waiting状态
)
})
处理动态缓存: 每次任何被 Service Worker 控制的资源被请求到时,都会触发 fetch 事件,Service Worker 添加一个 fetch 的事件监听器,接着调用 event 上的 respondWith() 方法来劫持我们的 HTTP 响应,然后你可以用来更新他们
self.addEventListener('fetch', function(e) {
e.respondWith(
caches.match(e.request)
.then(function (response) {
//如果有自己的返回,直接返回,减少请求
if (response != null) {
return response
}
const request = e.request.clone();
return fetch(request).then(function (httpRes) {
//失败返回失败结果
if (!httpRes || httpRes.status !== 200) {
return httpRes;
}
//成功,把请求缓存
const responseClone = httpRes.clone();
caches.open(cacheStorageKey).then(function (caches) {
caches.put(e.request, responseClone)
})
return httpRes;
})
})
)
})
更新静态资源:缓存的资源随着版本的更新会过期, 所以会根据缓存的字符串名称(这里变量为 cacheStorageKey, 值用了 "minimal-pwa-1")清除旧缓存, 可以遍历所有的缓存名称逐一判断决决定是否清除。如果 /sw.js 内容有更新,当访问网站页面时浏览器获取了新的文件,逐字节比对 /sw.js 文件发现不同时它会认为有更新启动 更新算法,于是会安装新的文件并触发 install 事件。但是此时已经处于激活状态的旧的 Service Worker 还在运行,新的 Service Worker 完成安装后会进入 waiting 状态。直到所有已打开的页面都关闭,旧的 Service Worker 自动停止,新的 Service Worker 才会在接下来重新打开的页面里生效
self.addEventListener('activate', function (e) {
e.waitUtill(
caches.keys().then(cacheNames => {
return Promise.all(
//处理旧版本
cacheNames.map(name => {
if (name !== cacheStorageKey) {
return caches.delete(name)
}
})
)
}).then(() => {
//更新客户端该方法表示取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存
return self.clients.claim();
})
)
})
在新安装的 Service Worker 中通过调用 self.clients.claim() 取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面之后会被停止。
var version = 'manimal-pwa-2';navigator.serviceWorker.register('/sw.js').then(function (reg) {
if (localStorage.getItem('sw_version') !== version) {
reg.update().then(function () {
localStorage.setItem('sw_version', version)
});
}
});
执行命令:
http-server -p 8088 -c-1 # 注意设置关闭缓存, 这里用参数 -c-1
# 用另一个终端
ngrok http 8088
注意: Demo 当中如果直接启动 http-server 而不使用 -c-1 关闭缓存, sw.js 可能被缓存住, 导致更新方案失败。这种情况下存在 Caches API 和 HTML caching 两层缓存, 需要进行清理才能完成更新。由于 Service Worker 限制了使用 HTTPS 地址, 在 Android Chrome 打开需要借助 ngrok 生成的 HTTPS 地址, 这样才能把 demo 添加到首屏。添加到首屏之后, 即便在离线状态下, 页面也可以打开。
桌面浏览器可以直接通过 http://localhost:8088 访问, 从 DevTools 的 Application 标签可以看到 Service Worker。
Service Worker 浏览器支持情况
使用 Chrome 浏览器,可以通过进入控制台 Application -> Service Workers 面板查看和调试
Web App Manifest 是一个 JSON 格式的文件用来描述应用相关的信息,目的是提供将应用添加至桌面的功能:
<link rel="manifest" href="manifest.json">
{
"scope": "/", //定义应用模式下的路径范围,超出范围会已浏览器方式显示
"name": "这是一个app 的名字", //完整的名字
"short_name": "app", //短名称
"start_url": "/index.html", //启动地址
"display": "standalone",
"description": "每天知道多一点.",
"dir": "rtl",
"lang": "cn",
"orientation": "portrait", //纵向, 竖屏
"theme_color": "#3f51b5",
"background_color": "#fff",
"icons": [{
"src": "images/avatar/lzwme-36x36.png",
"sizes": "36x36",
"type": "image/png"
}, {
"src": "images/avatar/lzwme-48x48.png",
"sizes": "48x48",
"type": "image/png"
}, {
"src": "images/avatar/lzwme-72x72.png",
"sizes": "72x72",
"type": "image/png"
}, {
"src": "images/avatar/lzwme-96x96.png",
"sizes": "96x96",
"type": "image/png"
}, {
"src": "images/avatar/lzwme-144x144.png",
"sizes": "144x144",
"type": "image/png"
}, {
"src": "images/avatar/lzwme-192x192.png",
"sizes": "192x192",
"type": "image/png"
}, {
"src": "images/avatar/lzwme-256x256.png",
"sizes": "256x256",
"type": "image/png"
}]
}
PWA 应用具备了轻量化、离线使用、本地通知等优势特点,应用本身只需占用很小的存储空间,依然保留了原生 Apps 大部分功能,甚至还优化了硬件性能消耗、应用频繁出现广告内容的问题。如果你希望在安装原生应用之前,提前体验功能和内容,轻量化的 PWA 应用会是一个非常不错的选择。