理解 service worker 的生命周期,可以帮助掌握其行为,以达到更好的体验。
本文主要概括 https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle
理解 service worker 的“哲学” #
概括来讲就是离线优先和一致性。离线优先没说的,一致性体现在:
- 页面打开是什么(用不用 service worker,用哪个版本的)就一直是什么
- 所有(同一个作用域内的)标签页用同一个版本的 service worker
- 当然新的不能影响正在运行的旧的 service worker
知识补充:precache 机制 #
Precache 简单讲就是在 service worker 安装的时候预先下载需要的文件( JS, CSS 之类),保证安装成功的时候这些文件都已经缓存完毕,一旦 service worker 激活即可直接从 cache 中获取。
cli-plugin-pwa
用到了 workbox-webpack-plugin
。之所以成为 webpack-plugin
,就是为了自动将打包出来的文件 precache。
安装成功不代表可用 #
按照第一条,由于第一次打开时是无 service worker 的,所以即使安装成功了,激活了,请求也不会走 service worker。必须刷新页面才行。
不过可以用选项 clientsClaim
可以让 service worker 激活后立马接管页面,如果当前无人接管的话。需要注意的是,如果请求早于 service worker 安装并激活,显然即使激活后立马接管页面也无能为力了。而且如果 precache 的东西太多的话会严重减慢 service worker 的安装进度,不利于快速接管。
Scope #
当然安装成功了但是没法用还有一种可能,作用域不对。如果是注册 /assets/js/sw.js
,那默认作用域就是 /assets/js/
。解决方法有二:
- 把
sw.js
放到应用根目录下 注册的时候说明
register('/assets/js/sw.js', { scope: '/' })
// 或者翻译成 Vue 的 register('/service-worker.js', { registrationOptions: { scope: './' }, // ... })
js
注意如果你的应用在 /app/
下,注册了 /app/sw.js
,但是某一次以 /app
(没有末尾斜杠)访问主页,那么 service worker 是不会起作用的。
什么时候更新 #
这个简单情况很简单,只要访问到作用域内的页面即可触发浏览器查看更新。
不过 push
和 sync
的事件在一定条件下也可触发,但这是直接写 InjectManifest
的大佬的事情了。
还可以手动更新:
navigator.serviceWorker.register('/sw.js').then(reg => {
// sometime later…
reg.update();
})
或者翻译成 Vue 的:
import { register } from 'register-service-worker'
register('/service-worker.js', {
registered (registration) {
console.log('Service worker has been registered.')
registration.update()
// Or
setTimeout(registration.update.bind(registration), 10000)
}
})
更新完要重开才有效果 #
为什么安装是刷新就可以,但是更新是重开?因为在刷新的时候,旧页面和新页面是有交叠的,也就是说旧的 service worker 没法放手,要一直抓着,直到页面关闭。
所以结合起来,要(正常)更新 service worker(也就是更新应用版本,因为 precache 机制),需要打开页面,稍等一会,等待新的 service worker 安装完成后
不过(在新的 service worker 上)用 skipWaiting
选项可以让新的 service worker 在安装成功后直接激活。但是也有可能使页面混乱,谨慎使用。
更新这么麻烦,开发测试的时候等不起啊 #
勾上 Update on reload #
这样每次刷新或访问新的页面都会触发更新,且无论 service worker 是否有变化都会重新安装并立即生效。
手动 Skip waiting #
强制刷新 #
不仅在开发时有用,还可以作为一个选项提供给用户,方便解决设计出错或用不上新功能给用户带来的烦恼。
location.reload(true)