PWA(progressive web app)学习

666 阅读6分钟

渐进式web app,其目的是通过各种web技术实现与原生App相近的用户体验。纵观现有 Web 应用与原生应用的对比差距,如离线缓存、沉浸式体验等等,可以通过已经实现的 Web 技术去弥补这些差距,最终达到与原生应用相近的用户体验效果。

  • 严格依赖https,保证了一定的安全性

特性

  • 安全可靠

使用 Service Work 技术实现即时下载,当用户打开应用后,页面资源的加载不再完全依赖于网络,而是使用 Service Work 缓存离线包存在本地,确保为用户提供即时可靠的体验。

  • 访问更快

首屏可以部署在服务端,节省网页请求时间,加载速度更快,拥有更平滑的动态效果和快速的页面响应。

  • 响应式界面

支持各种类型的终端和屏幕。

  • 沉浸式体验

在支持 PWA 的浏览器和手机应用上可以直接将 Web 应用添加到用户的主屏幕上,无需从应用商店下载安装。从主屏幕上打开应用之后,提供沉浸式的全屏幕体验。

功能

手机应用配置(Web App Manifest) 可以通过 manifest.json 文件配置,使得可以直接添加到手机的桌面上。

离线加载与缓存(Service Worker+Cache API ) 可以通过 Service Worker + HTTPS +Cache Api + indexedDB 等一系列 Web 技术实现离线加载和缓存。

消息推动与通知(Push&Notification ) 实现实时的消息推送与通知

数据及时更新(Background Sync ) 后台同步,数据及时更新

PWA核心技术介绍

1.service Worker

服务工作线程,类似于web worker,一个独立于js主进程的独立进程。

一个服务器与浏览器之间的中间人角色,如果网站中注册了service worker那么它可以拦截当前网站所有的请求,进行判断,如需要向服务器发起请求就转给服务器,如果可以直接在缓存中拿到数据,那么就直接返回缓存不再转给服务器,从而大大提升浏览器体验。

  • 常驻内存运行
  • 代理网络请求
  • 依赖HTTPS

2. promise

service worker中就存在着大量的promise API

fetch 网络请求

catch API

支持资源的缓存系统,在没有网络的情况下可以使用缓存的资源,让 web页面依然能够运行

  • 缓存资源(css/script/image)
  • 依赖service Worker代理网络请求
  • 支持页面离线运行

Notification API

发出后台推送消息

消息推送

  • 依赖用户授权
  • 适合在service worker中推送

PWA核心技术学习

service-worker

注册serviceWorker

index.js

  <script>
      // 注册serviceWorker
      navigator.serviceWorker
        .register("./sw.js", {
          scope: "/", //这个脚本可控制的相对路径,默认是脚本本身所在的路径
        })
        .then(
          (registration) => {
            // 成功返回registration
            console.log(registration);
          },
          (err) => {
            // 失败返回err
            console.error(err);
          }
        );
    </script>

使用

在指定的serviceWorker.js中书写对应的安装及拦截逻辑.

sw.js中有以下几点
  1. 不可访问dom
  2. 不可访问window、location等
  3. service worker编程就是在于service worker的生命周期打交道
  4. self 代表当前serverworker
  5. 它设计为完全异步,同步API(如XHR和localStorage)不能在service worker中使用 以下代码都是sw.js中的
1.安装worker
  • 监听安装事件,install事件一般用来设置浏览器的离线缓存逻辑
  • install方法,会在一个新的serviceworker脚本被安装后触发,
  • 注意sw.js只要有一点点不同,浏览器就会认为是以个新的service worker,然后新的版本会被下载安装,并不会立即生效,还是上一版本的生效
  • install和activate 第一次安装会被触发,再次刷新页面,则不会被触发,因为已经被安装了。除非脚本发生变化。
self.addEventListener("install", (event) => {
  //   event.waitUntill(promise)当传入的promise完成之后才是真正的完成,他会推迟后面的activate的触发,waitUntil一般会结合一些特定的行为
  // 比如
  // self.skipWaiting() 强制停止旧的serviceworker,启动新的serviceworker
  // 以下写法可以在install新版本的之后强制停止掉旧的serviceWorker,使用新的,
  // 因为新的版本不会立即生效
  event.waitUntil(self.skipWaiting());
});
2. activate 激活serviceWorker

这一步是让serviceWorker完成安装,让所有页面收到serviceWorker的控制;或者是清除之前worker留下来的一些相关资源,比如遗留下的无用缓存

self.addEventListener("activate", (event) => {
  console.log("activate", event);
  //   让所有页面受到serviceWorker的控制
  event.waitUntil(slef.client.claim());

  //
});

以上两步之后,serviceWorker就完全的可以控制所有的页面了。

3. fetch方法

在 fetch 我们可以拦截到到全站的所有请求。 在这里我们可以通过与缓存对比,然后判断是否发送请求还是返回缓存。

self.addEventListener("fetch", (event) => {
  console.log("fetch", event);
});

cache API

有了缓存之后离线也可以看到资源 cache API 不一定要在serviceworker中使用,也可以在页面中写缓存

const Cache_Name = "Cache_v1";
self.addEventListener("install", (event) => {
  event.waitUntil(
    //
    /* 创建一个名叫Cache_V1的缓存版本 */
    caches.open("v1").then(function (cache) {
      /* 指定要缓存的内容,地址为相对于跟域名的访问路径 */
      cache.addAll(["/", "./index.css"]);
    })
  );
});

self.addEventListener("activate", (event) => {
  //   如果一个新版本的serviceWorker 那么缓存可能会发生变化
  // 需要清理不必要的缓存,activate这个位置就可以很好的清理缓存
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(cacheNames).then((cacheName) => {
        if (cacheName !== Cache_Name) {
          // 注意要return 因为caches.delete也是要返回一个promise的
          return caches.delete(cacheName);
        }
      });
    })
  );
});

// fetch到一些请求
self.addEventListener("fetch", (event) => {
  event.respondWith(
    /* 在缓存中匹配对应请求资源直接返回 */
    caches.open(Cache_Name).then((cache) => {
      return cache.match(event.request).then((response) => {
        // response 存在命中缓存,直接返回缓存
        if (response) {
          return response;
        }
        // response不存在,则发送请求得到数据,放入缓存
        return fetch(event.request).then((response) => {
          // response 是流式的需要clone一份出来
          cache.push(event.request, response.clone());
          return response;
        });
      });
    })
  );
});

serviceWorker学习引路:developer.mozilla.org/zh-CN/docs/…

notification API

通知API --用于接口用于向用户配置和显示桌面通知。 他在sw脚本中是禁止的 以下需要在页面中设置通知

let notification = new Notification(title, options)
  • title

  • 一定会被显示的通知标题

  • options 可选

  • 一个被允许用来设置通知的对象。它包含以下属性:

    • dir : 文字的方向;它的值可以是 auto(自动)ltr(从左到右), or rtl(从右到左)
    • lang: 指定通知中所使用的语言。
    • body: 通知中额外显示的字符串
    • tag: 赋予通知一个ID,以便在必要的时候对通知进行刷新、替换或移除。
    • icon: 一个图片的URL,将被用于显示通知的图标。

notification的一些属性

1. Notification.permission 只读

获取通知的授权状态

  • denied (用户拒绝了通知的显示)。
  • granted (用户允许了通知的显示)。
  • default (因为不知道用户的选择,所以浏览器的行为与 denied 时相同)。

notification 的一些方法

1. Notification.requestPermission()

用于当前页面向用户申请显示通知的权限。

2. Notification.close()

关闭通知

在sw脚本中想使用怎么办?

  1. 在页面中授权为允许 notification.requestPermission()
  2. 在sw脚本文件中通过 self.registration.showNotification() 使用