HTTP缓存限制 和 Service Worker

一直以来只知道 HTTP Cache 是有缓存总量上限的,但是不知道对于单个文件的体积大小也是有限制的。

在这次接盘的3D可视化项目中就遇到了这个问题。因为模型文件体积比较大,在实际使用中经常会反馈说载入时间过长。所以在拿到服务器权限之后就改了一下 Nginx 的配置文件开启了静态资源的 HTTP cache,以为完事大吉就和一线说载入时间慢的问题解决好了。现在进入项目会使用本地缓存,在第一次载入的时候会慢一些,后面再次进入就会快很多了。

但是实际使用中反馈过来的时依旧会有很长的载入时间。以为是 Nginx 的配置项写错了,就又去确认了一下。确实是开启了缓存的,首页的各种静态资源也是会被缓存,但是等我进入到模型展示页的时候就发现问题了!
最外层展示的模型是可以被缓存的,但是操作数据下钻之后开始展示内部模型从 Network 面板中看到的 glb 模型文件不是每一个都会从 disk cache 读取,有一些模型依旧会发起请求。

面板截图

所以就想着浏览器是不是可以有配置项可以修改,但是并没有(可以通过给快捷方式添加启动命令来修改,但不可能要求所有设备都这样做)。所以只能想其他法子来解决这个问题,自然就找到了 Service Worker


使用 Service Worker 其实很简单,一点也不麻烦,创建好 service-work.js 文件之后,注册一下就可以了直接在浏览器中看到效果了。

🌰 比如说创建这样的一个 service-work.js 文件:

// service-work.js
// 监听install事件
// self 就是 service worker
self.addEventListener('install', event => {
  // 安装完成后,使用 addAll 进行文件缓存
  event.waitUntil(
    caches.open('my-cache').then(cache => {
      return cache.addAll([
        '/static/glb/workshop_a/base.glb',
        '/static/glb/workshop_a/equipment_a.glb',
        '/static/glb/workshop_a/equipment_b.glb',
        '/static/glb/workshop_a/equipment_c.glb',
      ])
    })
  )
})

// 监听fetch事件
self.addEventListener('fetch', event => {
  event.respondWith(
    // 匹配缓存中是否有相同的请求资源
    caches.match(event.request).then(response => {
      // 如果有相同的资源,则直接返回。否则,通过fetch方法请求资源
      return response || fetch(event.request).then(fetchResponse => {
        // 如果是GLB文件,将其缓存到磁盘
        if (event.request.url.endsWith('.glb')) {
          return caches.open('my-cache').then(cache => {
            // 将请求响应内容复制并添加到缓存中
            cache.put(event.request, fetchResponse.clone());
            // 返回请求响应内容
            return fetchResponse;
          });
        } else { // 如果不是GLB文件,直接返回请求响应内容
          return fetchResponse;
        }
      })
    })
  )
})

上面使用了两种方式来缓存,一种是直接在注册的时候通过 cache.addAll 把对应的文件请求并缓存在本地。
一种是通过监听 fetch 事件的发起,通过匹配请求的 url 中是否有对应的 .glb 后缀来实现缓存。

然后到 main.js 中注册一下:

// main.js
// ...其他业务代码
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js')
    .then(registration => {
      console.log('Service Worker registered with scope:', registration.scope);
    })
    .catch(error => {
      console.error('Service Worker registration failed:', error);
    });
}
// 其他业务代码...

然后就可以在浏览器的 Network 面板中看到效果了:
开启sw的面板截图

模型的加载缩减到了原本耗时的 1/10 左右,但是模型载入的解析事件还是会按照演示时的设备性能有一些编码,因为使用了压缩技术把模型压缩了。


😫 遇到的问题

🎃 注册 service worker 时提示 The script has an unsupported MIME type ('application/json').

使用 navigator.serviceWorker.register('./service-worker.js') 注册时提示:

Service Worker registration failed: DOMException: Failed to register a ServiceWorker for scope ('http://localhost/') with script ('http://localhost/service-worker.js'): The script has an unsupported MIME type ('application/json').

因为我使用的 Vue 项目,所以把 service-worker.js 移动到 public 目录下,并且改写为绝对路径(navigator.serviceWorker.register('/service-worker.js'))解决了问题。

🎃 需要注意 service worker 注册的作用域

Service worker 是有作用域的,如果你把他放到 https://domain.com/js 目录下,那么 service worker 只会在 /js 目录中生效。除非你在注册时声明好 scope 作用域,比如说 navigator.serviceWorker.register("/service-worker.js", { scope: "/" })

🎃 需要开启 HTTPS

Service worker 是需要开启 HTTPS 的,如果项目没有开启 HTTPS 那么 Service worker 并不会被激活注册。在本地开发的时候可以通过访问 localhost 这样的特殊域名来测试。


相关链接

Service Worker API - Web APIs | MDN
Using Service Workers - Web APIs | MDN
Making PWAs work offline with Service workers - Progressive web apps | MDN

Web Storage | Articles | web.dev
What is Chrome default cache size limit? - Super User