PWA
PWA 定义
首先 带大家看一下什么是 PWA
这里给大家推荐几个接入 PWA 的站点
https://www.yuque.com/dashboard
https://cn.vuejs.org/index.html
分别是语雀和 Vue 的官网
如果你用 PC 端点开后 可以发现 浏览器的 url 一栏多了一个按钮 效果如下
如果你是 android 手机的话 也许你会看到如下的效果
如果是 iphone 手机的话 需要手动添加到桌面
至此 你的桌面端就会多一个 PWA 版本的 Web App 点开它就可以拥有类似 Native App 的体验
OK 现在说回什么是 PWA 下面 👇 引用 MDN 上的解释
PWA(progressing web app),渐进式网页应用程序,是 Google 在 2016 年 GoogleI/O 大会上提出的下一代 web 应用模型,并在随后的日子里迅速发展。 一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用. 随后借助于 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能
总的来说 PWA 是为了让 Web App 可以有 Native APP 效果的一组技术的实现 它包含
manifest
service worker
cache
消息推送
下面我们一个一个来讲
Manifest
简单来说 manifest 就是一个描述 web app 模拟 native app 行为的 json 配置文件
它大致上长这样
{
"name": "OS - PWA",
"short-name": "OS",
"start_url": "/",
"icons": [
{
"src": "images/logo48.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src": "images/logo64.png",
"sizes": "64x64",
"type": "image/png"
},
{
"src": "images/logo128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "images/logo144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "images/logo200.png",
"sizes": "200x200",
"type": "image/png"
}
],
"background_color": "yellow",
"theme_color": "yellow",
"display": "standalone"
}
具体的每一项配置 可以参考 MDN 的说明
https://developer.mozilla.org/zh-CN/docs/Web/Manifest
然后在我们的 index.html 中引入 manifest.json 文件即可
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Manifest</title>
<link rel="manifest" href="manifest.json" />
</head>
<body>
<h1>Hello Manifest</h1>
</body>
</html>
打开浏览器的调试工具 如果 manifest 一项显示如下 则表示你配置正确了
然而 现实是骨感的 你在 android 上或许可以看到开屏动画这些 但是在 iphone 上 你可能看不到一点效果
打开 https://caniuse.com/?search=manifest
可以看到 manifest 的兼容性目前还不是很好 iphone 上基本可以说是红红火火了
我们需要在 html 的 meta 标签中加入 针对 iphone 的属性 如下 👇
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Manifest</title>
<link rel="manifest" href="./manifest.json" />
<!-- 兼容ios -->
<!-- 是否隐藏safari地址栏 -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- 指定应用名称 -->
<meta name="apple-mobile-web-app-title" content="南树-PWA" />
<!-- 修改ios状态栏颜色 -->
<meta name="apple-mobile-web-app-status-bar-style" content="yellow" />
<!-- 配置ios icon -->
<link rel="apple-touch-icon" sizes="48x48" href="images/logo48.png" />
<link rel="apple-touch-icon" sizes="144x144" href="images/logo144.png" />
<link rel="apple-touch-icon" sizes="200x200" href="images/logo200.png" />
</head>
<body>
<h1>Hello Manifest</h1>
</body>
</html>
虽然达不到 android 的效果 但是勉强 iPhone 上也可以看到一些 PWA 的痕迹了
Service Worker
从上面的图可知 Service Worker 其实一个代理服务器的概念
它可以拦截页面的请求 然后决定是访问缓存还是发起 API 请求
下面的图则表明 Service Worker 其实是一种特殊的 Web Worker
因为和 UI 主线程的宿主环境不同 Service Worker 不能直接操作 DOM
除此之外 它还有以下特性
需要时直接唤醒 不需要时自动休眠
离线缓存内容开发者可控
一旦被安装永久存活 除非手动卸载
必须在 Https 环境上(localhost 除外)
大量使用 promise
有关 Service Worker 的生命周期可以参考下图
install 事件会在 service worker 注册成功时触发 主要用于缓存资源
activate 事件会在 service worker 激活的时候触发 主要用于删除旧的资源
fetch 事件会在发送请求的时候触发 主要用于缓存或者读取网络资源
在页面中注册一个 service worker 很简单
// 1. 在网页加载完成的时候 注册service worker
window.addEventListener('load', () => {
// 2. 兼容旧版本
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('./sw.js')
.then((registration) => {
console.log('注册成功', registration);
})
.catch((err) => {
console.log(`注册失败`, err);
});
}
});
这样便完成了一个注册 打开浏览器的调试工具 这次你可以在 service worker1 一栏中看到内容
接下来 说几个 service worker 的注意点
如果 sw.js 发生的变化 install 事件会重新触发
activate 事件会在 install 事件后触发 但是如果已经存在 service worker 那么就会处于等待状态 直到当前 service worker 终止
可以通过 self.skipWaiting()方法跳过等待 返回一个 promise 对象
可以通过 event.waitUntil()方法防止浏览器在异步操作之前就停止了生命周期
service worker 激活后 会在下一次刷新页面的时候生效 可以通过 self.clients.claim()立即获取控制权
代码如下
// install 事件会在service worker注册成功时触发 主要用于缓存资源
self.addEventListener('install', (event) => {
console.log(`install`, event);
// 让service worker跳过等待 直接进入activate状态 返回一个promise对象
// self.skipWaiting();
// 等待self.skipWaiting()执行结束 再进入activate事件
event.waitUntil(self.skipWaiting());
});
// activate 事件会在service worker激活的时候触发 主要用于删除旧的资源
self.addEventListener('activate', (event) => {
console.log(`activate`, event);
// 当一个sw注册时 页面在下次加载时才会使用它 claim方法会立即控制这些页面
// self.clients.claim();
event.waitUntil(self.clients.claim());
});
// fetch 事件会在发送请求的时候触发 主要用于缓存或者读取网络资源
self.addEventListener('fetch', (event) => {
console.log(`fetch`, event);
});
Cache
接下来 讲讲 Cache
cacheStorage 接口表示 Cache 对象的存储 配合 sw 来实现资源的缓存
cache api 类似于数据库的操作
cache.open(cacheName).then((cache)=>{}) 用于打开缓存 返回一个匹配 cacheName 的 cache 对象的 promise 类似于连接数据库
cache.keys()返回一个 promise 对象 包括所有缓存的 key
cache.delete(key) 根据 key 删除对应的缓存
cache 对象常用的方法
cache.put(req,res) 把请求当成 key 把对应的响应存储起来
cache.add(url) 根据 url 发起请求 并且把结果存储起来
cache.addAll(urls) 抓取一个 url 数组 并且把结果都存储起来
cache.match(req) 获取 req 对应的 response
配合 service worker 的代码如下
const CACHE_NAME = 'cache_v1';
// 缓存内容
self.addEventListener('install', async (event) => {
// 开启一个cache 得到一个cache对象
const cache = await caches.open(CACHE_NAME);
await cache.addAll([
'/',
'/static/img1.jpg',
'/static/img2.jpg',
'/static/img3.jpg',
'/manifest.json',
'/index.css',
]);
await self.skipWaiting();
});
// 清除旧的缓存
self.addEventListener('activate', async (event) => {
const keys = await caches.keys();
keys.forEach((key) => {
if (key !== CACHE_NAME) {
caches.delete(key);
}
});
event.waitUntil(self.clients.claim());
});
// fetch 事件会在发送请求的时候触发 主要用于缓存或者读取网络资源
// 判断资源能否请求成功 请求成功 --> 响应成功的结果 请求失败 --> 读取caches缓存
self.addEventListener('fetch', (event) => {
// 请求对象
const req = event.request;
// 浏览器响应
event.respondWith(networkFirst(req));
});
// 网络优先策略
const networkFirst = async (req) => {
try {
// 先从网络读取资源
const fresh = await fetch(req);
return fresh;
} catch (e) {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(req);
return cached;
}
};
打开浏览器的开发者工具 我们可以看到我们想要缓存的文件都在里面了
缓存策略
上述示例代码中 我们用到了网络优先策略
但其实缓存策略还有很多
Cache only
Network only
Cache falling back to network
Network falling back to cache
Cache & network race
Cache then network
Generic fallback
Cache only
Network only
Cache falling back to network
Cache & network race
Cache then network
Generic fallback
消息推送
因为引入 PWA 后 用户可能访问的是过期的缓存内容
所以我们可以调用消息推送的 API 来告诉用户
// 提示授权
if (Notification.permission === 'default') {
Notification.requestPermission();
}
if (!navigator.onLine) {
new Notification('提示', { body: '你当前没有网络,访问的是缓存' });
}
window.addEventListener('online', () => {
new Notification('提示', {
body: '你已经连上网络,请刷新页面,访问最新的数据',
});
});
PWA 应用
最后简单 说一下 PWA 可以有哪些应用
例如 APP Shell 和 APP Skeleton
就是运用了上述的 Cache then network 缓存策略
在网络不好的情况下 可以先给用户呈现 Web App 大致的样子 而不是白屏
Last updated
Was this helpful?