基于workbox实现PWA预缓存能力

引言

Service Worker 是一项流行的技术,尽管在许多项目中尚未得到充分利用。基于本次项目首页加载优化的机会,决定尝试使用 Google 出品的 Workbox,以观察其优化效果。

开始

安装

项目使用 Webpack 打包,而 Workbox 提供了 Webpack 插件,因此只需执行 npm i workbox-webpack-plugin --save-dev 即可安装。

初始化配置

安装依赖后,需在原有项目中添加以下 Webpack 配置:

ts 复制代码
const WorkboxPlugin = require('workbox-webpack-plugin');
module.exports = {
  // Other webpack config...
  plugins: [
    // Other plugins...
    new WorkboxPlugin.GenerateSW() // 采用默认配置
  ]
};

然后在项目首页加入以下代码以注册 Service Worker:

html 复制代码
<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');
    // 文档中用的是sw.js,实际上webpack生成的文件是service-worker.js
    navigator.serviceWorker.register('/service-worker.js');
  });
}
</script>

完成这些基本配置后,Service Worker 应该已经生效,会对 Webpack 打包的项目文件进行预缓存。

自定义配置

之前仅采用了 Workbox 的默认配置。具体的完整配置可参照 文档。这里仅介绍一些常用的简单自定义配置,配置使用 key -> value 形式传参:

ts 复制代码
new WorkboxPlugin.GenerateSW({
    option: 'value',
});

swDest

此参数指定 Webpack 生成的 service-worker.js 文件相对于 Webpack 输出目录的路径,默认值是 service-worker.js

ts 复制代码
new WorkboxPlugin.GenerateSW({
    swDest: 'sw.js', // {output}/sw.js
});

importWorkboxFrom

此参数指定页面将从何处加载 Workbox 库文件,默认是 CDN,会从 Google Cloud 上获取依赖的文件。考虑到国内的网络环境,这里一般设置成 local,Webpack 会打包出依赖的文件,供页面加载时使用:

ts 复制代码
new WorkboxPlugin.GenerateSW({
    importWorkboxFrom: 'local',
});

chunks

此参数可以指定 Workbox 需要预缓存的 chunk,因为在预缓存阶段,如果文件数量太多,依然会占用浏览器请求并发数,可能导致其他请求被阻塞,因此对于较大型的项目,合理配置需要预缓存的文件是必要的:

ts 复制代码
new WorkboxPlugin.GenerateSW({
    chunks: ['chunk1', 'chunk2'],
});

excludeChunks

chunks 参数相反,这里指定不需要预缓存的文件。

include

此参数的功能与 chunks 类似,但通过正则表达式匹配需要预缓存的文件:

ts 复制代码
new WorkboxPlugin.GenerateSW({
    // precache html和js文件
    include: [/.html$/, /.js$/],
});

exclude

include 相反。

runtimeCaching

以上配置针对的是预缓存,即在页面加载完后会自动去缓存一次的文件,而 runtimeCaching 针对的是在页面运行中发起的请求的缓存策略。

urlPattern

此参数是一个正则表达式,命中该规则的请求将被缓存下来:

ts 复制代码
new WorkboxPlugin.GenerateSW({
    runtimeCaching: [{
      // 缓存所有图片
      urlPattern: /.(?:jpg|jpeg|svg|png)/,
    }],
});
handler

此参数决定命中的请求使用什么缓存策略,可选的参数有 networkFirstnetworkOnlycacheFirstcacheOnlystaleWhileRevalidate,分别代表:

  • networkFirst:网络优先,即优先使用网络请求返回的结果,当网络请求失败时,尝试使用缓存结果。
  • networkOnly:只使用网络请求结果,不使用缓存。
  • cacheFirst:缓存优先,即优先使用缓存结果,缓存结果不存在时,尝试使用网络请求结果。
  • cacheOnly: 只使用缓存结果。
  • staleWhileRevalidate:有缓存的时候,优先使用缓存结果,同时获取新的网络请求结果,更新缓存。
    对于大部分情况,使用 staleWhileRevalidate 就可以了,其他情况根据自身业务的需求,合理使用 networkFirstcacheFirstnetworkOnlycacheOnly 用的比较少。
ts 复制代码
new WorkboxPlugin.GenerateSW({
    runtimeCaching: [{
      // 缓存所有图片
      urlPattern: /.(?:jpg|jpeg|svg|png)/,
      handler: 'staleWhileRevalidate',
    }],
});
options

