给博客加上Service Worker
随着 Web 的快速发展,用户对站点的体验期望值越来越高,前端工程师有时候为了几十毫秒的速度优化而费劲心思,消耗大量时间。想要让自己的产品在无数产品中脱颖而出,就必须提升产品的性能和体验。在时间成本高昂的今天,响应速度的提升是开发者不得不面对的话题。
前端工程师有很多性能优化的手段,包括 CDN、CSS Sprite、文件的合并压缩、异步加载、资源缓存等等。其实我们绝大部分情况是在干一件事情,那就是尽量降低一个页面的网络请求成本从而缩短页面加载资源的时间并降低用户可感知的延时。当然减少用户可感知的延时也不仅仅是在网络请求成本层面,还有浏览器渲染效率,代码质量等等。
我们这里要讲到的是一个叫做 Service Worker 的东东。
Service Worker 简介
W3C 组织早在 2014 年 5 月就提出过 Service Worker 这样的一个 HTML5 API ,主要用来做持久的离线缓存。
Service Worker 有以下功能和特性:
- 一个独立的 worker 线程,独立于当前网页进程,有自己独立的 worker context;
- 一旦被 install,就永远存在,除非被 uninstall;
- 需要的时候可以直接唤醒,不需要的时候自动睡眠(有效利用资源,此处有坑);
- 可编程拦截代理请求和返回,缓存文件,缓存的文件可以被网页进程取到(包括网络离线状态);
- 离线内容开发者可控;
- 能向客户端推送消息;
- 不能直接操作 DOM;
- 出于安全的考虑,必须在 HTTPS 环境下才能工作;
- 异步实现,内部大都是通过 Promise 实现;
- 所以我们基本上知道了 Service Worker 的伟大使命,就是让缓存做到优雅和极致,让 Web App 相对于 Native App 的缺点更加弱化,也为开发者提供了对性能和体验的无限遐想。
浏览器支持情况
虽说 W3C 组织为了让用户体验做到极致操碎了心提出了这么有用的 API ,但是根据以往经验我们可以知道标准或草案的提出之后各大浏览器的实现步伐是不一样的,那么 Service Worker 这么好用的东西到底浏览器支持情况怎么样呢?参考 Can I use 可得下图:
使用 Service Worker 前提条件
Service Worker 出于安全性和其实现原理,在使用的时候有一定的前提条件:
- 由于 Service Worker 要求 HTTPS 的环境;
- Service Worker 的缓存机制是依赖 Cache API 实现的;
- 依赖 HTML5 fetch API;
- 依赖 Promise 实现;
Workbox
workbox 是 GoogleChrome 团队推出的一套 Web App 静态资源和请求结果的本地存储的解决方案,该解决方案包含一些 Js 库和构建工具,在 Chrome Submit 2017 上首次隆重面世。而在 workbox 背后则是 Service Worker 和 Cache API 等技术和标准在驱动。在 Workebox 之前,GoogleChrome 团队较早时间推出过 sw-precache 和 sw-toolbox 库,但是在 GoogleChrome 工程师们看来,workbox 才是真正能方便统一的处理离线能力的更完美的方案,所以停止了对 sw-precache 和 sw-toolbox 的维护。
- 不管你的站点是何种方式构建的,都可以为你的站点提供离线访问能力;
- 就算你不考虑离线能力,也能让你的站点访问速度更加快;
- 几乎不用考虑太多的具体实现,只用做一些配置;
- 简单却不失灵活,可以完全自定义相关需求(支持 Service Worker 相关的特性如 Web Push, Background
sync 等); - 针对各种应用场景的多种缓存策略。
用法
想要使用 workbox,首先需要在你的项目中创建一个 Service Worker 文件 sw.js 并且在你的站点上注册:
<script>
// Check that service workers are registered
if ('serviceWorker' in navigator) {
// Use the window load event to keep the page load performant
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js');
});
}
</script>
有了 sw.js
之后就可以使用 workbox 了,你只需要在 sw.js
中导入 workbox 就可以使用了:
importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.0.0-alpha.3/workbox-sw.js');
if (workbox) {
console.log(`Yay! workbox is loaded`);
}
else {
console.log(`Boo! workbox didn't load`);
}
一个比较完整的 sw.js
:https://wangdaodao.com/sw.js
'use strict';
//使用阿里的CDN
importScripts('https://g.alicdn.com/kg/workbox/3.3.0/workbox-sw.js');
workbox.setConfig({
modulePathPrefix: 'https://g.alicdn.com/kg/workbox/3.3.0/'
});
if (workbox) {
console.log(`Yay! Workbox is loaded`);
} else {
console.log(`Boo! Workbox didn't load`);
}
workbox.routing.registerRoute(
// Cache CSS files
/.*\.css/,
// Use cache but update in the background ASAP
workbox.strategies.staleWhileRevalidate({
// Use a custom cache name
cacheName: 'css-cache',
})
);
workbox.routing.registerRoute(
// Cache JS files
/.*\.js/,
// Use cache but update in the background ASAP
workbox.strategies.staleWhileRevalidate({
// Use a custom cache name
cacheName: 'js-cache',
})
);
workbox.routing.registerRoute(
// Cache gravatar files
new RegExp('https://cdn\.v2ex\.com/'),
// Use the cache if it's available
workbox.strategies.cacheFirst({
// Use a custom cache name
cacheName: 'gravatar-cache',
plugins: [
new workbox.expiration.Plugin({
// Cache for a maximum of 30 Days
maxAgeSeconds: 30 * 24 * 60 * 60,
})
],
})
);
workbox.strategies
对象为我们提供了几种最常用的策略:
Stale-While-Revalidate
Cache First
Network First
Network Only
Cache Only
注意事项
如果你想让页面离线可以访问,使用 NetworkFirst,如果不需要离线访问,使用 NetworkOnly,其他策略均不建议对 HTML 使用。
CSS 和 JS情况比较复杂,因为一般站点的 CSS,JS 都在 CDN 上,SW 并没有办法判断从 CDN 上请求下来的资源是否正确(HTTP 200),如果缓存了失败的结果,问题就大了。这种我建议使用 Stale-While-Revalidate 策略,既保证了页面速度,即便失败,用户刷新一下就更新了。如果你的 CSS,JS 与站点在同一个域下,并且文件名中带了 Hash 版本号,那可以直接使用 Cache First 策略。
图片建议使用 Cache First,并设置一定的失效事件,请求一次就不会再变动了。
对于不在同一域下的任何资源,绝对不能使用 Cache only 和 Cache first。
效果
如果成功开启Service Worker,在 Chrome 调试工具的 Network 页签下可以看到:
我这里只对CSS、JS和头像开启了 Service Worker,可以根据自己的需要去调整。
只知道这肯定是个好东西,无奈水平有限不会用。
直接复制代码即可,前提要开启HTTPS!
我也用了,目前还没针对 cdn 做缓存策略,目前的感觉是加了之后并没有变快,每次 sw 跑完再跑页面感觉还慢了一些。