作为 PWA 的象征之一,我们首先做的,就是加上 Service Worker。
添加 Service Worker
注册
我们的项目是使用 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
剩下的就是一些比较棘手的性能和体验问题了,我们下回见。