使用 Service worker 实现加速 / 离线访问静态 blog 网站

2,543 阅读7分钟
原文链接: mp.weixin.qq.com

作者:Google 开发技术专家 (GDE) 杨波 (Alpha)


现在很流行基于 GitHub page 和 markdown 的静态 blog,非常适合技术的思维和习惯,针对不同的语言都有一些优秀的静态 blog 系统出现,如 Jekyll/Ruby,Pelican/Python,Hexo/NodeJs,由于静态内容的特性非常适合做缓存来加速页面的访问,就利用 Service worker 来实现加速,结果是除了 PageSpeed,CDN 这些常见的服务器和网络加速之外,通过客户端实现了更好的访问体验。



加速/离线访问只需三步

1. 首页添加注册代码

   

2. 复制代码

将 https://alphayang.github.io/sw.js 保存到你的网站根目录下。


3. 修改不缓存域名列表及离线状态页面

在你的 sw.js 中修改


打开 Chrome Dev Tools->Source,看看自己的 blog 都引用了哪些第三方资源,逐个加到忽略列表里。


在根目录下添加 offline.html,在没有网络且缓存中也没有时使用,效果如下:


在根目录下添加 offline.svg,在无网络时图片资源请求返回该文件。


4. 加速效果

首页加速后,网络请求从 16 降为 1,加载时间从 2.296s 降为 0.654s,得到了瞬间加载的结果。



加速/离线原理探索

什么是 Service worker?


如上图,Service worker 是一种由 Javascript 编写的浏览器端代理脚本,位于你的浏览器和服务器之间。当一个页面注册了一个 Service worker,它就可以注册一系列事件处理器来响应如网络请求和消息推送这些事件。Service worker 可以被用来管理缓存,当响应一个网络请求时可以配置为返回缓存还是从网络获取。由于 Service worker 是基于事件的,所以它只在处理这些事件的时候被调入内存,不用担心常驻内存占用资源导致系统变慢。


Service worker 生命周期


Service worker 为网页添加一个类似于 App 的生命周期,它只会响应系统事件,就算浏览器关闭时操作系统也可以唤醒 Service worker,这点非常重要,让 Web App与 Native App 的能力变得类似了。


Service worker 在 Register 时会触发 Install 事件,在 Install 时可以用来预先获取和缓存应用所需的资源并设置每个文件的缓存策略。


一旦 Service worker 处于 activated 状态,就可以完全控制应用的资源,对网络请求进行检查,修改网络请求,从网络上获取并返回内容或是返回由已安装的 Service worker 预告获取并缓存好的资源,甚至还可以生成内容并返回给网络语法。


所有的这些都用户都是透明的,事实上,一个设计优秀的 Service worker 就像一个智能缓存系统,加强了网络和缓存功能,选择最优方式来响应网络请求,让应用更加稳定的运行,就算没有网络也没关系,因为你可以完全控制网络响应。


Service worker 的控制从第二次页面访问开始

在首次加载页面时,所有资源都是从网络载的,Service worker 在首次加载时不会获取控制网络响应,它只会在后续访问页面时起作用。



页面首次加载时完成 install,并进入 idle 状态。



页面第二次加载时,进入 activated 状态,准备处理所有的事件,同时 浏览器会向服务器发送一个异步 请求来检查 Service worker 本身是否有新的版本,构成了 Service worker 的更新机制。



当 Service worker 处理完所有的事件后,进入 idle 状态,最终进入 terminated 状态资源被释放,当有新的事件发生时再度被调用。


特点

  • 浏览器: Google Chrome,Firefox,Opera 以及国内的各种双核浏览器都支持,但是 safari 不支持,那么在不支持的浏览器里 Service worker 不工作。

  • https: 网站必须启用 https 来保证使用 Service worker 页面的安全性,开发时 localhost 默认认为是安全的。

  • non-block: Service worker 中的 Javascript 代码必须是非阻塞的,因为 localStorage 是阻塞性,所以不应该在 Service Worker 代码中使用 localStorage。

  • 单独的执行环境: Service worker 运行在自己的全局环境中,通常也运行在自己单独的线程中。

  • 没有绑定到特定页面: Service worker 能控制它所加载的整个范围内的资源。

  • 不能操作 DOM: 跟 DOM 所处的环境是相互隔离的。


  • 没有浏览页面时也可以运行: 接收系统事件,后台运行。

  • 事件驱动,需要时运行,不需要时就终止: 按需执行,只在需要时加载到内存。

  • 可升级: 执行时会异步获取最新的版本。


