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

Service Worker理解使用 #17

Open
pfan123 opened this issue Aug 3, 2017 · 0 comments
Open

Service Worker理解使用 #17

pfan123 opened this issue Aug 3, 2017 · 0 comments

Comments

@pfan123
Copy link
Owner

pfan123 commented Aug 3, 2017

什么是 Service Worker

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

浏览器中的 javaScript 都是运行在一个单一主线程上的,在同一时间内只能做一件事情。随着 Web 业务不断复杂,我们逐渐在 js 中加了很多耗资源、耗时间的复杂运算过程,这些过程导致的性能问题在 WebApp 的复杂化过程中更加凸显出来。

Service Worker 是在新开 Web Worker进程脱离在主线程之外, 将缓存拦截处理完成后通过 postMessage 方法告诉主线程,而主线程通过 onMessage 方法得到 Web Worker 的结果反馈。Service Worker 在 Web Worker 的基础上加上了持久离线缓存能力。

Service Worker 之前也有在 HTML5 上做离线缓存的 API 叫 AppCache, 但存在很多缺点。W3C 决定 AppCache 仍然保留在 HTML 5.0 Recommendation 中,在 HTML 后续版本中移除。
Issue: https://github.com/w3c/html/issues/40open_in_new
Mailing list: https://lists.w3.org/Archives/Public/public-html/2016May/0005.htmlopen_in_new

Service Worker 功能和特性

Service Worker 的伟大使命,就是让缓存做到优雅和极致,让 Web App 相对于 Native App 的缺点更加弱化,也为开发者提供了对性能和体验的无限遐想,其含有很多特性和功能点

  • 一个独立的 worker 线程,独立于当前网页进程,有自己独立的 worker context。
  • 一旦被 install,就永远存在,除非被 uninstall
  • 需要的时候可以直接唤醒,不需要的时候自动睡眠(有效利用资源,此处有坑)
  • 可编程拦截代理请求和返回,缓存文件,缓存的文件可以被网页进程取到(包括网络离线状态)
  • 离线内容开发者可控
  • 能向客户端推送消息
  • 不能直接操作 DOM
  • 出于安全的考虑,必须在 HTTPS 环境下才能工作(允许在开发调试的 localhost 使用)
  • 异步实现,内部大都是通过 Promise 实现

Service Worker 浏览器支持情况

Service Worker 浏览器支持情况怎么样呢?参考 Can I use 可得下图:
Service Worker 浏览器支持情况

前支持的浏览器不多,而且支持的浏览器也是在试验阶段,Chrome、Firefox、Opera 支持性较好,同时 x5 andriod 支持 Service Worker

Service Worker 使用限制

if('serviceWorker' in navigator) {
  navigator.serviceWorker.register(scriptURL, options)
  .then(registration => {
    console.error('registration', registration)
  })
  .catch(err => {
    console.error('catch', err)
  })
}

Service Worker 除了 work 线程的限制外,由于可拦截页面请求,为了保证页面安全,浏览器端对 Service Worker 的使用限制也不少。

1)service worker 脚本的 URL, 不支持跨域,不允许缓存 service worker 脚本如 service-worker.js。

2)service worker 脚本的 URL, 不支持 Blob/String URLCreate service worker from Blob/String URL

3)无法直接操作 DOM 对象,也无法访问 window、document、parent 对象。可以访问 location、navigator;

4)可代理的页面作用域限制 (scope) 。默认是 service-worker.js 所在文件目录及子目录的请求可代理,可在注册时手动设置作用域范围;

5)必须在 https 中使用,允许在开发调试的 localhost 使用。

Service Worker 生命周期

Service Worker 的工作原理是基于注册、安装、激活等步骤在浏览器 js 主线程中独立分担缓存任务的,那么我们如何在这些 API 自身一系列的操作中进行一些我们自己想让 worker 干的事情呢?

这里我们需要了解一下 Service Worker 的生命周期的概念,这有利于我们学会在各个生命周期的阶段进行有目的性的回调,让我们自定义的工作在 Service Worker 中正确有效的开展下去。MDN 给出了详细的 Service Worker 生命周期图:

Service Worker 生命周期

我们可以看到生命周期分为这么几个状态 安装中, 安装后, 激活中, 激活后, 废弃

  • 安装( installing ):这个状态发生在 Service Worker 注册之后,表示开始安装,触发 install 事件回调指定一些静态资源进行离线缓存。
    install 事件回调中有两个方法:

  • event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。

  • self.skipWaiting():self 是当前 context 的 global 变量,执行该方法表示强制当前处在 waiting 状态的 Service Worker 进入 activate 状态。

  • 安装后( installed ):Service Worker 已经完成了安装,并且等待其他的 Service Worker 线程被关闭。

  • 激活( activating ):在这个状态下没有被其他的 Service Worker 控制的客户端,允许当前的 worker 完成安装,并且清除了其他的 worker 以及关联缓存的旧缓存资源,等待新的 Service Worker 线程被激活。

Service Worker 文件

对于浏览器来说,Service Worker 是一个独立于 js 主线程的一种 Web Worker 线程,一个独立于主线程的 Context,但是面向开发者来说 Service Worker 的形态其实就是一个需要开发者自己维护的文件,我们假设这个文件叫做 service-worker.js,此文件的内容就是定制 Service Worker 生命周期中每个阶段所处理的定制化的细节逻辑,比如缓存 Cache 的读写,更新的策略,推送的策略等等,通常 service-worker.js 文件是处于项目的根目录,并且需要保证能直接通过 https: //yourhost/service-worker.js 这种形式直接被访问到才行。

service-worker.js基本结构代码,如下:

// 安装
self.addEventListener('install', function (e) {
    // 缓存 App Shell 等关键静态资源和 html (保证能缓存的内容能在离线状态跑起来)
});

// 激活
self.addEventListener('activate', function (e) {
    // 激活的状态,这里就做一做老的缓存的清理工作
});

// 缓存请求和返回(这是个简单的缓存优先的例子)
self.addEventListener('fetch', function (e) {
    e.respondWith(caches.match(e.request)
        .then(function (response) {
            if (response) {
                return response;
            }
            // fetchAndCache 方法并不存在,需要自己定义,这里只是示意代码
            return fetchAndCache(e.request);
        })
    );
});

快速注册 Service Worker

注册 Service Worker 还是蛮简单的,只要小段代码。只要在工程中的 html 文档的 <script> 标签里或者随便在页面的哪个 javaScript 模块中添加如下代码即可

navigator.serviceWorker && navigator.serviceWorker.register('/service-worker.js', {scope: '/'})
	.then( registration => {
		// 注册成功
		console.log('ServiceWorker registration successful with scope: ', registration.scope);
	})
	.catch( err => {
		// 注册失败:(
		console.log('ServiceWorker registration failed: ', err);
	})

sw-register-webpack-plugin 与 sw-precache-webpack-plugin

无论是 Service Worker 作用域问题,还是 Service Worker 的更新问题,都与 Service Worker 的注册息息相关,一个看似简单的 Service Worker 的注册还是有很多地方需要注意,但是如果这些都需要在每个项目中都要自己完全实现一遍,还是非常繁琐的。而sw-precache-webpack-pluginsw-register-webpack-plugin 作为一个 Webpack Plugin 很好的帮助我们解决了优雅的注册 Service Worker 的问题

参考资料:

workbox

Service Worker 简介

如何优雅的为 PWA 注册 Service Worker

Service Worker最佳实践

Progressive Web App 的离线存储

Service Workers 与离线缓存

PWA与service worker工作原理探析

service worker 实现离线缓存

使用service worker对静态资源进行全缓存

PWA 应用实战

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