前端应该知晓的PWA

1、传统web 应用

当前web应用在移动时代并无达到其在桌面设备上流行的程度,下面有张图来对比与原生应用之间的差异。
https://user-gold-cdn.xitu.io/2018/5/9/163452d596bad18f?w=1920&h=1080&f=png&s=452169
究其缘由,无外乎下面不可避免的几点:css

  • 移动设备网络限制-不可忽略的加载时间
  • web应用依赖于浏览器做为入口
  • 体验与原生的差距

假如能解决以上的几点,对web app 来讲会有多大的提高能够想象。html

2、PWA是什么

PWA 全称Progressive Web Apps(渐进式Web应用程序),旨在使用现有的web技术提供用户更优的使用体验。
基本要求java

  • 可靠(Reliable)
    即便在不稳定的网络环境下,也能瞬间加载并展示
  • 快速响应(Fast)
    快速响应,而且有平滑的动画响应用户的操做
  • 粘性(Engaging)
    像设备上的原生应用,具备沉浸式的用户体验,用户能够添加到桌面

PWA 自己强调渐进式,并不要求一次性达到安全、性能和体验上的全部要求,开发者能够经过 PWA Checklist 查看现有的特征。 git

除以上的基准要求外,还应该包括如下特性:github

  • 渐进式 - 适用于全部浏览器,由于它是以渐进式加强做为宗旨开发的
  • 链接无关性 - 可以借助 Service Worker 在离线或者网络较差的状况下正常访问
  • 相似应用 - 因为是在 App Shell 模型基础上开发,由于应具备 Native App 的交互和导航,给用户 Native App 的体验
  • 持续更新 - 始终是最新的,无版本和更新问题
  • 安全 - 经过 HTTPS 协议提供服务,防止窥探和确保内容不被篡改
  • 可索引 - 应用清单文件和 Service Worker 可让搜索引擎索引到,从而将其识别为『应用』
  • 粘性 - 经过推送离线通知等,可让用户回流
  • 可安装 - 用户能够添加经常使用的 webapp 到桌面,免去去应用商店下载的麻烦
  • 可连接 - 经过连接便可分享内容,无需下载安装

看起来有点眼花缭乱,这又是一个新的飞起的轮子吗?这里重申一下,PWA背后不是一种新的技术,而是集合当前多种web技术的一种集合。分别利用各自的功能来完成渐进式的总体需求。下面就沿着前面提出的问题分别了解一下相关技术web

3、技术组成

由如下几种技术构成:chrome

  • App Manifest
  • Service Worker
  • Notifications API
  • Push API

其中Service Worker是PWA技术的关键,它们可让app知足上面的三基准。其余技术则是锦上添花,让app更加的强大。json

3.1 service worker背景

离线缓存背景

针对网页的体验,从前到后都作了不少努力,极力去下降响应时间,这里就不表述多样的技术手段。
另外一个方向的就是缓存,减小与服务器非必要的交互,不过对于离线的状况下浏览器缓存就无力了,
这样离线缓存的需求就出现了。数组

离线缓存的历程

web应用在离线缓存发展的过程当中也不是一簇而就的,经历了逐渐完善的过程。
初期的解决方案是AppCache
然而,事实证实这是一个失败的尝试,缺陷太多,已经被废弃了。具体能够查看Application Cache is a douchebag
可是方向仍是正确的,那就继续孜孜不倦的探索。promise

workers

持久化先放一边,来谈谈另外一个问题
基于浏览器中的 javaScript 单线程的现实逐渐不能知足现代web需求的现状,例如耗时的计算,用户的交互显然会受影响。
为了将这些耗时操做从主线程中解放出来,早期W3C新增了一个Web Worker 的 API,能够脱离主线程单独执行,而且能够与主线程交互。
不过Web Worker是临时性的依赖于建立页面 ,不能知足咱们持久化的需求。
冲着这个目标,下面就比较容易解决了,搞个能持久存在的就好了。
在Web Worker的基础上,W3C新增了service worker来知足咱们持久化的需求。
其生命周期与页面无关,关联页面未关闭时,它也能够退出,没有关联页面时,它也能够启动
功能

Service Worker虽然知足了离线缓存来,其功能可不只仅局限于此。 能够提供

  • 丰富的离线体验,
  • 周期的后台同步,
  • 消息推送通知,
  • 拦截和处理网络请求,
  • 管理资源缓存

