PWA渐进式Web应用

Progressive Web App,简称 PWA,是提升 Web App 的体验的一种新方法,能给用户原生应用的体验。

目录:

1、PWA 基本介绍

PWA 全称 Progressive Web App,即渐进式 WEB 应用

2、PWA 核心技术揭秘

  1. Web app manifest
  2. Service worker
  3. Promise / async / await
  4. Fetch api
  5. Cache storage
  6. 常见的缓存策略
  7. notification
  8. test

3、PWA 项目实战

内容:

1.PWA 基本介绍

  1. PWA:渐进式 Web 应用,MDN 地址:https://developer.mozilla.org/zh-CN/docs/Web/Progressive_web_apps
  2. Progressive Web App,简称 PWA,是提升 Web App 的体验的一种新方法,能给用户原生应用的体验。
  3. PWA 运用现代的 Web APP 以及传统的渐进式增强策略来创建跨平台 Web 应用程序。
  4. PWA 能做到原生应用的体验不是特指某一项技术,而是经过应用一些新技术进行改造
  5. 只要你拥有一个 Web App,那么 PWA 的旅程就开始了
  6. 现在 Vue 和 React 的脚手架中都已经集成了 PWA 功能

2.PWA 的优势

  1. 渐进式
    适用于所有浏览器,因为它是以渐进式增强作为宗旨开发的。
  2. 流畅
    能够借助 Service Worker 在离线或者网络较差的情况下进行正常访问。
  3. 可安装
    用户可以添加常用的 webApp 到桌面,免去去应用商店下载的麻烦
  4. 原生体验
    可以和 app 一样,拥有首屏加载动画,可以隐藏地址栏等沉浸式体验。
  5. 黏性
    通过推送离线通知等,可以让用户回流。

3.PWA 核心技术揭秘

1.Web App manifest:应用程序清单

  1. 基本介绍

    Web App manifest 可以让网站安全安装到设备的主屏幕,而不需要用户
    通过应用商店下载。
    Web App manifest:在一个 JSON 文本文件中提供有关应用程序的信息
    (如名称、作者、图标和描述)
    传统的 Web App 入口:1、网址 2、书签、收藏夹 3、直接搜索

    Web App manifest:

    • 可以添加到桌面,有唯一的图标和名单
    • 有启动时界面,避免生硬的过渡
    • 隐藏浏览器相关的 UI,比如地址栏等
  2. 使用步骤

    • 在项目根目录下创建一个 manifest.json 文件。
    • 在 index.html 中引入 manifest.json
    • 在 manifest.json 文件中提供常见的配置。
    • 需要在 https 协议或者http://localhost 下访问项目
  3. 常见配置:

    • name:用于指定应用的名称,用户安装横幅提示的名称,和启动动画里的文字。
    • short_name:应用的端名称,用于主屏幕显示
    • start_url:指定用户从设备启动应用程序时加载的 URL,可以是绝对路径和相对路径
    • icons:用于指定可在各种环境中用做应用程序图标的图像对象数组,144*144
    • background_color:用户指定启动动画的背景色
    • theme_color:用于指定应用程序的主题颜色
    • display:用于指定 app 的显示模式
    • fullscreen:全屏模式,所有可用的显示区域都被使用,并且不现实状态栏
    • standalone 让这个应用看起来像一个独立的应用程序,包括具有不同的窗口,在应用程序启动器中拥有自己的图标等。
    • minimal-ui 该应用程序将看起来像一个独立的应用程序,但会有浏览器地址栏。

2. Service Worker

  1. 基本介绍

    • 一个标准的 PWA 程序,必须包含 3 个部分
      manifest.json
      service worker
      https 服务器或者http://localhost

    • W3C 组织早在 2014 年 5 月就提出过 Service Worker 这样一个 html5 API,主要用来做持久的离线缓存。

    • 前端有很多性能优化的手段:CDN、CSS Sprite、文件的合并压缩、异步加载、资源缓存等等,这
      些手段都是用来做性能优化的,但是如果断网了,会发生什么?

    • service worker 允许 web 应用在网络环境比较差或者是离线环境下依然可以使用

    • service worker 可以极大的提升 web app 的用户体验

    • service worker 是一个独立的 worker 线程,独立于当前网页链接,是一种特殊的 web worker

    • Web Worker 是临时的,每次做的事情的结果还不能被持久存下来,如果下次有同样的复杂操作,还得
      费时间的重来一遍

    • 一旦被 install,就永远存在,除非手动 unregister

    • 用到的时候可以直接唤醒,不用的时候自动休眠

    • 可编程拦截代理请求和返回,缓存文件,缓存的文件可以被网页进程取到(包括网络离线状态)

    • 离线内容开发者可控

    • 必须在 Https 环境下才能工作

    • 异步实现,内部大都是通过 Promise 实现
      PWA渐进式Web应用_第1张图片

  2. 使用步骤

    1. 在 window.onload 中注册 service worker,防止与其他资源竞争
    2. 在 navigator 对象中内置了 serviceWorker 属性
    3. serviceWorker 在老版本的浏览器中不支持,需要进行浏览器兼容 if(‘serviceWordker’ in navigator){}
    4. 注册 service worker navigator.serviceWorker.register(‘./sw.js’),返回一个 promise 对象
      PWA渐进式Web应用_第2张图片