此参数是一个对象,其中包含了很多缓存相关的配置,这里不多说,直接看 文档

需要注意的点

在完成上述配置后,如果幸运的话,可以直接使用了。不过第一次使用时,还是遇到了不少问题,很大的原因也是之前没用过 Service Worker,有些基本的知识不了解。官方也列出了一些常见的问题和解决方法,详见 常见问题

本地调试

Service Worker 只有在 HTTPS 或者本地环境才能成功注册,也就是在本地开发时,需要使用 127.0.0.1 或者 localhost 来访问页面才行。

service-worker.js 文件路径

在前面的配置中,我们注册的 service-worker.js 文件默认放在页面的根路径,即 127.0.0.1/service-worker.js,但是在我们项目里,静态文件打包后都放在 public 目录,也就是说访问 URL 是 127.0.0.1/public/service-worker.js,于是把注册的文件路径改成:

ts 复制代码
<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/public/service-worker.js');
  });
}
</script>

这样 Service Worker 可以成功加载注册了,但是却发现很多资源都无法缓存,这是怎么回事?

原因是每个 Service Worker 都有自己的权限域,而这个域的范围依赖于注册文件的路径。如 /public/service-worker.js 注册的 Service Worker 的权限域在 /public 下,所以他只能缓存诸如 127.0.0.1/public/a.png 这种路径的资源,如 127.0.0.1/b.png 就无法缓存了。

要解决这个问题有2种方法:

  • service-worker.js 文件增加 Service-Worker-Allowed='/' 响应头,然后修改注册代码:
ts 复制代码
navigator.serviceWorker.register('/blog/sw.js', {scope: '/'});
  • 修改文件路径到根路径,在服务端对该文件重定向,因为我们项目用的是 Egg,所以这里配置下 siteFile 即可:
ts 复制代码
config.siteFile = {
    '/service-worker.js': fs.readFileSync(
      path.join(
        appInfo.baseDir,
        '/public/service-worker.js',
      ),
    ),
  };

跨域资源的缓存

官方文档 中有专门对这种情况的解释,说明了为什么跨域资源需要特殊处理。对于我们来说,针对跨域资源要做的就是在标签上加上 crossorigin="anonymous" 的属性:

复制代码
<link crossorigin="anonymous" rel="stylesheet" href="https://example.com/path/to/style.css">
<img crossorigin="anonymous" src="https://example.com/path/to/image.png">

然后缓存策略使用 networkFirst 或者 staleWhileRevalidate,最重要的是正则匹配规则需要匹配资源 URL 的头部,不然无法命中跨域资源:

ts 复制代码
new WorkboxPlugin.GenerateSW({
    runtimeCaching: [{
      // To match cross-origin requests, use a RegExp that matches
      // the start of the origin:
      urlPattern: new RegExp('^https://cors.example.com/'),
      handler: 'staleWhileRevalidate',
    }],
});

结尾

Workbox 接入成本低,配置简单灵活,能够满足大部分的页面缓存需求,质量有保障,因此还是十分推荐的。

相关推荐
1892280486144 分钟前
NY243NY253美光固态闪存NY257NY260
大数据·网络·人工智能·缓存
青鱼入云1 小时前
redis怎么做rehash的
redis·缓存
FFF-X2 小时前
Vue3 路由缓存实战:从基础到进阶的完整指南
vue.js·spring boot·缓存
夜影风1 天前
Nginx反向代理与缓存实现
运维·nginx·缓存
编程(变成)小辣鸡1 天前
Redis 知识点与应用场景
数据库·redis·缓存
菜菜子爱学习2 天前
Nginx学习笔记(八)—— Nginx缓存集成
笔记·学习·nginx·缓存·运维开发
魏波.2 天前
常用缓存软件分类及详解
缓存
yh云想2 天前
《多级缓存架构设计与实现全解析》
缓存·junit
白仑色2 天前
Redis 如何保证数据安全?
数据库·redis·缓存·集群·主从复制·哨兵·redis 管理工具
浩浩测试一下2 天前
02高级语言逻辑结构到汇编语言之逻辑结构转换 if (...) {...} else {...} 结构
汇编·数据结构·数据库·redis·安全·网络安全·缓存