PWA是什么
PWA(Progressive web apps,渐进式 Web 应用)运用现代的 Web API 以及传统的渐进式增强策略来创建跨平台 Web 应用程序。这些应用无处不在、功能丰富,使其具有与原生应用相同的用户体验优势。(摘自MDN)
总的来说就是web页面在移动端也能展现出类似原生的用户体验。
14年3月的时候,Chromium就已经开始研发service workers,同年11月份Chrome for Android release 40正式支持。谷歌开发者大会2016大力推广PWA相关技术,让PWA概念深入人心。在此之后的各种大型技术会议,PWA成了不可或缺的主题。iOS Safari 在18年2月发布的Safari Technology Preview Release 49宣布正式支持Service Workers,扫清了PWA发展的最大障碍。
18年算是PWA快速发展的一年。到了19年会发现这股热情似乎降了下来,甚至还有人在知乎问出2019 年 PWA(Progressive Web App) 凉了吗?,总的来说,该技术的发展主要还是依赖于谷歌的推广。
优势
PWA是基于web的,所以h5能做的它也能做
-
易安装 通过配置manifest,可以主动提示用户或者被动由用户安装到桌面;或者借助第三方服务(如Microsoft开发的pwabuilder)将其包装成原生的app
-
独立于网络 利用ServiceWorker将页面资源提前加载并离线运行
-
可链接 可通过生成的桌面图标进入应用,原生般的体验(没有浏览器地址栏)
-
可推送 ServiceWorker有一定的后台运行权限,可以像原生app一样接受来自网站的推送
-
自动更新 通过ServiceWorker可以在后台可控地自动更新PWA
-
渐进式 上面的扩展都不是必选的,你可以在觉得合适的时候再在已有的web app上渐进支持,这样可以无缝兼容和迁移旧浏览器和已有的站点
-
安全: 需要https的网络或者localhost(便于本地调试)
兼容性
PWA与小程序等其他app的比较
| PWA | 小程序 | HTML5 | native(iOS/Android) | Electron | |
|---|---|---|---|---|---|
| 安装方式 | 点开即用,可选添加到桌面全屏运行 | 即点即用,支付宝/微信等端内运行 | 点开即用,可以添加快捷方式到桌面 | 从appstore安装 | 去官网等地方下载安装包 |
| 占用空间 | 轻量 | 轻量,有大小限制 | 轻量 | 可大可小,限制不多 | 大,自带chromium runtime |
| 性能 | 中 | 中 | 中 | 高 | 中 |
| 跨平台 | iOS/Android/桌面(需要比较新的浏览器) | 依赖于支付宝/微信 | 通过浏览器 | 仅支持目标平台 | 跨桌面,不支持移动设备 |
| 独立运行 | 是(依赖于浏览器) | 否 | 否 | 是 | 是(自带浏览器) |
| 与native交互 | 少(通过schema等)其他能力依赖于浏览器实现 | 容易(通过容器提供的jsbridge等接口) | 少(通过schema等)其他能力依赖于浏览器实现 | 天然支持 | 可以(通过electron的api) |
| 分发方式 | 取决于网站本身,可以用CDN | 支付宝/微信提供CDN | 取决于网站本身,可以用CDN | appstore | 手动安装或者macappstore等 |
| 离线运行 | 是 | 是 | 否 | 是 | 是 |
| 版本控制 | 通过ServiceWorker | 通过支付宝/微信 | 无 | appstore推送 | 手动升级 |
| 推送消息 | ServiceWorker | 借用支付宝/微信能力 | ServiceWorker | 原生推送(FCM/APNS) | 自行维护后台推送 |
| 热更新 | ServiceWorker | 借用支付宝/微信能力 | 每次刷新都是新的 | 难,需要hack | 可借助插件 |
| 安全性 | 强制HTTPS | 由平台保证 | 不强制HTTPS |
核心点
service workers
PWA的核心,和Cookie一样是有Path路径的概念的,建议将其注册到顶级目录,以便管理全部页面
在网页已经关闭的情况下还可以运行, 用来实现页面的缓存和离线, 后台通知等等功能
特点
- 在页面中注册并安装成功后,运行于浏览器后台,不受页面刷新的影响,可以监听和截拦作用域范围内所有页面的 HTTP 请求。
- 网站必须使用 HTTPS。除了使用本地开发环境调试时(如域名使用 localhost)
- 运行于浏览器后台,可以控制打开的作用域范围下所有的页面请求
- 单独的作用域范围,单独的运行环境和执行线程
- 不能操作页面 DOM。但可以通过事件机制来处理
- 事件驱动型服务线程
全局变量
- self: 表示 Service Worker 作用域, 也是全局变量
- caches: 表示缓存
- skipWaiting: 表示强制当前处在 waiting 状态的脚本进入 activate 状态(刷新页面的时候只是调用了注册,是否重新注册决定于当前的sw是否已经存在,文件名改变或者文件内容改变的时候会重新注册,新的sw会先install,然后waiting。等浏览器重启时替换掉老的sw,新的sw则进入active状态,通过install是调用skipWaiting达到则跳过了等浏览器重启的过程)
- clients: 表示 Service Worker 接管的页面
生命周期
register(注册)
if ('serviceWorker' in navigator) {
window.onload = () => {
navigator.serviceWorker.register('sw.js')
.then((registration) => {
console.log('serviceWorker registration successful,it is: ', registration);
})
.catch(function (err) {
console.log('serviceWorker registration failed: ', err)
});
}
}
install(安装)
## cache相关API
Cache.match(request, options) 返回一个 Promise对象,resolve的结果是跟 Cache 对象匹配的第一个已经缓存的请求。
Cache.matchAll(request, options) 返回一个Promise 对象,resolve的结果是跟Cache对象匹配的所有请求组成的数组。
Cache.addAll(requests)接收一个URL数组,检索并把返回的response对象添加到给定的Cache对象。
Cache.delete(request, options)搜索key值为request的Cache 条目。如果找到,则删除该Cache 条目,并且返回一个resolve为true的Promise对象;如果未找到,则返回一个resolve为false的Promise对象。
Cache.keys(request, options)返回一个Promise对象,resolve的结果是Cache对象key值组成的数组。
const CACHE_VERSION = 'pwa-cache-key';
const CACHE_LIST = [
"./css/index.css",
"./image/logo.png"
];
// 监听install事件,安装完成后,进行文件缓存
self.addEventListener('install', e => {
console.log('install', );
// 将promise对象传给event
e.waitUntil(
// 找到key对应的缓存并且获得可以操作的cache对象
caches.open(CACHE_VERSION)
// 将需要缓存的文件加进来
.then(cache => cache.addAll(CACHE_LIST))
.then(() => {
// 调用 self.skipWaiting() 方法是为了在页面更新的过程当中,
// 新的 Service Worker 脚本能立即激活和生效。
self.skipWaiting();
})
);
});
self.addEventListener('fetch', function (e) {
console.log('fetch:' + e.request.url)
e.respondWith(
// 判断当前请求是否需要缓存
caches.match(e.request).then(function (cache) {
// 有缓存就用缓存,没有就从新发请求获取
return cache || fetch(e.request)
}).catch(function (err) {
console.log(err)
// 缓存报错还直接从新发请求获取
return fetch(e.request)
})
)
})
activate(激活)
// 监听activate事件,激活后通过cache的key来判断是否更新cache中的静态资源
self.addEventListener('activate', function (e) {
console.log('Service Worker 状态: activate')
var cachePromise = caches.keys().then(function (keys) {
// 遍历当前scope使用的key值
return Promise.all(keys.map(function (key) {
// 如果新获取到的key和之前缓存的key不一致,就删除之前版本的缓存
if (key !== cacheName) {
return caches.delete(key)
}
}))
})
e.waitUntil(cachePromise)
// 保证第一次加载fetch触发
return self.clients.claim()
})
redundant(废弃)
浏览器缓存模式
from memory cache 内存,只存在浏览器运行时,如base64图片数据和静态资源,不可控
from disk cache 硬盘,长期缓存在硬盘中,如静态资源,不可控
from ServiceWorker sw代理,完全可控
示例
mainifest.json配置文件
需要将该配置文件引入到入口页面
{
"name": "pwa demo", // 必填,插件名称
"short_name": "demo PWA", // 选填 在APP launcher和新的tab页显示,如果没有设置,则使用name
"description": "The app that helps you understand PWA", // 应用描述
"display": "standalone", // 应用的显示模式。fullscreen:全屏显示,会尽可能将所有的显示区域都占满。
standalone:浏览器相关UI(如导航栏、工具栏等)将会被隐藏,因此看起来更像一个Native App。
minimal-ui:显示形式与standalone类似,浏览器相关UI会最小化为一个按钮,不同浏览器在实现上略有不同。
browser:一般来说,会和正常使用浏览器打开样式一致(PS: 当一些系统的浏览器不支持fullscreen时将会显示成 standalone 的效果,当不支持 standalone 属性时,将会显示成 minimal-ui 的效果,以此类推)。
"start_url": "/", // 应用启动时的url
"theme_color": "#8888ff", // 桌面图标的背景色
"background_color": "#aaaaff", // 为web应用程序预定义的背景颜色。在启动web应用程序和加载应用程序的内容之间创建了一个平滑的过渡。
"icons": [ // 桌面图标
{
"src": "image/logo.png",
"sizes": "256x256", // 多个图片尺寸以空格分隔
"type": "image/png" // 方便userAgent快速排除不支持的类型
}
]
}
用例
谷歌作为推广者,应用PWA的相对较多,比如:Youtube Music、Google Photos、 Android Messages、Facebook移动版、Twitter
what web can do today: PWA的兼容性检测网站,可以看到你的浏览器支持哪些PWA特性
AppScope: PWA应用商店,本身就是一个PWA,里面收录的app也都是PWA