概述

目前造的轮子功能实现没有问题,但是在长期使用中发现一些问题。受限于服务器性能,加载速度有点拉胯(丐中丐服务器,确实不能奢求太高,一分钱一分货)。解决办法有2个,CDN或者本地缓存。CDN朋友在用,可能有免费的,本质上也是缓存分发,加快下载速度,如果网站的用户分布比较广,用CDN的效果会更好。但是CDN的更新有延迟,这也是我不太喜欢的原因,另外一种就是今天要介绍的service worker。
(两种策略并不冲突,有想法的可以一起用。)
Service Worker是Google渐进式网页应用Progressive-Web-App(PWA)中使用的一种资源复用特性,利用service worker甚至可以达到网站的离线使用。service worker运行在独立的javascript线程,可以拦截网络请求并根据网络是否可用才语适当的动作更新服务器资源,甚至还有消息推送和后台同步的API。目前已经是新标准的一部分,主流的现代浏览器都支持。

使用Service Worker必须保证当前网站运行在https下,在页面加载完以后注册service worker脚本,剩下的就是在service worker脚本内根据需求逻辑缓存资源了。

service worker 内容

self.addEventListener('install', (e) => {
  e.waitUntil(
    caches.open('store').then((cache) => cache.addAll([
      '/js/file1.js',
    ])),
  );
});

self.addEventListener('fetch', (e) => {
  e.respondWith(
    caches.match(e.request).then((response) => response || fetch(e.request)),
  );
});

注册

if ('serviceWorker' in navigator) {
  // sw.js 文件 必须与 manifest start_url 设置为同级
  navigator.serviceWorker.register('/service-worker.js')
    .then(serviceWorker => {
      console.log('Service Worker registered');
      window.serviceWorker = serviceWorker
    });
}

window.addEventListener('beforeinstallprompt', e => {
  if (window.matchMedia('(display-mode: standalone)').matches) {
    // don't display install banner when installed
    return e.preventDefault();
  } else {
    const btn = document.querySelector('#install')
    btn.hidden = false;
    btn.onclick = _ => e.prompt();
    return e.preventDefault();
  }
});

以上的内容,会在用户请求/js/file1.js的时候,拦截请求,如果用户本地有缓存文件,直接调用缓存。

但是有一个问题,网站不是一锤子买卖,如果按照上面的操作,不管以后怎么更新/js/file1.js文件,用户收到的始终是他第一次请求这个文件时候的版本(除非用户清除浏览器缓存,但是又没有办法强制所有用户清除缓存)。有没有办法做到自动更新呢?

自动更新

有关自动更新的功能,Google已经更新了好几个版本,之前有一个sw-precache可以完成这个功能,最近在npmjs上搜索这个包,已经提示被抛弃了。取而代之的是workbox,与之配套的webpack包也有。所以直接就可以更新。原理是在service worker注册的时候,检测服务端该js文件的hash值变化,然后进行更新。

配置流程

  1. 安装模块
npm i workbox-webpack-plugin --save
  1. 配置webpack
{
  // 其他配置...
  plugins: [
    // 其他插件
    new GenerateSW({
      // additionalManifestEntries : [
      //   {
      //     url:'/lib/lib1.js',
      //     revision:'<HASH>'
      //   }
      // ]
    })
  ]
}

剩下就交给webpack自动完成了,这样在webpack打包的过程中,会自动将打包的js文件添加到service worker文件,如果需要增加一些额外的js文件到service worker缓存中,可以在additionalManifestEntries中定义:

参数 含义
url js文件路径
revision 版本hash值

workbox自动生成的缓存信息会自动计算hash值,附加文件的revision可以选填。

  1. 这个lib以后永远不会更新,revision可以不填。
  2. 这个lib文件可能会更新,可以使用file.<HASH>.js的形式定义文件名。
  3. 这个lib文件可能会更新,文件名是file.js,并添加revision信息。

对我来说,由于类库文件是在多个页面引用,所以动态名字不太现实。而且难保以后不会升级某个类库,所以第3种方式更适合我。类库文件比较多,或者嵌套路径结构不固定,写个递归方法遍历自动生成一下,最后赋值给additionalManifestEntries就可以了。

相关链接

additionalManifestEntries参数介绍
GenerateSW介绍
workbox-webpack-plugin介绍
预缓存介绍
workbox插件介绍
workbox介绍
ServiceWork介绍

总结

service worker在网站体验上绝对是一个杀手级的功能,缓存仅仅是冰山一角。Web push也是一个特别强大的功能,可以在浏览器关闭的情况下完成网站消息推送到设备(但是这个功能又依赖于浏览器实现)。chrome浏览器的消息推送服务,自然是被ban的。国内这种小程序shabi-app横行的环境,只有UC一家实现了web push。所以很难有发展。

得益于Google的Chrome浏览器,在推动网页技术进展上,该公司作了很多贡献。且不论chrome会不会是以后的恶龙,起码他在成长的过程中,反哺社区,为web标准和用户体验做了很多实质性的努力,这点作为一个web技术爱好者,甚感欣慰。
反观国内一些大厂炮制的套壳浏览器、乃至各种小程序,真的连微软都不如(起码微软还努力过,做过chakera引擎),就更谈不上为标准做贡献了。这些所谓的“大厂”,我是真看不起,除了无休止的内斗、制造技术壁垒、消费民众,对这个社会贡献几乎可以忽略不计。就事论事,只以一个IT爱好者的角度谈论这些,不谈政治。