PWA
  # 什么是PWA
PWA(渐进式Web App),MDN参考 (opens new window)
# 优点
- 渐进式:可安装到桌面,使用更方便
 - 离线使用:网络差、离线也都可以进行访问
 - 原生体验:可配置启动页、隐藏地址栏等,拥有app类似的使用体验
 - 离线推送:可推送离线通知,增加用户粘性
 
# PWA现状
# 背景
- 国外
- 手机自带谷歌浏览器与谷歌服务
 - 不需要翻墙
 
 - 国内
- 浏览器众多(qq、360、百度浏览器等),导致对h5、es6、css3支持度不统一
 - 谷歌浏览器普及度低
 - 外网需要翻墙
快应用前几年国内刚兴起,但各浏览器厂商因为利益等各种原因,不能形成一套规范,最终还是小程序胜利✌️
 
 
# 应用一:创建桌面应用
由于pc端chrome、firefox浏览器普及度高,所以pc端适用效果还不错,其他浏览器厂商支持不一未进行测试(如:360浏览器、QQ浏览器等)
因为国外手机自带谷歌浏览器与谷歌服务,所以pwa应用在国外非常流行,而在国内恰恰相反,移动端各浏览器厂商支持程度不一,又要涉及各种权限,所以在国内pwa应用基本凉凉。
# 应用二:离线消息通知
离线通知,要借助谷歌服务,先把消息传给谷歌相应消息平台(FCM),然后由谷歌向浏览器派送消息,使得用户可以接收到离线通知。因为谷歌浏览器普及度低,又要借助翻墙,所以离线通知在国内基本没戏。
# 应用三:service worker
有离线需求的话,service worker + caches可以去单独使用,做离线存储效果还不错,主流浏览器几乎都支持
# 谁在用
# pwa
- 抖音 (opens new window)
 - 语雀 (opens new window)
 - YouTube (opens new window)
 - 微博 (opens new window)
 - 个人博客 (opens new window)
 
# service worker
# 技术要点
# 1.manifest.json
应用清单,用来配置应用图标、展示方式、主题、入口页等,应用基本信息 MDN参考 (opens new window)
# 2.service worker
pwa的核心技术,是一个独立的worker线程,不会占用js主线程,可用来做持久化离线缓存。MDN参考 (opens new window)
注意:只能在https或loclhost下使用
# 3.cacheStorage
配合service worker来实现缓存,MDN参考 (opens new window)
# 在vue中使用
# vite中使用
vite-plugin-pwa (opens new window)
yarn add vite-plugin-pwa -D- vite.config.js增加配置
 
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({
  plugins: [
    VitePWA({ registerType: 'autoUpdate' })
  ]
})
 1
2
3
4
5
6
2
3
4
5
6
vite-plugin-pwa入门指南 (opens new window)
# vue-cli中使用
终端执行 vue add @vue/pwa 参考 (opens new window)
# 案例完整代码
# manifest.json
{
  // 应用名称
  "name": "一个神器的应用",
  // 应用简称
  "short_name": "神应用",
  // 应用描述
  "description": "一个无所不能的离线应用",
  // 启动首页地址
  "start_url": "/index.html", 
  // app显示模式 
  // 1.standalone 更像app  2.fullscreen 全屏模式  3.minimal-ui 会显示地址栏
  "display": "standalone",
  // 启动动画背景色
  "background_color": "#999",
  // 主题颜色
  "theme_color": "#fff",
  // 应用图标
  "icons": [
    {
      "src": "/image/logo.png",
      "sizes": "256x256",
      "type": "image/png"
    }
  ],
  // 应用语言
  "lang":"zh-CN"
}
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="manifest" href="./manifest.json">
  <link rel="stylesheet" href="css/index.css">
  <title>pwa演示</title>
</head>
<body>
<h3>service worker缓存图片</h3>
<img src="image/1.png" width="500">
<h3>http强缓存图片</h3>
<img src="https://oper.anju.dev.nnzhineng.com:20104/s/cms/e1ac985b-30b5-45f8-a950-e1c534c876da/%E7%AE%80%E7%BA%A6%E7%BB%BF%E8%89%B2%E5%A4%A7%E6%9A%91%E8%8A%82%E6%B0%94%E6%8F%92%E7%94%BB%E5%8D%A1%E9%80%9A%E5%85%AC%E4%BC%97%E5%8F%B7%E9%A6%96%E5%9B%BE_%E5%85%AC%E4%BC%97%E5%8F%B7%E5%B0%81%E9%9D%A2%E9%A6%96%E5%9B%BE_2022-07-14+11_09_09.jpeg" width="500">
<h3>无缓存图片</h3>
<img src="image/2.png" width="500">
</body>
<script>
  window.addEventListener('load', () => {
    if ('serviceWorker' in navigator) {
      console.log('该环境支持 serviceWorker')
      navigator.serviceWorker.register('./sw.js').then(sw =>
              console.log('serviceWorker启动成功:', sw)
      ).catch(err => {
        console.log('serviceWorker启动失败',err)
      });
    }
  })
  // 浏览器无接受消息权限,去获取授权
  if(Notification.permission === 'default'){
    Notification.requestPermission()
  }
  window.addEventListener('online', () => {
    new Notification('网络提示',{body:'您的设备处于联网状态'})
    // 刷新页面
    location.reload();
  })
  window.addEventListener('offline', () => {
    new Notification('网络提示',{body:'您的设备处于断网状态'})
  })
</script>
</html>
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# sw.js
// cache缓存名称
const CACHE_NAME = 'cache_v1'
self.addEventListener('install', async event => {
    console.log('serviceWorker 安装成功', event)
    // 开启一个cache
    const cache = await caches.open(CACHE_NAME)
    // cache存储资源
    await cache.addAll([
        '/', // 要缓存/ 而不是缓存 /index.html,因为访问的是localhost/
        '/image/logo.png',
        '/image/1.png',
        '/manifest.json',
        '/css/index.css'
    ])
    // 让serviceWorker跳过等待,直接进入activate状态
    // event.waitUntil 等待skipWaiting结束后,在往下执行,也可以用await
    await self.skipWaiting()
})
self.addEventListener('activate', async event => {
    console.log('serviceWorker 重新激活', event)
    // 去除旧的缓存资源
    const keys = await caches.keys()
    keys.forEach(key => {
        if(key !== CACHE_NAME){
            caches.delete(key)
        }
    })
    // 立即获得控制权,否则要等到页面下次刷新才能拿到控制权
    await self.clients.claim()
})
// 处理fetch请求
self.addEventListener('fetch', event => {
    console.log('捕获fetch请求', event)
    const req = event.request
    // 给浏览器响应
    event.respondWith(networkFirst(req))
})
async function networkFirst(req){
    const res = (await caches.match(req))
    if(res){
        return res
    }else {
        return await fetch(req)
    }
}
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 效果

编辑  (opens new window)
  上次更新: 2022/08/11