Service worker在PWA中的应用

503 阅读4分钟

PWA

PWA全称Progressive web apps,即渐进式 Web 应用,这个概念最开始由google推出,概念也是炒得非常火,2018年以后主流浏览器在google的推动下基本上都只支持了开发pwa的各项能力,业内也有了不少实践,在现有公司内还没有比较好的应用,于是回顾一下,看能否在现有系统中接入提升web的使用体验。

渐进式应用

什么是渐进式应用? 我的理解是,一个web应用被变得更易于发现,更易于使用,流畅性和便捷性上都得到了提升,逐渐达到接近native app的使用体验,提升用户的满意度。而官方描述为:PWA不是使用一种技术创建的。它们代表了构建Web应用程序的新理念,涉及一些特定的模式,API和其他功能。如果一个Web App从一开始就是PWA,那就不那么明显了。当应用程序满足某些要求时,可以将其视为PWA,或者实现一组给定的功能:离线工作,可安装,易于同步,可以发送推送通知等

一般认为PWA主要包含以下几种技术:

  • ServiceWorker
  • Fetch
  • Cache API 缓存
  • Push & Notification推送和通知
  • Add to Home Screen,即Web App Manifest,可以直接将你的web应用添加图标到桌面上。 对于web应用来说,最大的痛点就是无法离线使用,所以PWA的核心就是利用Service worker和cache api + indexDb等实现web的离线加载和缓存

Service worker

Service worker为浏览器支持的一个特性,对应支持的浏览器版本如下: service worker为前端开放了很多一些浏览器的底层缓存能力,类似于我们http协议中的cache机制(即304 not modified以及cache control控制的缓存能力这一套)。与http缓存不同的是,http缓存依赖于后台服务,并且无法做更加精细化的控制。 service worker让客户端完全具备控制前端请求,拦截,响应,缓存等能力。这样做离线缓存就变得更加方便。

还有一点很重要的是,service worker的设计天生就是独立于浏览器主线程,我们知道浏览器js都是运行在单线程之上,而service worker可以独立于主线程,做一些比较复杂的计算工作,再通过postMessage等方式通知主线程,以达到更好的性能。

生命周期

在网上找到一张Service worker的生命周期大概是这样的:

  • Installing: service worker需要被注册,是通过调用浏览器navigator.serviceWorker.registerAPI来实现,注册之后表示开始安装,触发这个生命周期,会有一些内部过程和缓存的指定,如果事件里面有event.waitUtil()则会等待promise完成才会结束。
  • Installed: 表示sw已经安装完成,这个时候页面如果有之前存在的旧的sw,当前注册的sw则还不会生效,需要等待下一个步骤来激活
  • Activating: 如果没有被其他sw控制的客户端,允许激活上一步注册的sw,会清理一些之前存在的缓存资源
  • Activated: 激活完成后就可以使用了,sw会根据注册信息,对客户端的请求进行拦截和缓存。
  • Redundant: 废弃状态,即sw不在使用,生命和周期结束

注册

首先需要一个sw.js文件,sw是核心的worker,里面定义了我们要如何来缓存资源,然后检查浏览器是否支持,如果支持即开始注册sw,在html底部添加缓存代码:

if ('serviceWorker' in navigator) {
      window.addEventListener('load', function() { navigator.serviceWorker.register('./sw.js').then(function() {
          // 注册成功
          console.log('service worker register success');
        }).catch(function(err) {
          // 注册失败
          console.log('service worker register failed: ', err);
        });
      });
}

运行后可以看到,我们的浏览已经注册成功:

sw.js

sw.js如下:定义一个CACHE_NAME用于记录缓存标识cacheUrl来定义需要缓存的url目录

var CACHE_NAME = 'my-sw-cache';
var cacheUrl = [
  '/',
  '/js/app.js',
  '/chunk-vendors.js'
];

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        // console.log('Opened cache');
        return cache.addAll(cacheUrl);
      })
  );
});

打开浏览器可以看到,由sw.js发起的几个缓存文件 在application界面可以看到注册sw文件:

到这里基本的搭建就完成了,后续主要是利用api 对缓存进行详细的控制,例如直接拦截响应

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        // 如果发现匹配的响应,则返回缓存的值
        if (response) {
          return response;
        }
        return fetch(event.request);
      }
    )
  );
});

对于上面这些其实只是最基本的搭建,对于缓存策略的更新& 卸载,路由处理,结合webpack对于动态hash文件的缓存等等都有不同的处理方式,但是service worker开放的缓存能力,为前端缓存提供了更丰富的想象力。