下一代 Web 应用应用模型Progressive Web App(PWA)- Service Worker

背景

Web 应用的现状html

  1. 网络资源下载带来的网络延迟
  2. Web 应用依赖于浏览器做为入口
  3. 没有很好的离线使用方案
  4. 没有好的消息通知方案
  5. ….

针对以上问题,结局方案出现了PWA前端

PWA简介

PWA全称Progressive Web App,即渐进式WEB应用。 一个 PWA 应用首先是一个网页, 能够经过 Web 技术编写出一个网页应用. 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能web

PWA主要特色

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

PWA特色的实现

  • 可靠-离线缓存- Service Worker
  • 体验-web 存储- App Shell 模型
  • 粘性-吸引留住用户(添加到主屏幕和网络推送通知)- manifest.json

Service Worker

Service Worker 是浏览器在后台独立于网页运行的脚本,它打开了通向不须要网页或用户交互的功能的大门。 Service Worker从英文翻译过来就是一个服务工人,服务于前端页面的后台线程,基于Web Worker实现。有着独立的js运行环境,分担、协助前端页面完成前端开发者分配的须要在后台悄悄执行的任务。编程

客户端访问,经过Service Worker 服务,判断请求内容从哪里取的,若是缓存中存在,直接 取缓存,不然就走网络

Service Worker功能

  • 推送通知 — 激活沉睡的用户,推送即时消息、公告通知,激发更新等。如web资讯客户端、web即时通信工具、h5游戏等运营产品。
  • 离线缓存 — 可编程拦截代理请求和返回,缓存文件,缓存的文件能够被网页进程取到(包括网络离线状态),将H5应用中不变化的资源或者不多变化的资源长久的存储在用户端,提高加载速度、下降流量消耗、下降服务器压力
  • 事件同步 — 确保web端产生的任务即便在用户关闭了web页面也能够顺利完成。如web邮件客户端、web即时通信工具等。
  • 定时同步 — 周期性的触发Service Worker脚本中的定时同步事件,可借助它提早刷新缓存内容。如web资讯客户端

Service Worker特性

  • Service Worker 是一种可编程网络代理,让您可以控制页面所发送网络请求的处理方式
  • 它是一种 JavaScript Worker,没法直接访问 DOM
  • 必须在 HTTPS 环境下才能工做
  • Service Worker 普遍地利用了 promise,异步实现
  • Service Worker 在不用时会被停止,并在下次有须要时重启, Service Worker线程中不能保存须要持久化的信息

浏览器支持状况

看上图能够看出来,目前浏览器的支持状况仍是很可观的,基本上市场占比很大的浏览器目前都是支持的,可喜可贺。

查看当前页面是否有Service Worker

咱们打开浏览器的控制台,查看Service Workers ,会给咱们展现出全部已经支持Service Workers 的网站

Service Worker 生命周期

主要包含六种状态 解析成功(parsed),正在安装(installing),安装成功(installed),正在激活(activating),激活成功(activated),废弃(redundant)。json

  • 解析成功(Parsed):首次注册 Service Worker 时,浏览器解决脚本并得到入口点,若是解析成功,就能够访问到 Service Worker 注册对象(registration object)
  • 正在安装(Installing):解析完成后,浏览器会试着安装,进入下一状态,“installing”,在 installing 状态中,Service Worker 脚本中的 install 事件被执行
  • 安装成功/等待中(Installed/Waiting):若是安装成功,Service Worker * 进入安装成功(installed)(也称为等待中[waiting])状态
  • 正在激活(Activating):处于 waiting 状态的 Service Worker,在如下之一的状况下,会被触发activating 状态:
    • 当前已无激活状态的 workerService
    • Worker 脚本中的 self.skipWaiting() 方法被调用
    • 用户已关闭 Service Worker 做用域下的全部页面,从而释放了此前处于激活态的 worker
    • 超出指定时间,从而释放此前处于激活态的 worker
  • 激活成功(Activated):就能够应对事件性事件 —— fetch 和 message
  • 废弃(Redundant):
    • installing 事件失败
    • activating 事件失败
    • 新的 Service Worker 替换其成为激活态 worker 咱们看一下在代码中是如何体现的