3. Promise

  1. 基本使用

    • Promise 是异步编程的一种解决方案,比传统的解决方案—回调函数和事件—更合理更强大

    • Promise 可以链式的进行异步请求,解决了回调地狱的问题

    • Promise 常用的静态方法

        1. Promise.resolve()返回一个解析过带着给定值的 Promise 对象,如果返回值
        2. 是一个 Promise 对象,则直接返回这个 Promise 对象
        3. Promise.reject()静态函数 Promise.reject 返回一个被拒绝的 Promise 对象
        4. Promise.all()返回一个 Promise 实例,等所有 promise 对象都成功了,才会成功
        5. Promise.race()竞速,只要有一个 promise 对象成功或者失败了,结果就是成功或者失败
      

4. async/await

  1. ES2017 标准引入 async 函数,使得异步操作变得更加方便
  2. async 用于修饰一个函数,async function fn(){},await 函数会返回一个 promise 对象
  3. await 只能出现在 async 函数中,await 后面跟一个 promise 对象,用于获取 promise 对象成功的结果,
    如果不是 promise 对象,直接返回值
  4. await 后面的 promise 如果没有成功,那么会抛出异常,需要使用 try.catch 语法

5. fetch api

  1. Fetch API 提供了一个 Javascript 接口,用于访问和操纵 Http 管道的部分,例如请求和响应
  2. 在 service worker,如果想要发送请求,无法使用 XMLHttpRequest,必须使用 fetch api
  3. Fetch.api 是基于 promise 实现的
  4. request 是一个二进制数据流,需要调用 json()方法可以转换 json
  5. config 常见参数
    • body:用于设置请求体
    • headers:用于设置请求头
    • method:用于设置请求方式

6. cache storage

  1. cacheStorage 接口表示 Cache 对象的存储,配合 service worker 来实现资源的缓存
  2. caches api 类似数据库的操作
    • caches.open(cacheName).then(function(catch){}):用于打开缓存,返回一个匹配 cacheName 的 cache 对象的 promise,类似于链接数据库
    • caches.keys()返回一个 promise 对象,包括所有的缓存 key
    • caches.delete(key)根据 key 删除对应的缓存(数据库)
  3. cache 对象常用方法(单挑数据的操作)
    • cache 接口为缓存的 Request/response 提供存储机制
    • cache.put(req,res)把请求当成可以,并且把对应的响应存储起来
    • cache.add(url)根据 url 发起请求,并且把响应结果存储起来
    • cache.addAll(urls)抓去一个 url 数组,并且把结果都存储起来
    • cache.match(req)获取 req 对应的 response

7. 常见的缓存策略

