Progressive Web App介绍
- pwa可以让用户保存网页入口,并且离线访问
- pwa依赖Service Worker功能
Web App Manifest(配置添加到桌面的设置项)
// html插入
<link rel="manifest" href="./manifest.json">
// manifest.json
{
"name": "客服管理系统", // 应用名称
"short_name": "客服管理系统", // 桌面应用的名称
"display": "standalone", // 显示模式
"background_color": "#fff", // 背景颜色
"description": "一个集成工单处理,客服管理的系统", // 应用描述
"start_url": "/index.html", // 打开时的网址
"theme_color": "#fff", // 主题颜色
"icons": [ // 图标,自动设置尺寸,图标尺寸需要和实际的一样
{
"src": "./android-chrome-maskable-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "./android-chrome-maskable-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
- tomitm.github.io/appmanifest… 可以根据配置自动生成manifest的网站
- iOS系统对manifest.json属于部分支持,所以我们需要在head里给配置meta属性才能让iOS系统更加完善.
<!-- 图标,支持尺寸匹配 -->
<meta name="apple-touch-icon" sizes="192x192" href="/android-chrome-maskable-192x192">
<meta name="apple-touch-icon" sizes="512x512" href="/android-chrome-maskable-512x512">
<!-- short_name一致,标题 -->
<meta name="apple-mobile-web-app-title" content="">
<!-- 显示模式,有且仅支持yes,代表Standalone -->
<meta name="apple-mobile-web-app-capable" content="yes">
// 还有其他很多属性,在上面提供的网址,选择也可生成
service Worker 特点
- 不能访问/操作dom
- 所有api都是基于promise的
- 只有在https与localhost下navigator里才会有serviceWorker这个对象
- 离线缓存
- 不会随浏览器关闭而关闭,必须手动卸载
service Worker生命周期
触发更新条件
- 当未安装serviceWorkbench的页面都打开时
- 当上次写入ServiceWorker数据库的时间戳与本次之间超过24小时
- 当该注册文件(sw.js)发生更新时
installing
当更新条件触发时,install会被执行,需要将声明的资源加入缓存之中
installed
install完成之后,进入该阶段,需要等待激活,可以告知用户需要升级了
activating
当前没有其他激活的serviceWorker,或者脚本中手动调用skipWaiting,或者作用域下面的页面全被关闭了,会进入该阶段,在该阶段activate会被执行,需要将之前的缓存清理了,
activated
activating完成之后,进入该阶段,serviceWorker内的fetch(网络请求)和message(与客户端通信)可以被监听了
redundant
安装或激活失败,或者当前serviceWorker被其他所替换,就会进入该状态
Cache(为ServiceWorker提供缓存)
Cache 接口为缓存的 Request / Response 对象对提供存储机制,例如,作为ServiceWorker 生命周期的一部分。请注意,Cache 接口像 workers 一样,是暴露在 window 作用域下的。尽管它被定义在 service worker 的标准中, 但是它不必一定要配合 service worker 使用.
caches.open(cacheName) 打开(创建)一个cache对象,用作缓存
Caches.match(request, options) 返回一个 Promise对象,resolve的结果是匹配到的第一条缓存
Caches.matchAll(request, options) 返回一个Promise 对象,resolve的结果是跟Cache对象匹配的所有请求组成的数组。
Caches.addAll(requests)接收一个URL数组,检索并把返回的response对象添加到给定的Cache对象。
Caches.delete(request, options)搜索key值为request的Cache 条目。如果找到,则删除该Cache 条目,并且返回一个resolve为true的Promise对象;如果未找到,则返回一个resolve为false的Promise对象。
Caches.keys(request, options)返回一个Promise对象,resolve的结果是Cache对象key值组成的数组。
实践
注册serviceWorker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/sw.js')
.then(res => {
// 当serviceWorker更新时触发
res.onupdatefound = () => {
const e = res.installing;
// 当更新流程状态发生变化的时候触发
e.onstatechange = () => {
switch (e.state) {
case 'installed':
// 根据当前有没有serviceWorker,来判断是更新还是新的安装
navigator.serviceWorker.controller
? // 更新可以设计为向用户询问是否更新,手动触发,向sw.js传递信息,让其调用skipWaiting,进入activeting状态
console.log('serviceWorker有新的内容可以使用!')
: console.log('serviceWorker现在已经离线可用了!');
break;
case 'redundant':
console.error('当前serviceWorker已经失效了.');
}
};
};
})
.catch(error => {
console.log('serviceWorker注册失败了!', error);
});
});
}
安装,更新,缓存
// 获取当前格式化后的时间
const time = () =>
new Date(new Date() + 8 * 3600 * 1000)
.toJSON()
.substr(0, 19)
.replace('T', ' ');
// 缓存的名称,根据当前时间变化而变化
const CACHE_NAME = `cache ${time()}`;
// 缓存资源列表
const CACHE_LISTS = [
'/',
'/workbench.js',
'/workbench/',
'/manifest.json',
'/workbench/myTicketComponent.js',
'/fonts/element-icons.313f7da.woff',
'/fonts/element-icons.4520188.ttf'
];
// 监听请求事件,event.respondWith可以劫持相应内容,通过这个,在这里可以处理离线请求问题
self.addEventListener('fetch', e => {
e.respondWith(
// 方案1, 请求成功走网络,失败走缓存
// fetch(e.request).catch(err => caches.match(e.request))
// 方案2, 请求后直接走缓存,缓存没有才走网络
caches
.match(e.request)
.then(cache => {
// 有缓存就用缓存,没有就重新发请求获取
return cache || fetch(e.request);
})
.catch(err => {
// 缓存报错还直接重新发请求获取
return fetch(e.request);
})
);
});
// 什么情况下会触发serviceWorker更新:
// 1,当未安装serviceWorkbench的页面都打开时
// 2,当上次写入ServiceWorker数据库的时间戳与本次之间超过24小时
// 3,当该sw文件发生更新时
// 注意: serviceWoker如果上一个没有销毁,需要手动调用skipWating才能跳过等待
self.addEventListener('install', e => {
// service worker标准提供一个waitUntil方法,当oninstall或者onactivate触发时被调用,接受一个promise。在这个 promise被成功resolve以前,不会分发到serviceWorker中继续执行
e.waitUntil(
caches
.open(CACHE_NAME)
.then(cache => cache.addAll(CACHE_LISTS))
.then(skipWaiting) // 缓存后手动跳过当前serviceWorker,进入下个serviceWorker激活,该操作也可以进行询问用户是否更新来进行操作
);
});
// 当更新激活完毕后,将之前的缓存清除
self.addEventListener('activate', e => {
e.waitUntil(
caches.keys().then(keys => {
return Promise.all(
keys.map(key => {
// 如果新获取到的key和之前缓存的key不一致,就删除之前版本的缓存
if (key !== CACHE_NAME) {
return caches.delete(key);
}
})
);
})
);
//当一个 serviceWorker 被初始注册时,页面在下次加载之前不会使用它,clients.claim方法会立即控制这些页面
return self.clients.claim();
});
offline-plugin(webpack插件)
介绍
- 自动生成sw相关逻辑
- 缓存列表根据打包资源自动生成
实践
webpack配置
const OfflinePlugin = require('offline-plugin');
plugins: [
new OfflinePlugin({
responseStrategy: 'cache-first', // 缓存/网络优先
AppCache: false, // 不启用appCache
safeToUseOptionalCaches: true, // Removes warning for about `additional` section usage
autoUpdate: true, // 自动更新
caches: {
// webpack打包后需要换的文件正则匹配
main: [
'**/*.js',
'**/*.css',
/\.(png|jpe?g|gif|svg)(\?.*)?$/,
/\.(woff2?|eot|ttf|otf)(\?.*)?$/
], // install时缓存
additional: [':externals:'],
optional:[] // 这里配置的文件清单在serviceWorker安装激活阶段不会进行缓存,只有在监听到网络请求的时候才进行缓存
},
externals: [
'http://127.0.0.1:3000/api/workbench/me',
'http://127.0.0.1:3000/api/user'
], // 设置外部链接,例如配置http://hello.com/getuser,那么在请求这个接口的时候就会进行接口缓存
excludes: ['**/.*', '**/*.map', '**/*.gz', '**/manifest-last.json'], // 需要过滤的文件
ServiceWorker: {
output: './static/sw.js', // 输出目录
publicPath: '/static/sw.js', // sw.js 加载路径
scope: '/', // 作用域(此处有坑)
minify: true, // 开启压缩
events: true // 当sw状态改变时候发射对应事件
}
})
]
offline-plugin配置项
Caches: 'all' | Object,
all: 意味着所有webpack构建出来的资源,以及在externals选项中的资源都会被缓存。
Object: 包含三个数组或正则的配置对象(main, additional, optional),它们都是可选的,且默认为空。
默认:all。
externals: []
允许开发者指定一些外部资源(比如CDN引用,或者不是通过webpack生成的资源)。配合Caches的additional项,能够实现缓存外部资源的功能。
默认:null。
举例:['fonts/roboto.woff']。
ServiceWorker: Object | null | false
events:布尔值。允许 runtime 接受来自 Service Worker 的消息,默认值为 false。
navigateFallbackURL:当一个 URL 请求从缓存或网络都无法被获取时,将会重定向到该选项所指向的 URL。
updateStrategy:'all' | 'changed'
指定了缓存策略选择全部更新,另外一种是增量更新changed
项目配置
import * as OfflinePluginRuntime from 'offline-plugin/runtime';
OfflinePluginRuntime.install({
onUpdateReady: () => {
// 资源下载完毕后,调用applyUpdate即skipwaiting()方法
if (window.confirm('发现新版本,是否更新?')) {
OfflinePluginRuntime.applyUpdate();
}
},
onUpdated: () => {
console.log('SW Event:', 'onUpdated')
window.swUpdate = true
}
});