实现加速/离线

Cache

网页缓存有很多,如 HTTP 缓存,localStorage,sessionStorage 和 cacheStorage 都可以灵活搭配进行缓存,但操作太繁琐,直接使用更高级 Service worker –本文的主人公。


添加 Service worker 入口

在 Web App 的首页添加以下代码


如果浏览器支持 Service worker 就注册它,不支持还是正常浏览,没有 Service worker 所提供的增强功能。


Service worker 控制范围:

简单情况下,将 sw.js 放在网站的根目录下,这样 Service worker 可以控制网站所有的页面,同理,如果把 sw.js 放在 /my-app/sw.js 那么它只能控制 my-app 目录下的页面。


把 sw.js 放在 /js/ 目录呢?更好的目录结构和范围控制呢?在注册时指定 js 位置并设置范围。

navigator.serviceWorker.register('/js/sw.js', {scope: '/sw-test/'}).then(function(registration) {

      // Registration was successful

      console.log('ServiceWorker registration successful with scope: ', registration.scope);

    }).catch(function(err) {

      // registration failed :(

      console.log('ServiceWorker registration failed: ', err);

    });


Service worker 实现

监听三个事件:


install

   

install 时将所有符合缓存策略的资源进行缓存。


fetch


onFetch 做为浏览器网络请求的代理,根据需要返回网络或缓存内容,如果获取了网络内容,返回网络请求时同时进行缓存操作。

   

activate

///////////

// Activate

///////////

function onActivate(event) {

  log('activate event in progress.');

  event.waitUntil(removeOldCache());

}

function removeOldCache() {

  return caches

    .keys()

    .then((keys) => {

      return Promise.all( // We return a promise that settles when all outdated caches are deleted.

        keys

         .filter((key) => {

           return !key.startsWith(version); // Filter by keys that don't start with the latest version prefix.

         })

         .map((key) => {

           return caches.delete(key); // Return a promise that's fulfilled when each outdated cache is deleted.

         })

      );

    })

    .then(() => {

      log('removeOldCache completed.');

    });

}

 

在 activate 时根据 version 值来删除过期的缓存。


管理 Service worker

特定网站

1) Google Chrome

Developer Tools->Application->Service Workers,


在这里还有三个非常有用的复选框:

  • Offline: 模拟断网状态

  • Update on reload: 加载时更新

  • Bypass for network: 总是使用网络内容


2) Firefox

只有在 Settings 里有一个可以在 HTTP 环境中使用 Service worker 的选项,适应于调试,没有单独网站下的 Service worker 管理。


3) Opera 及其它双核浏览器同 Google Chrome

如果看到多个相同范围内的多个 Service worker,说明 Service woker 更新后,而原有 Service worker 还没有被 terminated。


浏览器全局

看看你的浏览器里都有哪些 Service worker 已经存在了。


1) Google Chrome

在地址栏里输入:

chrome://serviceworker-internals/


可以看到已经有 24 个 Service worker 了,在这里可以手动 Start 让它工作,也可以 Unregister 卸载掉。


2) Firefox

有两种方式进入 Service worker 管理界面来手动 Start 或 unregister。

  • 菜单栏,Tool->Web Developer->Service workers

  • 地址栏中输入: 

    about:debugging#workers



3) Opera 及其它双核浏览器同 Google Chrome


更多

TODO:

  • Service workers 的更新需要手动编辑 version,每次发布新文章时需要编辑;

  • 使用 AMP 让页面渲染速度达到最高。


作者其他文章:

AR/VR/MR,Android开发者可以做些什么?

Android无处不在,Android开发者大有可为

与谷歌开发技术专家一起,开启“I/O地图开发技术”之旅