《Progressive Web Apps》读书笔记三
第三章 缓存
3.1 HTTP 缓存基础
浏览器发起请求时,服务器返回的资源附带了一些 HTTP 响应头,告知浏览器这个资源是什么类型的,要缓存多长时间,是否压缩过等等。
使用 HTTP 缓存意味着要依赖服务器来告知何时缓存资源以及资源何时过期。如果内容具有相关性,任何更新都可能导致服务器发送的到期日期变得很容易不同步,并影响网站。
3.2 Service Worker 缓存基础
Service Worker 缓存无需由服务器来告知浏览器资源需要缓存多久,可以全权掌握。
3.2.1 在 Service Worker 安装过程中预缓存
1 2 3 4 5 6 7 8 9 10
| var cacheName = 'helloWorld' self.addEventListener('install', event = { event.waitUntil( caches.open(cacheName) .then(cache => cache.addAll([ '/js/script.js', '/images/hello.png' ])) ) })
|
一旦缓存开启,就可以开始把资源添加进去。接下来,调用了 cache.addAll()
并传入文件数组。event.waitUntil()
方法使用了 JavaScript 的 Promise 来知晓安装所需的时间以及是否安装成功。
如果所有的文件都成功缓存了,那么 Service Worker 便会安装完成。如果有任何文件下载失败了,安装过程也随之失败。
1 2 3 4 5 6 7 8 9 10 11
| self.addEventListener('fetch', function() { event.respondWith( caches.match(event.request) .then(function(response) { if (response) { return response } return fetch(event.request) }) ) })
|
3.2.2 拦截并缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| var cacheName = 'helloWorld'
self.addEventListener('fetch', function() { event.respondWith( caches.match(event.request) .then(function(response) { if (response) { return response } var requestToCache = event.request.clone() return fetch(requestToCache) .then(function(response) { if (!response || response.status !== 200) { return response } var responseToCache = response.clone() caches.open(cacheName) .then(function(cache) { cache.put(requestToCache, responseToCache) }) return response }) }) ) })
|
首先,要检查请求的资源是否存在于缓存中。如果存在于缓存中,可以就此返回缓存并不再继续执行代码。
如果缓存中没有请求资源,就按原计划发起网络请求。在代码进一步执行之前,需要复制请求,请求是一个流,只能使用一次。因为之前已经通过缓存使用了一次请求,接下来发起 HTTP 请求还要再使用一次,所以需要在此时复制请求。然后检查 HTTP 响应,确保服务器返回的是成功响应并且没有任何问题。
如果成功响应,会再次复制响应。因为想要浏览器和缓存都能够使用响应,所以需要复制它,这样就有了两个流。
最后,在代码中使用这个响应并将其添加至缓存中,以便下次再使用它。
在上面的示例中,每次返回成功的 HTTP 响应时,都能够动态地向缓存中添加资源。如果想要缓存资源但不太确定它们可能更改的频率或确切地来自哪里,这种方案可能会适合。
3.2.3 整合所有代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| var cacheName = 'latestNews-v1'
self.addEventListener('install', event => { event.waitUntil( caches.open(cacheName) .then(cache => cache.addAll([ './js/main.js', './js/article.js', './images/newspaper.svg', './css/site.css', './data/latest.json', './data/data-1.json', './article.html', './index.html' ])) ) })
self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request, { ignoreSearch: true }) .then(function (response) { if (response) return response var requestToCache = event.request.clone()
return fetch(requestToCache) .then(function (response) { if (!response || response.status !== 200) { return response }
var requestToCache = response.clone() caches.open(cacheName) .then(function (cache) { cache.put(requestToCache, requestToCache) }) }) }) ) })
|
如上代码是安装期间的预缓存和获取资源时进行缓存的组合应用。该 Web 应用使用了应用外壳架构,意味着可以利用 Service Worker 缓存来只请求填充页面所需的数据。你已经成功存储了外壳的资源,所以剩下的就是来自服务器的动态新闻内容。
3.3 缓存前后的性能对比
WebPagetest.org,略。
3.4 深入 Service Worker 缓存
3.4.1 对文件进行版本控制
理念是每次更改文件时创建一个全新的文件名,以确保浏览器可以获取最新的内容。
3.4.2 处理额外的查询参数
- ignoreSearch,忽略请求参数和缓存请求中 URL 的查询部分
- ignoreMethod,忽略请求参数的方法
- ignoreVary,忽略已缓存响应中的 vary 响应头
3.4.3 需要多少内存
略。
3.4.4 将缓存提升到一个新的高度:Workbox
Workbox,是一个由谷歌团队编写的辅助库,帮助快速创建 Service Worker,涵盖了最常见的网络策略。
1 2 3 4 5 6 7 8
| importScripts('workbox-sw.prod.v1.1.0.js') const workboxSW = new self.WorkboxSW()
workboxSW.router.registerRoute( 'https://test.org/css/(.*)', workboxSW.strategies.cacheFirst() )
|
更多用法详见 Workbox 文档。