Service Worker 入门

4.25

什么是 Service Worker

Server Worker 可以理解为是一个特殊的 JavaScript 脚本

由浏览器在后台运行,由页面触发又独立于页面的 JavaScript 脚本。可以提供推送通知、后台同步、处理页面请求提供缓存等功能。优化了页面的离线体验。

特点:

  • 是一个可编程的网络代理,可以处理网络请求

  • 独立异步的线程。与页面通过postMessage交互,由页面去控制 DOM

  • 会自动按需停止和启动。涉及持久化时可以使用 IndexedDB API

  • 大量使用 Promise API

  • 事件驱动

生命周期

一个 Service Worker 实现请求缓存的例子

准备

  • 确定浏览器支持

  • 站点部署需要支持 HTTPS,开发时可以直接用 localhost

  • 入口文件:index.js (注册 sw.js)

  • Service Worker 文件:sw.js (实现具体功能, 名字可以任意, 例如 service-worker.js)

注册 Servcie Worker 文件 sw.js

入口文件 index.js

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function () {
    navigator.serviceWorker.register('/sw.js').then(function (registration) {
      // Registration was successful
      console.log('ServiceWorker registration successful with scope: ', registration.scope);
    }, function (err) {
      // Registration failed :(
      console.log('ServiceWorker registration failed: ', err);
    });
  });
}

Service worker 文件 sw.js 所在的路径决定了接收 fetch 事件的作用域

实现 Service Worker 文件 sw.js

Service Worker文件 sw.js 可以使用一些专有关键字:实例self,缓存caches。通过self可以监听特定的事件addEventListener

测试

Service Worker 文件 sw.js

console.log('hello from Service Worker', self, caches)

添加缓存

intall时启用缓存,在fetch时先检查缓存是否匹配。当用户打开或刷新页面时,会触发fetch事件。

Service Worker 文件 sw.js

const CACHE_NAME = 'my-site-cache-v1';
const urlsToCache = [
  '/', 
  '/asset',
  '/main.css',
  '/main.js', 
];

self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open(CACHE_NAME).then(function (cache) {
      return cache.addAll(urlsToCache);
    })
  )
});

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.match(event.request).then(function (response) {
      // Cache hit - return response
      if (response) {
        return response;
      } else {
        return fetch(event.request);
      }
    })
  )
});
  • 查看安装的 Service Worker

    • Devtools > Application > Application > Service Workers

    • chrome://inspect/#service-workers

  • 查看添加的缓存

    • Devtools > Application > Cache Storage > CACHE_NAME

缓存的内容需要调用caches接口来更新和删除,也可能直接被浏览器清理。

动态添加缓存

成功的请求,拷贝后放入缓存

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.match(event.request).then(function (response) {
      if (response) {
        return response;
      } else {
        return fetch(event.request).then(function (response) {
          // Check if we received a valid response
          if (!response || response.status !== 200 || response.type !== 'basic') {
            return response;
          }

          // IMPORTANT: Clone the response. A response is a stream
          // and because we want the browser to consume the response
          // as well as the cache consuming the response, we need to 
          // clone it so we have two streams.
          const responseToCache = response.clone();
          caches.open(CACHE_NAME).then(function (cache) {
            cache.put(event.request, responseToCache);
          });

          return response;
        })
      }
    })
  )
})

缓存策略

可以对event.request等条件进行过滤,按需添加缓存。

根据 Network,Cache,update 和 refresh 的组合和优先级形成不同的缓存策略。

Workbox: 构建 Progressive Web Apps(PWA) 的库

参考:Workbox 介绍

配合框架使用

前端框架脚手架的 PWA 库可以借助 workbox 自动生成 Service Worker / PWA 相关代码。

一般默认在生产模式下启用。使用缓存优先策略。缓存静态文件(static site assets),不会缓存跨域的请求如 API requests,images,embeds 等。

  • Vue-cli: @vue/cli-plugin-pwa

    vue add pwa

  • Create React App: Making a Progressive Web App

    • npx create-react-app my-app --template cra-template-pwa
    • Switch serviceWorker.unregister() to serviceWorker.register()
  • Nexst.js: next-pwa

    npm i next-pwa
    

    next.config.js(默认开发和生产模式都会启用)

    const withPWA = require('next-pwa');
    
    module.exports = withPWA({
      pwa: {
        // disable: process.env.NODE_ENV === 'development',
        // dest: 'public'
      }
    });
    
📖