// main.js
if ('serviceWorker' in navigator) {
    window.addEventListener('load', function () {
        navigator.serviceWorker.register('/sw.js')
            .then(function (registration) {

                // 注册成功
                console.log('ServiceWorker registration successful with scope: ', registration.scope);
                if (registration.installing) {
                    // Service Worker is Installing
                    console.log('Service Worker is Installing')
                } else if(registration.waiting) {
                    // 这是更新新版本或自动更新缓存的绝佳时机
                    /*
                    * 当前没有激活的 worker
                    * 若是在 Service Worker 的脚本中 self.skipWaiting() 被调用
                    * 若是用户访问其余页面并释放了以前激活的 worker
                    * 在一个特定的时间过去后,以前一个激活的 worker 被释放
                    */
                    console.log('Service Worker is Waiting')
                } else if(registration.active) {
                    // 激活成功, Service Worker 是一个能够彻底控制网页的激活 worker
                    console.log('Service Worker is active')
                }
            })
            .catch(function (err) {

                // 注册失败:(
                console.log('ServiceWorker registration failed: ', err);
            });
    });
}

window.onload = function() {
    document.body.append('PWA!')
}
复制代码

Service Worker 事件

  • Install:Service Worker 注册并安装完成后,对站点离线访问最关键的资源 URL 列表,它们一般也是关键请求链包含的文件,并将其缓存进 caches 中
  • Activate:安装完成并激活后,一个管理老旧缓存的好地方, 在通常 PWA 中,咱们能够结合版本号和缓存名,及时删除过时缓存
  • Fetch:页面受控后全部请求会被 Service Worker “劫持”,根据资源类型动态返回缓存数据或请求新数据

在代码中的具体实现segmentfault

// sw.js
/*
   sw.js 控制着页面资源和请求的缓存
*/

// 监听 service worker 的 install 事件,
// 对站点离线访问最关键的资源 URL 列表,它们一般也是关键请求链包含的文件,并将其缓存进 caches 中
self.addEventListener('install', function (e) {
    // 若是监听到了 service worker 已经安装成功的话,就会调用 event.waitUntil 回调函数
    e.waitUntil(
        caches.open('v1').then(cache => {
            // 经过 cache 缓存对象的 addAll 方法添加 precache 缓存
            return cache.addAll([
                '/main.js',
                '/index.html',
                '/'
            ]);
        }).then(function() {
          // 跳过waiting,直接进入active
          console.log('Skip waiting!')
          return self.skipWaiting()
        })
    );
});

/*
    事件回调是一个管理老旧缓存的好地方
    在通常 PWA 中,咱们能够结合版本号和缓存名,及时删除过时缓存
*/
self.addEventListener('activate', function(e) {
  const cacheStorageKey = 'v1'
  e.waitUntil(
    Promise.all(
      caches.keys().then(cacheNames => {
        return cacheNames.map(name => {
          if (name !== cacheStorageKey) {
            return caches.delete(name)
          }
        })
      })
    ).then(() => {
      console.log('Clients claims.')
      // 经过clients.claim方法,更新客户端上的server worker
      return self.clients.claim()
    })
  )
})

/*
对不一样资源类型应用不一样的请求/缓存策略
看看这些请求所请求的文件在咱们的缓存里有没有,有的话就直接从缓存里拿,不用下载了。
这也是PWA最重要的功能之一
*/
self.addEventListener('fetch', function (event) {
    event.respondWith(
        caches.match(event.request)
        .then(function (response) {
            // 检测是否已经缓存过
            if (response) {
                return response;
            }

            var fetchRequest = event.request.clone();

            return fetch(fetchRequest).then(
                function (response) {
                    // 检测请求是否有效
                    if (!response || response.status !== 200 || response.type !== 'basic') {
                        return response;
                    }

                    var responseToCache = response.clone();

                    caches.open('v1')
                        .then(function (cache) {
                            cache.put(event.request, responseToCache);
                        });

                    return response;
                }
            );
        })
    );
});

// 推送消息
self.addEventListener('message', function(event) {
  // Do stuff with postMessages received from document
    console.log("SW Received Message: " + event.data);
});

复制代码

参考文档

相关文章
相关标签/搜索