Service Worker是什么
在讲service worker之前,首先会提到PWA(Progressive Web App),中文叫作渐进式网页应用。
在以往的客户端中,主流的有Native App和Web App,两者都有各自的优点。
PWA通过应用一些新的技术,使得Web App具有一些Native APP的优点。例如:添加图标,推送消息,快速启动等,而且同时拥有Web App的无版本特点。
那么PWA主要包括三个特点:
- 在所有的网络环境下,都能瞬间加载并展示
- 像Native App一样,可以添加到桌面,并通过推送和通知使得用户回流
- 具有快速响应等特点,平滑地响应用户的操作
作为渐进式网页应用,就如其名,不一定要一次性实现所有功能,要在浏览器版本支持上,一步一步实现,提供新功能,新体验给用户。
讲回Service Worker,这是PWA的一个重要,且不可或缺的核心技术之一,跟PWA一样,一直被Google疯狂安利,推动发展。提供很多lib,例如workbox,angular ngsw等。但由于在苹果公司的阻碍,safari直到18年才支持这项技术。所以目前支持Service Worker技术的浏览器有Chrome,Firefox,Opera,Safari部分支持,可以在这里查看支持情况。
在浏览器中JavaScript都是运行在一个单一主线程中,那么当遇到一些复杂的运算时,会导致一些性能问题。那么W3C组织就推出了一个叫Web Worker的API,主要用于解放主线程,把一些复杂的运算交给它来做。
在Web Worker的基础上加上持久离线缓存能力,Service Worker就被造出来了,所以Service Worker拥有Web Worker的特点,运行在主线程之外的程序。
Service Worker 功能
Service Worker可以实现Native APP功能,推送消息、通知消息等。
它也可以实现拦截和处理网络请求,缓存应用,后台同步等功能。
后续还会推出其他功能, 例如定期同步和地理位置等功能
因为推送消息需要Google组件的支持,苹果不支持推送,所以在国内,大部分应用用它实现通过拦截处理请求来实现快速启动或通知消息的功能。
虽然它是用JavaScript来编写,但是运行在浏览器主线程之外,所以它无法直接访问DOM,也无法使用localstorage等同步存储功能,并且它必须运行在https环境下。
但它也有一些主线程没有的新接口
- CacheStorage:用于缓存资源,快速启动
- Clients:查看目前有多少被其控制的客户端,可以通过postMessage来与主线程交互
- Registration: 当前的Service Worker,用于通知功能
以及一些主线程没有的新事件
- InstallEvent:当ServiceWorker安装成功时触发,常用于资源缓存
- ActivateEvent:当ServiceWorker激活成功时触发,常用于旧资源释放
- FetchEvent: 当主线程进行发出Fetch请求时,ServiceWorker可以进行拦截,触发Fetch事件
Service Worker 生命周期
生命周期是理解Service Worker运行的最重要的一环。
当第一次安装Service Worker时:
Install:
当主线程调用navigator.serviceWorker.register时,就会下载Service Worker的代码,如果Service Worker的代码中监听的Install事件,那么它会马上触发,而且Install事件只会触发一次。你可以利用Install事件来缓存页面中所需的资源,存到CacheStorage里面,然后通过返回event.waitUntil()的Promise来告诉浏览器什么时候安装完成了
Activate:
当安装完Service Worker时,就会触发activate事件,准备处理push,sync和fetch事件。但客户端(调用register的页面)仍然不受它控制。要第二次加载或者调用clients.claim()控制所有客户端时,它才具备处理以上事件的能力。
当通过以下方式时,会触发检查更新Service Worker:
- 导航到一个作用域内的页面
- 更新 push 和 sync 等功能事件,除非在前 24 小时内已进行更新检查。
- 调用 .register(),仅在 Service Worker 网址已发生变化时。
如果Service Worker的字节(Content-length)与之前的不同,就会考虑触发更新周期
waiting:
触发了更新Service Worker,新的SW就会进入waiting状态,这是因为确保浏览器只运行一个Service Worker版本。所以在所有客户端都关闭之前,都不会更新当前的Service Worker。
例如: 使用Chrome打开了两个tab来运行SW的网页, 那么当前的SW就控制了两个客户端。当完全关闭Chrome之后,再次打开网页,Service Worker就会被更新。
如果想要快速跳过waiting状态,可以调用self.skipWaiting(),这时新的SW就会进入Activate状态,如果处理新的SW版本不兼容或者处理旧版的数据会出错时,可能会出现意料之外的错误,还是要谨慎去调用这个方法。
如果有使用过angular这个框架,它自带的sw库,是完美兼容所有版本,所以它每次更新sw的时候,都会调用self.skipWaiting(), clients.claim()。因为它在activate的时才会加载新的资源和清理旧缓存。
Service Worker 框架
最后介绍Service worker的一些框架,有兴趣的可以研究一下用法。这里就不细说了
分别是react和angular自带的Sw库,都是Google团队研发的通用库。
Workbox:
这个是最常用和通用的Service Worker库,官网。
使用react框架的时候,安装了workbox来作为Service worker的库。
默认只在生产环境进行编译时,才会启用Service worker来缓存资源。当然可以通过改变配置在开发环境进行测试。
它是通过Webpack的插件来生成代码,完全独立于react的库,所以workbox是可以应用于所有web网页中。
Angular SW:
这个是angular团队自己开发的一套Service worker库,只适用于angular框架。
遇到的问题:
在低版本Chromium或者Chrome中,例如: Chrome 56版本,会出现一个问题导致页面加载不出来:
Uncaught (in promise) DOMException: Failed to execute 'waitUntil' on 'ExtendableEvent': The event handler is already finished.
解决方法是在SW加上polyfill,。
参考文献
https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API
https://www.jianshu.com/p/f966bb9de576
https://www.jianshu.com/p/768be2733872