这些正好也是PWA的目的,因此说Service Worker是PWA的关键技术。

前提条件

Service Worker 出于安全性和其实现原理,在使用的时候有必定的前提条件。

  • 因为 Service Worker 要求 HTTPS 的环境
    固然通常浏览器容许调试 Service Worker 的时候 host 为 localhost 或者 127.0.0.1
  • Service Worker 的缓存机制是依赖 Cache API (略过)
  • 依赖 HTML5 fetch API(略过)
  • 依赖 Promise 实现

由上可知,不是全部的浏览器都支持的,支持状况大概以下:

https://user-gold-cdn.xitu.io/2018/5/9/163452e20751393f?w=2328&h=976&f=png&s=215696
iOS 内的全部的浏览器都基于 safari,因此iOS要在11.3以上
IE是放弃支持了,不过Edge好歹支持了。

3.2 Cache

Cache是Service Worker衍生出来的API,配合Service Worker实现对资源请求的缓存。
不过cache并不直接缓存字符串,而是直接缓存资源请求(css、js、html等)。
cache也是key-value形式,通常来讲key就是request,value就是response

  • caches.open(cacheName) 打开一个cache
  • caches是global对象,返回一个带有cache返回值的Promise
  • cache.keys() 遍历cache中全部键,获得value的集合
  • cache.match(Request|url) 在cache中匹配传入的request,返回Promise;
  • cache.matchAll只有第一个参数与match不一样,须要一个request的数组,固然返回的结果也是response的数组
  • cache.add(Request|url) 并非单纯的add,由于传入的是request或者url,在cache.add内部会自动去调用fetch取回request的请求结果,而后才是把response存入cache;
  • cache.addAll相似,一般在sw install的时候用cache.addAll把全部须要缓存的文件都请求一遍
  • cache.put(Request, Response) 这个至关于cache.add的第二步,即fetch到response后存入cache
  • cache.delete(Request|url) 删除缓存

3.3 注册Service Worker

注册即声明sw文件的位置,显然应该在主js中引入。大概以下:

//基于promise
function registerServiceWorker(){
    // 注册service worker
    return navigator.serviceWorker.register('./sw1.js').then(registration => {
        console.log('注册成功');
        // 返回
        return registration;
    })
    .catch(err => {
        console.error('注册失败', err);
    });
}
window.onload = function () {
    //是否支持
    if (!('serviceWorker' in navigator)) {
        return;
    }
    registerServiceWorker()
}

3.4 生命周期

Service worker 有一个独立于web 页面的生命周期。
若是在网站上安装 serice worker ,你须要注册,注册后浏览器会在后台安装 service worker。而后进入下面的不一样阶段。
激活以后,service worker 将控制全部的页面,归入它的范围,不过第一次在页面注册 service worker 时不会控制页面,直到它再次加载。
service worker 生效以后,它会处于下面两种状态之一:

  • service worker 终止来节省内存,
  • 页面发起网络请求后,它将处理请求获取和消息事件。

由上图看知,分为这么几个阶段:

  • Installing
    发生在 Service Worker 注册以后,表示开始安装,触发 install 事件回调指定一些静态资源进行离线缓存
  • Installed
    Service Worker 已经完成了安装,而且等待其余的 Service Worker 线程被关闭。
  • Activating
    在这个状态下没有被其余的 Service Worker 控制的客户端,容许当前的 worker 完成安装
  • Activated
    在这个状态会处理 activate 事件回调 (提供了更新缓存策略的机会)。并能够处理功能性的事件 fetch (请求)、sync (后台同步)、push (推送)
  • Redundant
    被替换,即被销毁

了解声明周期实际上是为了咱们在不一样时间段去监听事件来完成相应操做。对PWA来讲主要两个事件。

  • install 事件回调:

event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。
self.skipWaiting():self 是当前 context 的 global 变量,执行该方法表示强制当前处在 waiting 状态的 Service Worker 进入 activate 状态。

  • activate 回调:

event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。
self.clients.claim():在 activate 事件回调中执行该方法表示取得页面的控制权, 这样以后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本再也不控制着页面,以后会被中止。

