Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ServiceWorker #3

Open
itboos opened this issue Jan 25, 2018 · 1 comment
Open

ServiceWorker #3

itboos opened this issue Jan 25, 2018 · 1 comment

Comments

@itboos
Copy link
Owner

itboos commented Jan 25, 2018

https://www.zybuluo.com/yqlar/note/908619

#Service Worker


1、Service Worker 简介

在页面中注册并安装成功后,运行于浏览器后台,不受页面刷新的影响,可以监听和截拦作用域范围内所有页面的 HTTP 请求。


###1.1 Service Worker 特点

  • 网站必须使用 HTTPS。除了使用本地开发环境调试时(如域名使用 localhost)
  • 运行于浏览器后台,可以控制打开的作用域范围下所有的页面请求
  • 单独的作用域范围,单独的运行环境和执行线程
  • 不能操作页面 DOM。但可以通过事件机制来处理

###1.2 兼容性
https://caniuse.com/#feat=serviceworkers

'https://lzw.me/wp-content/uploads/2017/03/sw-caniuse.png'


###1.3 PWA
谷歌给以 Service Worker API 为核心实现的 web 应用取了个高大上的名字:Progressive Web Apps(PWA,渐进式增强 WEB 应用),并且在其主要产品上进行了深入的实践。那么,符合 PWA 的应用特点是什么?以下为来自谷歌工程师的解答。

Progressive Web Apps 是:

  • 渐进增强 – 能够让每一位用户使用,无论用户使用什么浏览器,因为它是始终以渐进增强为原则。
  • 响应式用户界面 – 适应任何环境:桌面电脑,智能手机,笔记本电脑,或者其他设备。
  • 不依赖网络连接 – 通过 Service Workers 可以在离线或者网速极差的环境下工作。
  • 类原生应用 – 有像原生应用般的交互和导航给用户原生应用般的体验,因为它是建立在 app shell model 上的。
  • 持续更新 – 受益于 Service Worker 的更新进程,应用能够始终保持更新。
  • 安全 – 通过 HTTPS 来提供服务来防止网络窥探,保证内容不被篡改。
  • 可发现 – 得益于 W3C manifests 元数据和 Service Worker 的登记,让搜索引擎能够找到 web 应用。
  • 再次访问 – 通过消息推送等特性让用户再次访问变得容易。
  • 可安装 – 允许用户保留对他们有用的应用在主屏幕上,不需要通过应用商店。
  • 可连接性 – 通过 URL 可以轻松分享应用,不用复杂的安装即可运行。

##2、 Service Worker 的生命周期

install -> installed -> actvating -> Active -> Activated -> Redundant


##3、 使用 Service Worker

首先要注意如下两点:

A. 使用 HTTPS 访问

B. 基础知识了解:

Promise: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
Promise 是 ES6 新增的规范,主要用于 javasCript 的异步处理。

Fetch API: https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API
Fetch API 用于 HTTP 请求处理,可以替代 XMLHttpRequest 实现异步请求(ajax),但功能上更为完善。

Cache API: https://developer.mozilla.org/zh-CN/docs/Web/API/Cache
Cache API 用于对 HTTP 请求进行缓存管理,是在 ServiceWorker 的规范中定义的,一般也跟 ServiceWorker 一起使用,是实现离线应用的关键。但是 Cache API 又不依赖于 Service Worker,可以单独使用


###3.1、 注册
在网站页面上注册实现 Service Worker 功能逻辑的脚本。例如注册 /sw/sw.js 文件,参考代码:

if ('serviceWorker' in navigator) {

    navigator.serviceWorker.register('/sw/sw.js', {scope: '/'})
    
        .then(registration => console.log('ServiceWorker 注册成功!作用域为: ',registration.scope))
        
        .catch(err => console.log('ServiceWorker 注册失败: ', err));
}

注意:
Service Worker 的注册路径决定了其 scope(范围) 默认作用范围。示例中 sw.js 是在 /sw/ 路径下,这使得该 Service Worker 默认只会收到 /sw/ 路径下的 fetch 事件。如果存放在网站的根路径下,则将会收到该网站的所有 fetch 事件。
如果希望改变它的作用域,可在第二个参数设置 scope 范围。示例中将其改为了根目录,即对整个站点生效。
另外应意识到这一点:Service Worker 没有页面作用域的概念,作用域范围内的所有页面请求都会被当前激活的 Service Worker 所监控。


###3.2、 安装

// 缓存文件的版本
var CACHE_VERSION = 'v1.0-2017/9/20-004';   

// 需要初次进入立即缓存的文件
var CACHE_FILES = [                     
    '/index.html',
    // '/static/css/app.css',
    // '/static/js/app.js',
    // '/static/js/1.js',
    // '/static/js/9.js',
];
// 监听 install (安装)事件
self.addEventListener('install', function (event) {

// 此处 event.waitUntil 是等待 CACHE_FILES 中的文件全部加载完毕之后再安装
    event.waitUntil(
    
        // 打开对应名称的缓存
        caches.open(CACHE_VERSION).then (function (cache) {
        
            // 将列表中的所有文件都缓存起来
            // 如果没有全部缓存完毕,则视为安装失败
            return cache.addAll(CACHE_FILES);
        })
    );
});

可以看到,示例中在文件 sw.js 内监听了 install 事件。当 sw.js 被安装时会触发 install 事件,监听该事件可执行安装时要做的事情。示例中是缓存用于离线时使用的静态资源。