Cache only
PWA渐进式Web应用_第3张图片
Network only
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ThkrMxuj-1579502717128)(https://m.360buyimg.com/img/jfs/t1/94162/24/7057/446668/5df89f4eE4447cca5/dfbb6edcaa343ffd.pngg)]
Cache,falling back to Network
PWA渐进式Web应用_第4张图片
Network,falling back to Cache
PWA渐进式Web应用_第5张图片
Cache & Network race
PWA渐进式Web应用_第6张图片

8. notification api

  1. Notification API 的通知接口用于向用户配置和显示桌面通知
  2. Notification.permission 可以获取当前用户的授权情况
    • default:默认的,未授权
    • Denied:拒绝的,如果拒绝了,无法再次请求授权,也无法弹窗提醒
    • Granted:授权的,可以弹窗提醒
  3. 通过 Notification.requestPermission()可以请求用户的授权
  4. 通过 new Notification(‘title’,{body:’’,icon:’’})可以显示通知
  5. 在授权通过的情况下,可以在 service worker 中显示通知 self.registration.showNotification(‘您好’,{body:’msg’})

3.PWA 项目实战

代码展示:

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <link rel="manifest" href="./manifest.json" />
    <title>PWA</title>
    <script>
      window.onload = function() {
        //serviceWorker
        // if ("serviceWorker" in navigator) {
        //   navigator.serviceWorker
        //     .register("./sw.js")
        //     .then(registration => {
        //       console.log("registration", registration);
        //     })
        //     .catch(err => {
        //       console.log(err);
        //     });
        // }

        //添加通知功能
        if (Notification.permission === "default") {
          Notification.requestPermission().then(result => {
            console.log(result);
          });
        }

        if (!window.navigator.onLine) {
          new Notification("提示", { body: "当前没有网络" });
        }

        if (window.navigator.onLine) {
          new Notification("提示", { body: "当前有网络" });
        }
      };
    </script>
  </head>
  <body>
    Hello PWA
  </body>
</html>

mainfest.json

{
    "name": "PWA例子PWA例子",
    "short_name": "PWA",
    "start_url": ".",
    "display": "minimal-ui",
    "background_color": "#f8f8f8",
    "theme_color": "#ff0000",
    "description": "PWA例子",
    "icons": [
        {
            "src": "https://storage.jd.com/tenant-image/1/bigLogo.png",
            "sizes": "144x144",
            "type": "image/png"
        },
        {
            "src": "https://storage.jd.com/tenant-image/1/bigLogo.png",
            "sizes": "168x168",
            "type": "image/png"
        },
        {
            "src": "https://storage.jd.com/tenant-image/1/bigLogo.png",
            "sizes": "192x192",
            "type": "image/png"
        }
    ]
}

sw.js

// //缓存内容
// self.addEventListener('install', event => {
//   console.log('install', event);
//   //跳过等待,直接进入activate
//   //self.skipWaiting()等待结果,才进入activate
//   event.waitUntil(self.skipWaiting());
// });

// //主要清除旧的缓存
// self.addEventListener('activate', event => {
//   //跳过等待,直接进入active
//   console.log('activate', event);
//   //表示service worker激活后,立即获取控制权
//   event.waitUntil(self.clients.claim());
// });

// //fetch事件会在请求发送的时候触发
// self.addEventListener('fetch', event => {
//   //跳过等待,直接进入active
//   self.skipWaiting('fetch', event);
// });

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

const CACHE_NAME = "cache";
//缓存内容
self.addEventListener("install", async event => {
  // //开启一个cache
  // const cache = await caches.open(CACHE_NAME);
  // //cache对象就可以存储的资源
  // //等待cache把所有资源存储起来
  // cache.addAll(['./index.html', './manifest.json', '/']);
  // await self.skipWaiting();

  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => {
      return cache.addAll(["/", "/manifest.json", "/index.html"]);
    })
  );
});

//主要清除旧的缓存
self.addEventListener("activate", async () => {
  const keys = await caches.keys();
  keys.forEach(key => {
    if (key !== CACHE_NAME) {
      caches.delete(key);
    }
  });
  await self.clients.claim();
});

//fetch事件会在请求发送的时候触发
//判断资源是否能够请求成功,如果请求成功,就响应成功的结果,如果断网,就读取caches缓存
self.addEventListener("fetch", event => {
  const req = event.request;
  const url = new URL(req.url);
  // if(url.origin !== self.origin){
  //   return
  // }
  event.respondWith(cacheFirst(req));

  // if(req.url.includes('./api')){
  //   event.respondWith(networkFirst(req))
  // }else{
  //   event.respondWith(cacheFirst(req))
  // }
});

//缓存优先
async function cacheFirst(req) {
  const cache = await caches.open(CACHE_NAME);
  const cached = await cache.match(req);
  //如果从缓存中得到
  if (cached) {
    return cached;
  } else {
    const fresh = await fetch(req);
    return fresh;
  }
}

//网络请求优先,如果我们获取到了数据,应该往缓存中存一份
async function networkFirst(req) {
  const cache = await caches.open(CACHE_NAME);
  try {
    const fresh = await fetch(req);
    cache.put(req, fresh.clone());
    return fresh;
  } catch (e) {
    const cached = await cache.match(req);
    return cached;
  }
}

你可能感兴趣的:(前端,移动端,PWA)