web

PWA

Progressive Web Application

Posted by Lorry on August 25, 2018

文章字数:4000, 阅读全文大约需要:11 分钟

概述

PWA(渐进式网页应用)是谷歌推出的一个可以媲美原生app用户体验的web网页。具体涉及的技术有:

  • service worker
  • notification/push api(需翻墙)
  • manifest

优点

  • 渐进式:可以运行所有浏览器, 再支持的PWA中使用PWA功能,不支持的浏览器也能正常访问
  • 响应式:可以适配不同大小的屏幕
  • 允许离线访问:service worker的缓存机制
  • 媲美原生APP:图标,启动画面,全屏体验,独立与浏览器进程
  • seo友好:Manifest
  • 交互性强:可推送notification api
  • 安装简单:浏览器点击“添加到主屏”即可

技术点

Service Worker(以下简称SW)

类似于代理,所有浏览器发出去的http请求都会被拦截, 然后对资源进行缓存。 跟Web Worker一样,是独立于浏览器的进程,能够在后台运行

不过由于安全策略,SW只能运行在https协议,或者本地localhost/127.0.0.1开发域名下。

生命周期

xs 图片来源自:bitsofcode

一共有6个

parsed

当第一次尝试注册SW, 用户代理会实例化脚本并获取入口(entry point),如果实例化成功,就可以饮用SW对象了,这个对象会包含SW的状态信息和scope信息。对应的是register方法

if('serviveWorker' in navigator) {
    navigator.serviceWorker.register('./sw.js')
    .then((registration) => {
        console.log('registration successed')
    })
    .catch((err) => {
        console.log('registration failed')
    })
}

需要注意的是, 成功注册并不代表已经完成安装或激活, 仅仅是该脚本被成功的实例化而已, 它跟document同域。一旦实例化之后便向下一个状态installing转移

installing

UA将尝试安装SW,状态也将变为 installing , 对registration.installing状态当子对象进行检查

navigator.serviceWorker,register('./sw,js')
.then((registration) => {
    // SW state is Installing

})

在installing状态中, install时间会被出发, 可以缓存静态文件

self.addEventListener('install', (e) => {
    e.waitUntil(
        caches.open(currentCacheName).then((cache) => cache.addAll(arrayOfFilesToCache))
    )
})

如果有event。waitUntil方法存在, 那么installing事件在promise resolve掉之前状态不会发生改变, 如果Promise被拒绝了, install事件将会失败,SW也将会被丢弃。

self.addEventListener('install', (e) => {
    e.waitUntil(return Promise.reject());// failure
})

installed/waiting

当成功安装后, SW便会更改为此状态,在此状态中,SW是可用, 但是还是没有被激活, 还没有在document的控制里, 而是正在等待当前的worker获取控制权 在registration对象中, 可通过registration.waiting来检查此状态

navigator.serviceWorker.register('sw.js')
.then((registration.waiting) => {
    // sw is waiting
})

此状态是一个通知app使用者可以更新新的version,或自动更新。

activating

此状态只在下列场景中触发:

  • 如果当前没有活动的worker
  • 如果self。skipWaiting()方法被调用
  • 如果用户已经导航出本页面,释放之前活跃的worker
  • 如果又一个具体的过期时间被传入, 释放之前活跃的worker

在此状态中, activate事件被触发, 在典型的触发事件,通常用来清除old的caches

self.addEventListener('activate', (e) => {
    e.waitUntil(
        caches.keys().then((cacheNames) => {
            return Promise.all(cacheNames.filter((cacheName) => {
                return cacheName !== currentCacheName;
            }).map((cacheName) => {
                return caches.delete(cacheName);
            }))
        })
    )
})

与之前的installing一样, waitUntil只在所有的promise都被resolve掉之后状态才发生转移,否则为failed并且该SW被抛弃

activated

如果激活成功, SW将会转移到该状态,它将成为一个活跃的并且拥有document所有控制权的worker, 在registration对象中,可以通过registration.active检测

navigator.serviceWorker.register('sw/js')
.then((registration) => {
    if (registration.active) {
        // SW is active
    }
})

当为active时,可以处理两个函数事件: 1 fetch 2 message

self.addEventListener('fetch', (e) => {
    // fetch handler
})
self.addEventListener('message', (e) => {
    // message handler
})

Redundant

这是最后一个生命周期, 前面所说当会被丢弃的都会到这里来

  • install失败
  • activate失败
  • 一个新的SW作为活跃的SW替换原有的.

前两个是可以在chrome devTool中查看:

需要注意的是, 如果之前的SW已经处在激活状态, 并且对document有着控制权, 那么新的SW也对此document有控制权.

Manifest文件

添加到主屏将会使用到manifest.在之前说到对优点的第四条: 媲美原生APP:图标,启动画面,全屏体验,独立与浏览器进程都需要manifest的支持.

一个典型的manifest.json文件会位于网站的根目录.

{
  // PWA名
  "name": "Todo",
  // 一系列适应不同型号设备的icon
  "icons": [
    {
      "src": "android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "android-chrome-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  // 主题颜色, 将会影响手机状态栏的颜色
  "theme_color": "#ffffff",
  // 背景颜色.
  "background_color": "#ffffff",
  // 启动地址
  "start_url": "src/index",
  // 表示类似原生APP全屏方式启动
  "display": "standalone"
}

以下地址可以在线生成manifest.json文件

到目前为止, IOS尚未支持PWA, 但是可以通过由苹果公司提供到HTML标签使web页面获得和原生应用类似到效果

// 应用图标
<link rel="apple-touch-icon" href="apple-touch-icon.png">
// 启动画面
<link rel="apple-touch-startup-image" href="launch.png">
// 应用名称
<meta name="apple-mobile-web-app-title" content="Todo-PWA">
// 全屏效果
<meta name="apple-mobile-web-app-capable" content="yes">
// 设置状态栏颜色
<meta name="apple-mobile-web-app-status-bar-style" content="#fff">