需要注意的是,只有 CACHE_FILES 中的文件全部安装成功,Service Worker 才会认为安装完成。否则会认为安装失败,安装失败则进入 redundant (废弃)状态。所以这里应当尽量少地缓存资源(一般为离线时需要但联网时不会访问到的内容),以提升成功率。

安装成功后,即进入等待(waiting)或激活(active)状态。在激活状态可通过监听各种事件,实现更为复杂的逻辑需求。具体参见后文事件处理部分。

event.waitUntil 相关文档解释
Cache相关文档


###3.3、 Service Worker 的更新
如果 sw.js 文件的内容有改动,当访问网站页面时浏览器获取了新的文件,它会认为有更新,于是会安装新的文件并触发 install 事件。但是此时已经处于激活状态的旧的 Service Worker 还在运行,新的 Service Worker 完成安装后会进入 waiting 状态。直到所有已打开的页面都关闭,旧的 Service Worker 自动停止,新的 Service Worker 才会在接下来打开的页面里生效。

如果希望在有了新版本时,所有的页面都得到及时更新怎么办呢?

可以在 install 事件中执行 skipWaiting 方法跳过 waiting 状态,然后会直接进入 activate 阶段。接着在 activate 事件发生时,通过执行 clients.claim 方法,更新所有客户端上的 Service Worker。示例:

// 安装阶段跳过等待,直接进入 active

self.addEventListener('activate', function (event) {

  var cacheWhitelist = [CACHE_VERSION];
  
  event.waitUntil(
  
    caches.keys().then(keyList => {
      return Promise.all(keyList.map(key => {
      
        if (cacheWhitelist.indexOf(key) === -1) {
          return caches.delete(key);
        }
        
      }));
    })
);
});

###3.4、 fetch 事件
在安装过程中我们实现了资源缓存,安装完成后则进入了空闲阶段,此时可以通过监听各种事件实现各种逻辑。

当浏览器发起请求时,会触发 fetch 事件。

Service Worker 安装成功并进入激活状态后即运行于浏览器后台,可以通过 fetch 事件可以拦截到当前作用域范围内的 http/https 请求,并且给出自己的响应。结合 Fetch API ,可以简单方便地处理请求响应,实现对网络请求的控制。

这个功能是十分强大的。

参考下面的示例,这里实现了一个缓存优先、降级处理的策略逻辑:监控所有 http 请求,当请求资源已经在缓存里了,直接返回缓存里的内容;否则使用 fetch API 继续请求,如果是 图片或 css、js 资源,请求成功后将他们加入缓存中;如果是离线状态或请求出错,则降级返回预缓存的离线内容。

self.addEventListener('fetch', function (event) {

  var url = event.request.url;
  // 接口过滤条件
  // || url.indexOf('https://www.91kds.net/get_data') !== -1
  // 'https://cdn.bootcss.com/vue/2.4.2/vue.min.js',
  // 'https://h5cs.yingshiq.com/',

  // 各类请求过滤条件
  var val = (
              url.indexOf('http://127.0.0.1:8887/') !== -1
              || url.indexOf('https://img.yingshiq.com/') !== -1
              || url.indexOf('https://cdn.bootcss.com/') !== -1
              || url.indexOf('https://h5cs.yingshiq.com/') !== -1
              || url.indexOf('https://vodapics.91kds.com') !== -1
            );

  if (val) {
  
    //
    event.respondWith(
      caches.match(event.request).then(function (response) {
          var reqClone = event.request.clone();
          
          //
          if (response) {
            return response;
          } else {
          
            // 请求线上资源
            return fetch(reqClone).then(function (res) {
            
              //
              var resClone = res.clone();
              caches.open(CACHE_VERSION).then(function (cache) {
              
                // 缓存从线上获取的资源
                cache.put(reqClone, resClone);
              });
              
              return res;
            })
          }
        }
      )
    );
  }
});

###3.5、 push 事件

push 事件是为推送准备的。不过首先你需要了解一下 Notification API 和 PUSH API(相关链接见后文)。

通过 PUSH API,当订阅了推送服务后,可以使用推送方式唤醒 ServiceWorker 以响应来自系统消息传递服务的消息,即使用户已经关闭了页面。

推送的实现有两步:

不同浏览器需要用不同的推送消息服务器。以 Chrome 上使用 Google Cloud Messaging 作为推送服务为例,第一步是注册 applicationServerKey(通过 GCM 注册获取),并在页面上进行订阅或发起订阅。每一个会话会有一个独立的端点(endpoint),订阅对象的属性(PushSubscription.endpoint) 即为端点值。将端点发送给服务器后,服务器用这一值来发送消息给会话的激活的 Service Worker (通过 GCM 与浏览器客户端沟通)。

###3.6、 online/offline 事件

当网络状态发生变化时,会触发 online 或 offline 事件。结合这两个事件,可以与 Service Worker 结合实现更好的离线使用体验,例如当网络发生改变时,替换/隐藏需要在线状态才能使用的链接导航等。

下面是一个监听 offline 的示例:

self.addEventListener('offline', function() {
    Notification.requestPermission().then(grant => {
        if (grant !== 'granted') {
            return;
        }

        const notification = new Notification("Hi,网络不给力哟", {
            body: '您的网络貌似离线了,不过访问过的页面还可以继续打开~',
            icon: '我是图标'
        });

        notification.onclick = function() {
            notification.close();
        };
    });
});

@itboos
Copy link
Owner Author

itboos commented Jan 25, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant