前言:
pwa 讲起来大家都有所耳闻, 在实际项目开发中会运用到,它最重要的一个作用就是可以实现客户端离线缓存,其次是服务端消息推送。我们在现有的一些webapp用户体验差(不能离线访问),用户粘性低(无法保存入口)而pwa就是为了解决这一系列问题(Progressive Web Apps),让webapp具有快速,可靠,安全等特点。
PWA一系列用到的技术
- Web App Manifest (生成桌面图标)
- Service Worker (离线访问)
- Push Api & Notification Api (服务端消息推送)
- App Shell & App Skeleton (骨架屏)
一.Web App Manifest
将网站添加到桌面、更类似native的体验。
针对安卓手机:
<link rel="apple-touch-icon" href="/icon.png" />
{
"name": "PWA效果展示", // 应用名称
"short_name": "PWA", // 桌面应用的名称 ✓ (兼容性)
"display": "standalone", // fullScreen (standalone) minimal-ui browser ✓
"start_url": "/", // 打开时的网址 ✓
"icons": [{ // 设置桌面图片 icon图标
"src": "/icon.png",
"sizes": "144x144",
"type": "image/png"
}],
"background_color": "#aaa", // 启动画面颜色
"theme_color": "#aaa" // 状态栏的颜色
}
ios 不支持 manifest
文件,可以通过 meta/link 私有属性进行设置
<!-- 图标icon -->
<link rel="apple-touch-icon" href="/icon.png"/>
<!-- 添加到主屏后的标题 和 short_name一致 -->
<meta name="apple-mobile-web-app-title" content="标题">
<!-- 隐藏safari地址栏 standalone模式下默认隐藏 -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- 设置状态栏颜色 -->
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
二.Service Worker
Service Worker特点:
- 不能访问/操作dom
- 会自动休眠,不会随浏览器关闭所失效(必须手动卸载)
- 离线缓存内容开发者可控
- 必须在https或者localhost下使用
- 所有的api都基于promise
生命周期:
- 安装( installing ):这个状态发生在 Service Worker 注册之后,表示开始安装,触发 install 事件回调指定一些静态资源进行离线缓存。
- 安装后( installed ):Service Worker 已经完成了安装,并且等待其他的 Service Worker 线程被关闭。
- 激活( activating ):在这个状态下没有被其他的 Service Worker 控制的客户端,允许当前的 worker 完成安装,并且清除了其他的 worker 以及关联缓存的旧缓存资源,等待新的 Service Worker 线程被激活。
- 激活后( activated ):在这个状态会处理 activate 事件回调 (提供了更新缓存策略的机会)。并可以处理功能性的事件 fetch (请求)、sync (后台同步)、push (推送)。
- 废弃状态 ( redundant ):这个状态表示一个 Service Worker 的生命周期结束。
关键方法
- self.skipWaiting():表示强制当前处在 waiting 状态的 Service Worker 进入 activate 状态
- event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。
- self.clients.claim():在 activate 事件回调中执行该方法表示取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面,之后会被停止。
关键步骤:
1、注册serviceWorker
window.addEventListener('load', async () => {
if ('serviceWorker' in navigator) {
let registration = await navigator.serviceWorker.register('/sw.js');
}
});
只要sw.js 中的内容发生改变,就会重新注册serviceWorker
2、注册监听函数
创建一个sw.js文件
self.addEventListener('fetch',(e)=>{
console.log(e.request.url); // 拦截请求
});
self.addEventListener('install',(e)=>{ 只有第一次安装才执行该回调
e.waitUntil(skipWaiting()); // 跳过等待,直接激活
});
self.addEventListener('activate',(e)=>{ 只有第一次激活才执行该回调
e.waitUntil(self.clients.claim()); // 让serviceWorker拥有控制权
})
self指向的是当前浏览器进程
以上三个步骤是关键
3、缓存静态资源
安装时将缓存列表进行缓存操作
const CACHE_NAME = 'cache_v' + 1;
const CACHE_LIST = [
'/',
'/index.css',
'/main.js',
'/api/list',
'/index.html',
];
async function preCache(){
let cache = await caches.open(CACHE_NAME);
await cache.addAll(CACHE_LIST);
await self.skipWaiting();
}
self.addEventListener('install',(e)=>{
e.waitUntil(preCache()); // 跳过等待,直接激活
});
激活后删除无用的缓存
async function clearCache(){
self.clients.claim();
let keys = await caches.keys();
await Promise.all(keys.map(key=>{
if(key !== CACHE_NAME){
return caches.delete(key);
}
}));
}
self.addEventListener('activate',(e)=>{
e.waitUntil(clearCache()); // 让serviceWorker拥有控制权
});
4、离线使用缓存
self.addEventListener('fetch',(e)=>{
let url = new URL(e.request.url);
if(url.origin !== self.origin){ // 静态资源走浏览器缓存
return;
}
e.respondWith(
fetch(e.request).catch(err=>{
return caches.match(e.request);
})
)
});
5、缓存策略
- cachefirst 缓存优先
- cacheonly 仅缓存
- networkfirst 网络优先
- networkonly 仅网络
- StaleWhileRevalidate 从缓存取,用网络数据更新缓存
完整的sw.js如下:
// 对资源进行离线缓存 seriveWorker 可以自定义缓存的内容
const CACHE_NAME = 'cache_v' + 2;
const CAHCE_LIST = [ // 列表越长 越容易出问题
'/',
'/index.html',
'/main.js',
'/index.css',
'/api/list',
'/manifest.json',
'/icon.png'
];
// 当断网时 我需要拦截请求, 使用缓存的结果
// 核心就是拦截请求
async function fetchAndSave(request){
let res = await fetch(request); // 数据流
let cloneRes = res.clone(); // 为了保证不破坏原有的响应结果
let cache = await caches.open(CACHE_NAME);
await cache.put(request,cloneRes); // 用响应结果更新缓存
return res;
}
self.addEventListener('fetch', (e) => {
// 如果是静态资源 不做拦截
let url = new URL(e.request.url);
if(url.origin !== self.origin){
return
}
// 缓存策略, 如果接口是不停的变化的 我们希望将数据更新到缓存中
if(e.request.url.includes('/api')){
return e.respondWith(
fetchAndSave(e.request).catch(res => {
return caches.match(e.request);
})
)
}
// serviceWorker中不支持ajax, 但是支持fetch
// 如果断网了, 抛出异常
e.respondWith(
fetch(e.request).catch(res => {
return caches.match(e.request);
})
)
});
// 当serviceWorker 安装时 需要跳过等待
async function preCache() {
let cache = await caches.open(CACHE_NAME); // 创建一个缓存空间
await cache.addAll(CAHCE_LIST);
await self.skipWaiting()
}
self.addEventListener('install', (e) => {
// e.waitUtil表示等待promise执行完成
// 预先将缓存列表的数据缓存起来
e.waitUntil(preCache())
})
async function clearCache() {
let keys = await caches.keys();
return Promise.all(keys.map(key => {
if (key !== CACHE_NAME) {
return caches.delete(key)
}
}))
}
self.addEventListener('activate', (e) => {
e.waitUntil(Promise.all([clearCache(), clients.claim()]));
// 激活后立刻让serviceWorker拥有控制权
})
以上就是pwa的离线缓存的一个完整的过程,
总结
1、注册serviceWorker;2、注册监听函数,分为: 请求拦截、安装、激活。 拦截请求,将对应的数据,缓存起来,调用cache.put,**更新视图数据; **在安装的时候需要将缓存列表里对应的数据缓存起来,并且执行self.skipWaiting()跳过等待;激活的时候 执行self.clients.claim(), 让当前的serviceWorker获得控制权并删除无用的缓存;
在实际项目项目开发中,我们只需要在webpack中配置workbox-webpack-plugin 这个插件。
new WorkboxPlugin.GenerateSW({ clientsClaim: true, skipWaiting: true })
关于pwa服务端消息推送的原理,未完待续, 先不在这里阐述了。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!