const CURCACHE = 'CURCACHE_test_1'
const RUNTIME = 'runtime';
const CURCACHE_URLS = [
    './',
    '/asset/sw.jpg',
    'index.js'
]
self.addEventListener('install',e=>{
    e.waitUntil(
      //存储缓存路径对应的资源
        caches.open(CURCACHE).then(cache=>{
            cache.addAll(CURCACHE_URLS)
        }).then(
            self.skipWaiting()
        )
    )
})
 
 
   
  //代理请求,使用缓存,请求发送以前
  self.addEventListener('fetch', e => {
    e.respondWith(
      //缓存是否匹配 
      caches.match(e.request).then(function(response) {
        if (response != null) {
          //命中缓存返回缓存,结束请求
          return response
        }
        //未命中缓存,正常请求
        return fetch(e.request.url)
      })
    )
  });

更新service worker
service worker 更新步骤以下:

  • 更新 service worker 的文件
    网页打开时服务器会进行对比,保持最新
  • 新的 service worker 启动install
  • 当前页面生效的依然是老的service worker,新的 service worker 会进入 “waiting” 状态。
  • 页面关闭以后,老的 service worker 会被干掉,新的 servicer worker 接管页面
  • 新的 service worker 生效后会触发 activate 事件。
const CURCACHE = 'precache_test_1'
//假设上个版本的key为precache_test_2 反正不等于CURCACHE
self.addEventListener('activate', e => {
  e.waitUntil(
      //遍历当前缓存keys
      caches.keys().then(cacheNames=>{
        return Promise.all(
          cacheNames.map(function(cacheName) {
            //是否等于当前key,保留本身
            if (cacheName !== CURCACHE) {
              return caches.delete(cacheName);
            }
          })
    )}).then(() => self.clients.claim())
 )
})

这样一个简单的service worker离线缓存完成了。控制台能够看到,来源是service worker

163452d5972592d3?w=2548&;h=464&f=png&s=165863
关闭网络以后再次访问,能够一样获得上面的结果,而且sw.js请求未能拿到,可是不影响,旧的文件依然在,这里证实了每次都回去对比sw文件以确保更新
163452d596d6984a?w=2456&h=586&f=png&s=196529
到这里,离线缓存就实现了。

4、添加到主屏幕

容许将站点添加至主屏幕,是 PWA 提供的一项重要功能。这样就不用再依赖于浏览器做为平台,符合移动端的用户习惯。

manifest.json

须要 manifest.json 文件去配置应用的图标、名称等基本信息以下:

{
    //被提示安装应用时出现的文本
    "name": "PQJ-PWA",
    //添加至主屏幕后的文本
    "short_name":"PQJ",
    "description": "测试demo",
    //添加以后,启动地址
    "start_url": "/index.html",
    //图标信息
    "icons": {
      "128": "/asset/sw.jpg"
    },
    "developer": {
      "name": "pqj",
      "url": ""
    },
    "display": "standalone",
    "background_color": "#287fc5",
    "theme_color": "#fff",
    "permissions": {
        "desktop-notification": {
          "description": "Needed for creating system notifications."
        }
      }
}

而后以以下方式在html中引入

<link rel="manifest" href="/mainfest.json" />

这样完成以后,移动端安卓使用chrome(亲测),首次访问时会提示是否容许安装到主屏幕,以应用icon的形式出现。
图片和文字即由配置决定。

5、消息通知

消息通知也是使用service worker的通知功能进行的,容许服务器想用户发生通知,而非用户主动请求才去响应某些行为。
正常的通知逻辑须要服务器来参与实现,此次展现只实现功能。

  • 首先申请通知权限
  • 注册service worker
  • 处理逻辑,发送通知
function getPermission(){
    return new Promise((resolve, reject) => {
        //权限获取
        const permissionPromise = Notification.requestPermission(result => {
            resolve(result);
        });
    }).then(result => {
            //判断条件
            if (result === 'granted') {
                execute();
            }
            else {
                console.log('no permission');
            }
        });
}

发送通知

function execute() {
    // 容许以后执行
    registerServiceWorker().then(registration => {
        // 通知
        registration.showNotification('Hello World!');
    });
}

结束语

参考文档

https://lavas.baidu.com/doc
https://developer.mozilla.org/zh-CN/Apps/Progressive

至此,本文介绍就结束了,更多请参考实例虽然PWA目前来看,面对的限制还不少,可是也能够看出web组织在更好的提高web应用方向上作的努力。正如一直提到的那句话,将来可期。目前国内百度这方面作的比较成熟,新浪微博已经有了pwa 测试版。

相关文章
相关标签/搜索