折腾小记 – Service Worker

0 序

退役了,文化课了。于是每天写代码的时间连 20min 都没有了。

但我还是想写!那就整点乐子吧

1 Service Worker

The service worker is designed first to redress this balance by providing a Web Worker context, which can be started by a runtime when navigations are about to occur. This event-driven worker is registered against an origin and a path (or pattern), meaning it can be consulted when navigations occur to that location. Events that correspond to network requests are dispatched to the worker and the responses generated by the worker may override default network stack behavior. This puts the service worker, conceptually, between the network and a document renderer, allowing the service worker to provide content for documents, even while offline.

https://www.w3.org/TR/service-workers/#motivations

翻译一下就是

Service Worker 提供了一种在请求前进行处理的 Web Worker。开发者可以通过此技术来拦截并修改每一个请求,并通过访问缓存等方式实现在网络较差甚至离线情况下对网页的访问。

也就是说,Service Worker 提供了一个缓存控制器,使得开发者能够更加精确的控制缓存。

其实我最早知道这个东西的时候,大家还喜欢把这玩意儿和 PWA 一起说,说什么 APP 未来的趋势。过两年小程序出来了,PWA 在国内也就是笑谈了。

1.1 兼容性

较为现代且非 IE 的浏览器都支持 Service Worker。

更详细的请参见:https://caniuse.com/?search=service%20worker

1.2 一些技术细节

同步和通知功能实在用不到,我就不写了。

Service Worker 是有一个注册流程的。

  1. 如果之前没有 Service Worker,那么经过 ServiceWorkerContainer.register() 方法注册,如果代码运行成功,那么 Service Worker 进入 Installed 状态。
  2. 在之后的访问,每次都会使用这一次注册的 Service Worker,也就是 Activate 状态。如果 Service Worker 文件发生变化,那么新的文件会进入 Waiting 状态。在关于注册域的所有网页都已经被关闭后,原有的 Service Worker 文件被回收,新的 Service Worker 将在下一次访问时开始工作。

Service Worker 有注册域(scope)的概念。

Service Worker 只在访问注册域下的页面时工作。

尽管在注册时有参数可以可以指定注册域,但是在没有配置的情况下,注册域只能是 sw.js 文件所在目录本身或其子目录。如果想要注册在这些目录之外的地方,需要服务器发送 Service-Worker-Allowed(具体参见)头以指定允许的注册域。

1.3 事件和方法

在安装时,有两个事件,installactivate

You can listen for the install event; a standard action is to prepare your service worker for usage when this fires, for example by creating a cache using the built in storage API, and placing assets inside it that you’ll want for running your app offline.

There is also an activate event. The point where this event fires is generally a good time to clean up old caches and other things associated with the previous version of your service worker.

https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API

install 事件;通常用于预加载需要的文件。

activate 事件;通常用于清除上一版本的 Service Worker 留下的缓存和其他东西。

在请求时,有事件 fetch

Your service worker can respond to requests using the FetchEvent event. You can modify the response to these requests in any way you want, using the FetchEvent.respondWith() method.

https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API

fetch 事件;监听每一个请求,可以通过 FetchEvent.respondWith() 方法来指定回应方式。

值得注意的是,respondWith(response) 的参数 response,可以是 Response,也可以是一个返回 Response 的 Promise

A Response or a Promise that resolves to a Response. Otherwise, a network error is returned to Fetch.

https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith

2 本站的实现

你可以在这里查看:https://github.com/woshiluo/wordpress-theme-simplemd/blob/b8583d1321662d3917de6ada0d481107d60ba0d6/js/sw.js

这个版本使用了两个缓存池,staticfilescachefiles,通过后缀来分辨 sw.js 的版本。

我没有选择在 install 事件时预加载一部分资源,因为本站的静态资源在每个页面是一致的。

activate 事件中,将非当前版本的缓存池删除,以去除老版本留下的缓存。

fetch 事件中,当前仅当 wp-contentwp-include 目录下的文件会被放入 staticfiles,其他文件会放入 cachefiles

  • staticfiles 的文件会优先读取缓存,如果没有命中缓存,则请求并放入缓存。
  • cachefiles 的文件会优先请求最新资源并放入缓存,如果无法连接,则返回缓存内的文件。

为什么 staticfiles 有请求一次就不再更新的底气呢?因为几乎所有会更新的资源都有 ?ver= 的 url 参数,这意味只要资源更新,那么链接也会被更新,就不会命中之前的缓存了。

3 关于前端性能优化

在配合上 Service Worker 后,在我的环境下, DOMContentLoaded 约在 600-1000ms。

显然不算难看,但也没多好看。

其实合理控制缓存只是加快方面速度的一环,对于本站来说,MathJax 已经成为了加载时间的大头(~300ms)而我显然拿这个东西没有办法。(一种可行的办法是使用服务端渲染,但是 PHP 似乎没有现成实现)

另一方面,本站的 JavaScript 加载顺序其实没有任何优化,同步的 JavaScript 加载仍然在阻碍页面加载。

如果我接下来一段时间每天还能有几分钟用来写代码,那么应该会有更多关于这方面的文章。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

message
account_circle
Please input name.
email
Please input email address.
links

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据