PWA 渐进式实践 (2) - Service Worker


作为 PWA 的象征之一,我们首先做的,就是加上 Service Worker。

添加 Service Worker

Service worker是一个注册在指定源和路径下的事件驱动worker。它采用JavaScript控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。你可以完全控制应用在特定情形(最常见的情形是网络不可用)下的表现。

Service worker运行在worker上下文,因此它不能访问DOM。相对于驱动应用的主JavaScript线程,它运行在其他线程中,所以不会造成阻塞。它设计为完全异步,同步API(如XHRlocalStorage)不能在service worker中使用。

出于安全考量,Service workers只能由HTTPS承载,毕竟修改网络请求的能力暴露给中间人攻击会非常危险。在Firefox浏览器的用户隐私模式,Service Worker不可用。

                                                            —— MDN

通过 Service Worker,我们可以直接摆脱服务端,不需要 304 一样能使用本地缓存。不仅如此,还能执行网页本身行为外的脚本(不过不能访问DOM),比如预先抓取后面可能要访问的资源,甚至在飞行模式下同样能使用 webapp。

注册

我们的项目是使用 ejs 在 webpack 阶段注入几个变量生成最后的 index.html 的,所以直接拿 index.ejs 动刀即可:

<body>
  <div id="container"></div>
  <script src="<%= bundle %>"></script>
  <script>
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/service-worker.js', {
        scope: './'
      }).then(function(registration) {
        registration.onupdatefound = function() {          if (navigator.serviceWorker.controller) {            const installingWorker = registration.installing;
            installingWorker.onstatechange = function() {              switch (installingWorker.state) {                case 'installed':                  break;                case 'redundant':                  throw new Error('The installing ' +                    'service worker became redundant.');                default:                // Ignore
              }
            };
          }
        };
      }).catch(function(e) {        console.error('Error during service worker registration:', e);
      });
    } else {      console.log('service worker is not supported');
    }  </script>

即 body 中,第二个 script 标签的内容,其参数 service-worker.js,是与 index.html 在同一个目录的空文件:

// This file is intentionally without code.// It's present so that service worker registration// will work when serving from the 'public' directory.

实际上打包后会生成真正的 service-worker.js,所以现在只是用来占个位子。

纳尼?这样就好了?

确实,这样,我们就已经完成了注册,这也是 PWA 和微信小程序这种二流方案不同的地方,其更注重于如何提高现有设计实现下的体验,使用开放的标准并进行推进。

Cache 策略

下一步就是增加我们的缓存策略了,我们需要安装 2 个小工具:

npm install sw-precache --save
npm install sw-toolbox --save

然后在 package.json 里面更新一下我们的 script:

"scripts": {
  "build": "npm run copy && node run build && npm run precache",
  "build:debug": "npm run copy && node run build --debug && npm run precache",
  "copy": "cp node_modules/sw-toolbox/sw-toolbox.js public/sw-toolbox.js",
  "precache": "./node_modules/sw-precache/cli.js --root=public --config=sw-precache-config.json"
}

如上,增加 copy 和 precache 任务,并更新 build,在 build 前后插入新的 task。

然后就是配置文件了,在项目目录下,增加 sw-precache-config.json 文件:

{
  "staticFileGlobs": [    "public/dist/**.css",    "public/dist/**.png",    "public/dist/**.js"
  ],
  "importScripts": [    "sw-toolbox.js",    "runtime-caching.js"
  ],
  "stripPrefix": "public/",
  "verbose": true}

在 public 目录下,增加 runtime-caching.js 文件:

// global.toolbox is defined in a different script, sw-toolbox.js, which is part of the// https://github.com/GoogleChrome/sw-toolbox project.// That sw-toolbox.js script must be executed first, so it needs to be listed before this in the// importScripts() call that the parent service worker makes.(function (global) {  // See https://github.com/GoogleChrome/sw-toolbox/blob/6e8242dc328d1f1cfba624269653724b26fa94f1/README.md#toolboxroutergeturlpattern-handler-options
  // and https://github.com/GoogleChrome/sw-toolbox/blob/6e8242dc328d1f1cfba624269653724b26fa94f1/README.md#toolboxfastest
  // for more details on how this handler is defined and what the toolbox.fastest strategy does.
  global.toolbox.router.get('/(.*)', global.toolbox.fastest, {
    origin: /\.(?:googleapis|gstatic|firebaseio|appspot)\.com$/,
  });
  global.toolbox.router.get('/(.+)', global.toolbox.fastest, {
    origin: 'https://api.pai.bigins.cn/',
  });
  global.toolbox.router.get('/(.+)', global.toolbox.fastest, {
    origin: 'https://qa.api.pai.bigins.cn/',
  });
  global.toolbox.router.get('/*', global.toolbox.fastest);
}(self));

运行一下 npm run build,发现 service-worker.js 被更新了,里面是生成的策略脚本。

评测

再次运行 Lighthouse 后,发现我们的评分已经嗖嗖嗖上去了:


离线依然返回 200

这里的秘密就在 runtime-caching.js 文件里,我们更新一下:

// global.toolbox is defined in a different script, sw-toolbox.js, which is part of the// https://github.com/GoogleChrome/sw-toolbox project.// That sw-toolbox.js script must be executed first, so it needs to be listed before this in the// importScripts() call that the parent service worker makes.(function (global) {  // See https://github.com/GoogleChrome/sw-toolbox/blob/6e8242dc328d1f1cfba624269653724b26fa94f1/README.md#toolboxroutergeturlpattern-handler-options
  // and https://github.com/GoogleChrome/sw-toolbox/blob/6e8242dc328d1f1cfba624269653724b26fa94f1/README.md#toolboxfastest
  // for more details on how this handler is defined and what the toolbox.fastest strategy does.
  global.toolbox.router.get('/(.*)', global.toolbox.fastest, {
    origin: /\.(?:googleapis|gstatic|firebaseio|appspot)\.com$/,
  });
  global.toolbox.router.get('/(.+)', global.toolbox.fastest, {
    origin: 'https://api.pai.bigins.cn/',
  });
  global.toolbox.router.get('/(.+)', global.toolbox.fastest, {
    origin: 'https://qa.api.pai.bigins.cn/',
  });
  global.toolbox.router.get('/(.+)', global.toolbox.fastest, {
    origin: 'https://pai.bigins.cn/',
  });
  global.toolbox.router.get('/*', global.toolbox.fastest);
  global.toolbox.precache(['/index.html', '/index.css', '/img/logo.png']);
}(self));

然后再提交构建一下,在 Chrome 的 Network Panel 中,勾选 Offline,然后刷新页面,哇,依然可以用诶。

评测


通过完善 Service Worker,我们的评分已经嗖嗖嗖上了80,达到了83分。

What’s next


剩下的就是一些比较棘手的性能和体验问题了,我们下回见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值