PWA 学习笔记之 Service Workers

View this thread on: d.buzz | hive.blog | peakd.com | ecency.com
·@semlinker·
0.000 HBD
PWA 学习笔记之 Service Workers
Service worker 是一个在 Web 应用程序后台运行的脚本。它不需要 DOM,实际上它甚至不能访问 DOM。Service worker 运行在与 UI 线程独立的线程中,因此它们在运行时不会阻塞 UI 线程。Service worker 的意义在于它充当了你的应用和互联网之间的中介。然后它会执行你设定的任何功能,最后通过消息传递将结果返回给应用程序。

出于安全考虑,Service workers 只能由 HTTPS 承载,因为修改网络请求的能力暴露给中间人攻击会非常危险。需要注意的是在 Firefox 浏览器的[用户隐私模式](https://support.mozilla.org/zh-CN/kb/%E9%9A%90%E7%A7%81%E6%B5%8F%E8%A7%88),Service Worker 不可用。

> 在开发过程中,可以通过 `localhost` 使用服务工作线程,但如果要在网站上部署服务工作线程,需要在服务器上设置 HTTPS。
>
> 使用服务工作线程,您可以劫持连接、编撰以及过滤响应。 这是一个很强大的工具。您可能会善意地使用这些功能,但中间人可会将其用于不良目的。 为避免这种情况,可仅在通过 HTTPS 提供的页面上注册服务工作线程,如此我们便知道浏览器接收的服务工作线程在整个网络传输过程中都没有被篡改。 —— [服务工作线程:简介](https://developers.google.com/web/fundamentals/primers/service-workers/?hl=zh-cn#_2)

Service workers 拥有强大的能力,它还可以用来做以下这些事情:

- 后台数据同步;
- 响应资源请求;
- 集中接收计算成本高的数据更新,比如地理位置和陀螺仪信息,这样多个页面就可以利用同一组数据;
- 提高用户体验,比如预取用户可能需要的资源,比如相册中的后面数张图片。

### 使用 Service Worker

*  **创建 service-worker.js 文件**:在该文件中我们使用 addEventListener 方法监听 service worker 的 install 和 activate 事件,并在控制台输出相关的信息。

```javascript
self.addEventListener('install', (event) => {
    console.log('service worker installed', event);
});

self.addEventListener('activate', (event) => {
  console.log('service worker activated', event);
});
```

* **创建 script.js 文件**:由于 Service Workers 仍存在较大的[兼容性问题](https://caniuse.com/#search=service%20worker),因此我们需要判断当前浏览器是否支持它,若当前浏览器支持 service worker,我们可以通过 serviceWorker 对象提供的 register 方法注册 service worker,该方法返回一个 Promise 对象,当注册成功后,我们将返回的 registration 对象输出到控制台。

```javascript
(() => {
  if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
      navigator.serviceWorker.register('service-worker.js')
        .then((registration) => {
          console.log('registered');
          console.log(registration);
      },(err) => {
        console.log(err);
      }); 
   });
  } else {
    alert('当前的浏览器不支持 Service Worker');
  } 
})();
```

* **创建 index.html 文件**:新建 index.html 文件,在该文件中我们引入已创建的 script.js 脚本。

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Service Worker Demo</title>
    <script src="script.js"></script>
</head>
<body>
    <h1>My name is Semlinker, I Love PWA!</h1>
</body>
</html>
```

如果你是使用 WebStorm,可以直接运行以上的 index.html 文件。或使用 [Web Server for Chrome](https://chrome.google.com/webstore/detail/web-server-for-chrome/ofhbbkphhbklhfoeikjpcbhemlocgigb?hl=zh-cn) 插件、其它 Web 服务器运行 index.html 文件。

在浏览器中成功打开 index.html 文件后,在控制台中能看到以下输出信息,具体如下图所示:

![sw-registration.jpg](https://steemitimages.com/DQmcR6xAHuAHyLKKceEzedXei8HvhwhFBvKPvTE4MWDVCiF/sw-registration.jpg)

图中 scriptURL 字段表示 service worker 脚本的加载地址,state 字段表示当前 service worker 的状态,scope 字段表示当前已激活 service worker 的作用域。**我们可以在注册 service worker 时,通过设置第二个参数的 scope 属性来设置 service worker 的作用域**。

Service worker 的简单示例已经运行起来了,接下来我们来了解一下调试 service worker 的相关知识。

### 调试 Service Worker

打开 Chrome 开发者工具,选择 Application Tab 页,然后选择左侧 Application 菜单的 Service Workers 子菜单项。

![dev-sw-tab.png](https://steemitimages.com/DQmX2Ko7BUQkmYvvBYdt36PKhfQYEZGFTNC6GCa41FCfHwb/dev-sw-tab.png)

图中红框中是一系列复选框,它们的作用如下:

- **Offline**:用于模拟断开网络连接;
- **Update on reload**:将用新的 service worker 强制替换当前 service worker(如果开发者已更新 `service-worker.js`)。通常情况下,浏览器将进行等待,直到用户在更新到新的service worker 之前关闭包含当前网站的所有标签。
- **Bypass for network**:将强制浏览器忽略所有活动 service worker 并从网络中获取资源。这有助于你放心地使用 CSS 或 JavaScript 等文件而不必担心 service worker 意外缓存或返回旧文件。
- **Show all**:在不考虑来源的情况下,显示所有活动服务工作线程。

此外图中绿色状态指示灯旁边的 ID,是用于表示当前 service worker 的 ID。上面我们已经介绍了 Update on reload 选项,接下来我们来体验一下如何更新 service worker。

### 更新 Service Worker

更新 service-worker.js 文件,具体内容如下:

```javascript
self.addEventListener('install', (event) => {
    console.log('New service worker installed', event);
});

self.addEventListener('activate', (event) => {
    console.log('New service worker activated', event);
});
```

保存 service-worker.js 文件后,在浏览器中重新刷新一下 index.html 页面。这时浏览器控制台只输出了两条消息,少了一条消息,没有输出 `New service worker activated` 这条消息。现在让我们在看一下当前 service worker 的状态,具体如下图所示:

![sw-update.jpg](https://steemitimages.com/DQmVvPkb6Dh6HypAEfcJY7m4daJLGqaFRvsb5SNMKBohocH/sw-update.jpg)

**通过观察上图,我们发现当更新 service-worker.js 后,刷新当前页面更新后的 service worker 并不会马上进入激活状态,而是进入等待激活的状态**。那么如何让更新后的 service worker 立即进入激活状态呢?相信一些小伙伴已经注意到了 **skipWaiting** 这个链接,顾名思义它用于跳过等待。当点击这个链接后,控制台将会输出我们预期的信息:

```
New service worker activated 
```

其实上图也表示了当前站点下同时存在两个 servicer worker,一个处于激活状态(运行状态),另一个处于非激活状态(等待状态)。如果你想同一个时刻,只有一个 service worker,我们也可以通过使用 skipWaiting 方法来实现强制更新。这意味着当更新 service worker 时我们将跳过等待状态,即会移除已激活的 service worker,然后激活新的 service worker。

具体代码如下:

```javascript
self.addEventListener('install', (event) => {
    self.skipWaiting();
    console.log('Update service worker installed', event);
});

self.addEventListener('activate', (event) => {
    console.log('Update service worker activated', event);
});
```

本文已经介绍了如何使用、调试及更新 Service Worker,最后我们再来简单介绍一下 Service Worker 的生命周期。

### Service Worker 生命周期

Service Worker 拥有一个完全独立于 Web 页面的生命周期,前面我们已经介绍过 install 和 activate,完整的生命周期可以参考[introduction-to-progressive-web-app-architectures](https://developers.google.com/web/ilt/pwa/introduction-to-progressive-web-app-architectures):

下一篇文章我们将介绍如何利用 fetch API、CacheStorage API 及 Service Worker 实现缓存控制,刚开始学习 PWA,以上内容有误之处,请小伙伴们多多指教。

### 参考资源

- [MDN - Service Worker API](https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API)
- [Google - 调试服务工作线程](https://developers.google.com/web/fundamentals/codelabs/debugging-service-workers/?hl=zh-cn)
👍 ,