Service Worker简述原创
# 虽然云Q同学由于工作原因,好久没有更新了。(虽然有很多想分享的)
提示
今天的文章有点长,如果你没时间一次性读完,建议先收藏
# 背景
由于最近使用语雀比较频繁,偶然一次点进去语雀的源码看到了serviceWorker,便产生了好奇去研究
丰富的离线体验、定期的后台同步以及推送通知等通常需要将面向本机应用的功能将引入到网页应用中。Service Worker 提供所有这些功能所依赖的技术基础。
# 什么是Service Worker
Service Worker 是浏览器在后台独立于网页运行的脚本,它打开了通向不需要网页或用户交互的功能的大门。现在,它们已包括如推送通知和后台同步等功能。将来,Service Worker 将会支持如定期同步或地理围栏等其他功能。 在 Service Worker 出现前,存在能够在网络上为用户提供离线体验的另一个 API,称为 AppCache。AppCache API 存在的许多相关问题,在设计 Service Worker 时已予以避免。
Service Worker 相关注意事项:
- 它是一种 JavaScript Worker,无法直接访问 DOM。Service Worker 通过响应 postMessage 接口发送的消息来与其控制的页面通信,页面可在必要时对 DOM 执行操作。
- Service Worker 是一种可编程网络代理,让您能够控制页面所发送网络请求的处理方式。
- Service Worker 在不用时会被中止,并在下次有需要时重启,因此,您不能依赖 Service Worker onfetch 和 onmessage 处理程序中的全局状态。如果存在您需要持续保存并在重启后加以重用的信息,Service Worker 可以访问 IndexedDB API。
- Service Worker 广泛地利用了 promise
# Service Worker生命周期
Service Worker 的生命周期完全独立于网页。
要为网站安装服务工作线程,您需要先在页面的 JavaScript 中注册。注册服务工作线程将会导致浏览器在后台启动服务工作线程安装步骤。
在安装过程中,通常需要缓存某些静态资产。如果所有文件均已成功缓存,那么 Service Worker 就安装完毕。如果任何文件下载失败或缓存失败,那么安装步骤将会失败,Service Worker 就无法激活(也就是说, 不会安装)。如果发生这种情况,不必担心,它下次会再试一次。但这意味着,如果安装完成,便知道已在缓存中有哪些静态资产。
安装之后,接下来就是激活步骤,这是管理旧缓存的绝佳机会,后面将在 Service Worker 的更新部分对此详加介绍。
激活之后,Service Worker 将会对其作用域内的所有页面实施控制,不过,首次注册该 Service Worker 的页面需要再次加载才会受其控制。服务工作线程实施控制后,它将处于以下两种状态之一: 服务工作线程终止以节省内存,或处理获取和消息事件,从页面发出网络请求或消息后将会出现后一种状态。
以下为简化的生命周期:
图片来源于 google (opens new window) 开发
# 先决条件
浏览器支持
可用的浏览器日益增多。Service Worker 受 Chrome、Firefox 和 Opera 支持。Microsoft Edge 现在表示公开支持。甚至 Safari 也暗示未来会进行相关开发。您可以在 Jake Archibald 的 is Serviceworker ready 网站上查看所有浏览器的支持情况 。
- 需要HTTPS/localhost
在开发过程中,可以通过 localhost 使用 Service Worker,但如果要在网站上部署 Service Worker,则需要在服务器上设置 HTTPS。
使用服务工作线程,您可以劫持连接、编撰以及过滤响应。这是一个很强大的工具。您可能会善意地使用这些功能,但中间人可会将其用于不良目的。为避免这种情况,可仅在通过 HTTPS 提供的页面上注册 Service Worker,如此我们便知道浏览器接收的 Service Worker 在整个网络传输过程中都没有被篡改。
如果想要向服务器添加 HTTPS,您需要获得 TLS 证书并在服务器上进行设置。具体因您的设置而异,因此请查看服务器的文档,并务必查阅 Mozilla SSL 配置生成器,了解最佳做法。
# 注册Service Worker
若要安装 Service Worker,您需要通过在页面中对其进行注册来启动安装。这将告诉浏览器 Service Worker JavaScript 文件的位置。
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
2
3
4
5
6
7
8
9
10
11
此代码用于检查 Service Worker API 是否可用,如果可用,则在页面加载后注册位于 /sw.js 的 Service Worker。
每次页面加载无误时,即可调用 register();浏览器将会判断服务工作线程是否已注册并做出相应的处理。
register() 方法的精妙之处在于服务工作线程文件的位置。您会发现在本例中服务工作线程文件位于根网域。这意味着服务工作线程的作用域将是整个来源。换句话说,Service Worker 将接收此网域上所有事项的 fetch 事件。如果我们在 /example/sw.js 处注册 Service Worker 文件,则 Service Worker将只能看到网址以/example/ 开头(即/example/page1/、/example/page2/)的页面的 fetch 事件。
现在,您可以通过转至 chrome://inspect/#service-workers 并寻找您的网站来检查 Service Worker 是否已启用。
首次实施 Service Worker 时,您还可以通过 chrome://serviceworker-internals 来查看 Service Worker 详情。如果只是想了解 Service Worker 的生命周期,这仍很有用,但是日后其很有可能被 chrome://inspect/#service-workers 完全取代。
您会发现,它还可用于测试隐身窗口中的服务工作线程,您可以关闭服务工作线程并重新打开,因为之前的服务工作线程不会影响新窗口。从无痕式窗口创建的任何注册和缓存在该窗口关闭后均将被清除。
# 安装Service Worker
在受控页面启动注册流程后,我们来看看处理 install 事件的 Service Worker 脚本。
最基本的例子是,您需要为安装事件定义回调,并决定想要缓存的文件。
self.addEventListener('install', function(event) {
// Perform install steps
});
2
3
在 install 回调的内部,我们需要执行以下步骤:
打开缓存。
缓存文件;
确认所有需要的资产是否已缓存;
2
3
var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js'
];
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
此处,我们以所需的缓存名称调用 caches.open(),之后再调用 cache.addAll() 并传入文件数组。这是一个 promise 链(caches.open() 和 cache.addAll())。 event.waitUntil() 方法带有 promise 参数并使用它来判断安装所花费的时间,以及安装是否成功。
如果所有文件都成功缓存,则将安装 Service Worker。如有任何文件无法下载,则安装步骤将失败。这可让您依赖于所定义的所有资产,但也意味着需要对您决定在安装步骤缓存的文件列表格外留意。定义一个过长的文件列表将会增加文件缓存失败的几率,从而导致服务工作线程未能安装。
这仅是一个示例,实际您可以在 install 事件中执行其他任务,或完全避免设置 install 事件侦听器。
# 缓存和返回请求
您已安装 Service Worker,现在可能会想要返回一个缓存的响应,对吧?
在安装 Service Worker 且用户转至其他页面或刷新当前页面后,Service Worker 将开始接收 fetch 事件。下面提供了一个示例。
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});
2
3
4
5
6
7
8
9
10
11
12
13
这里我们定义了 fetch 事件,并且在 event.respondWith() 中,我们传入来自 caches.match() 的一个 promise。此方法检视该请求,并从服务工作线程所创建的任何缓存中查找缓存的结果。
如果发现匹配的响应,则返回缓存的值,否则,将调用 fetch 以发出网络请求,并将从网络检索到的任何数据作为结果返回。这是一个简单的例子,它使用了在安装步骤中缓存的所有资产。
如果希望连续缓存新请求,可以通过处理 fetch 请求的响应并将其添加到缓存来实现,如下所示。
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
// IMPORTANT:Clone the request. A request is a stream and
// can only be consumed once. Since we are consuming this
// once by cache and once by the browser for fetch, we need
// to clone the response.
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function(response) {
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// IMPORTANT:Clone the response. A response is a stream
// and because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have two streams.
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
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
执行的操作如下:
在 fetch 请求中添加对 .then() 的回调。
获得响应后,执行以下检查:
- 确保响应有效。
- 检查并确保响应的状态为 200。
- 确保响应类型为 basic,亦即由自身发起的请求。这意味着,对第三方资产的请求也不会添加到缓存。
2
3
4
5
6
7
8
9
如果通过检查,则克隆响应。这样做的原因在于,该响应是数据流, 因此主体只能使用一次。由于我们想要返回能被浏览器使用的响应,并将其传递到缓存以供使用,因此需要克隆一份副本。我们将一份发送给浏览器,另一份则保留在缓存。
# 更新Service Worker
在某个时间点,您的 Service Worker 需要更新。此时,您需要遵循以下步骤:
- 更新您的服务工作线程 JavaScript 文件。用户导航至您的站点时,浏览器会尝试在后台重新下载定义 Service Worker 的脚本文件。如果 Service Worker 文件与其当前所用文件存在字节差异,则将其视为新 Service Worker。
- 新 Service Worker 将会启动,且将会触发 install 事件。
- 此时,旧 Service Worker 仍控制着当前页面,因此新 Service Worker 将进入 waiting 状态。
- 当网站上当前打开的页面关闭时,旧 Service Worker 将会被终止,新 Service Worker 将会取得控制权。
- 新 Service Worker 取得控制权后,将会触发其 activate 事件。
2
3
4
5
6
7
8
9
出现在 activate 回调中的一个常见任务是缓存管理。您希望在 activate 回调中执行此任务的原因在于,如果您在安装步骤中清除了任何旧缓存,则继续控制所有当前页面的任何旧 Service Worker 将突然无法从缓存中提供文件。
比如说我们有一个名为 'my-site-cache-v1' 的缓存,我们想要将该缓存拆分为一个页面缓存和一个博文缓存。这就意味着在安装步骤中我们创建了两个缓存: 'pages-cache-v1' 和 'blog-posts-cache-v1',且在激活步骤中我们要删除旧的 'my-site-cache-v1'。
以下代码将执行此操作,具体做法为: 遍历 Service Worker 中的所有缓存,并删除未在缓存白名单中定义的任何缓存。
self.addEventListener('activate', function(event) {
var cacheAllowlist = ['pages-cache-v1', 'blog-posts-cache-v1'];
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (cacheAllowlist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 瑕疵与问题
如果 Worker注册后未在 chrome://inspect/#service-workers 或 chrome://serviceworker-internals 中显示,则有可能是引发错误或向 event.waitUntil() 发送被拒绝的 promise 而导致无法安装。
要解决该问题,请转至 chrome://serviceworker-internals 并勾选“Open DevTools window and pause JavaScript execution on service worker startup for debugging”,然后将调试程序语句置于安装事件开始处。这与未捕获异常中的暂停共同揭露问题。
使用 fetch 时,默认情况下请求中不包含 Cookie 等凭据。如需凭据,改为调用
fetch(url, {
credentials: 'include'
})
2
3
这一行为是有意为之,可以说比 XHR 更复杂的以下默认行为更好: 如果网址具有相同来源,则默认发送凭据,否则忽略。提取的行为更接近于其他 CORS 请求,如 ,它将决不会发送 Cookie,除非您使用 选择加入。
非 CORS 默认失败
默认情况下,从不支持 CORS 的第三方网址中提取资源将会失败。您可以向请求中添加 no-CORS 选项来克服此问题,不过这可能会导致“不透明”的响应,这意味着您无法辨别响应是否成功。
cache.addAll(urlsToPrefetch.map(function(urlToPrefetch) {
returnnewRequest(urlToPrefetch, { mode: 'no-cors'});}))
.then(function() {
console.log('All resources have been fetched and cached.');
});
2
3
4
5
处理响应式图像
srcset 属性或 元素将在运行期间选择最适当的图像资产,并发出网络请求。
对于 Service Worker,如果您想要在安装过程中缓存图像,您有下列几种选择:
安装 <picture> 元素和 srcset 属性将请求的所有图像。
安装一个低分辨率版本的图像。
安装一个高分辨率版本的图像。
2
3
4
5
实际上,您应该选择 2 或 3,因为下载所有图像会浪费存储空间。
假定您在安装期间选择安装低分辨率版本的图像,在页面加载时您想要尝试从网络中检索高分辨率的图像,但是如果检索高分辨率版本失败,则回退到低分辨率版本。这没有问题,而且这种做法很好,但是有另外一个问题。
如果我们有以下两张图像:
屏幕密度 | 宽度 | 高度 |
---|---|---|
1x | 400 | 400 |
2x | 800 | 800 |
在 srcset 图像中,我们有一些像这样的标记:
<img src="image-src.png" srcset="image-src.png 1x, image-2x.png 2x" />
如果我们使用的是 2x 显示屏,浏览器将会选择下载 image-2x.png。如果我们处于离线状态,您可以对请求执行 .catch() 并返回 image-src.png(如已缓存)。但是,浏览器会期望 2x 屏幕上的图像有额外的像素,这样图像将显示为 200x200 CSS 像素而不是 400x400 CSS 像素。解决该问题的唯一办法是设定固定的图像高度和宽度。
<img src="image-src.png" srcset="image-src.png 1x, image-2x.png 2x" style="width:400px; height: 400px;" />
对于要用于艺术指导的 元素,这会变得相当困难,而且很大程度上取决于图像的创建和使用方式,但是您可以使用类似于 srcset 的方法
# 使用场景
- 预加载 && 离线化
打开语雀官网就可以看到https://www.yuque.com/ (opens new window),首次是从网络请求,但是第二次进入后便是通过serviceworker读取
访问开发者后台的service-worker.js查看源代码
self.assets = ["https://gw.alipayobjects.com/os/chair-script/skylark/common.b4c03be5.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/common.e2d71ce8.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__components__Explore__Recommend~p__explore__routers__Docs~p__explore__routers__Repos~p_~d6257766.aa1bcc43.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__components__Explore__Recommend~p__explore__routers__Docs~p__explore__routers__Repos~p_~d6257766.fac19d5b.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/c__Lakex~p__editor__routers__TextEditor.9defba11.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/c__Lakex~p__editor__routers__TextEditor.3a98afb8.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__bookRepo__routers.244d87f7.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__bookRepo__routers.ef3c862f.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/c__Lakex.acd5cec4.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/c__Lakex.653d1e93.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/layout__MainLayout.ae548301.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/layout__MainLayout.c0075e36.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__bookRepo__model.511a24e3.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__bookRepo__routers__EditCustomIndex.d4fbfe9e.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__bookRepo__routers__EditCustomIndex.28048163.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__bookRepo__routers__ShareExpired.8113c1a2.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__bookRepo__routers__ShareExpired.b6dff962.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__bookRepo__routers__SharePassword.1a6ae926.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__bookRepo__routers__SharePassword.f76c7685.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__components__Explore__Events.6d43e196.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__components__Explore__Events.979d04c6.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__components__Explore__Recommend.ab8c57cb.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__components__Explore__Recommend.ac025d9d.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__model.2d27d4bc.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__App.4d4a0a8c.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__App.08fcac15.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__CollabBooks.40627926.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__CollabBooks.91d8d56d.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__Collects.0a516ca7.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__Collects.b5f172fe.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__Dashboard.5f89b7f3.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__Dashboard.be7c1714.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__Explore.b51bb073.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__Groups.198f522b.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__Groups.ad67b3b7.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__History.086ddd9c.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__History.5387e7a8.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__MyBooks.40627926.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__MyBooks.61608f6e.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__Notes.a878e2d7.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__Notes.ffe2cc7a.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__Recycles.ab448ca1.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__dashboard__routers__Recycles.3434b09c.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__doc__model__page.424fcfd2.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__doc__routers.66f72a35.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__doc__routers.39267068.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__doc__routers__version.e7b71a05.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__doc__routers__version.186ff53b.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__model.7ef254a2.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__Asl.60282b53.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__Asl.fa585dad.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__TextEditor.f413dbfc.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__TextEditor.81c5d11d.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__board.591d841b.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__board.832f1003.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__doc.a1ccd84d.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__doc.e652cf65.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__embed.500645af.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__embed.743631c5.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__embed_extreme.5563bfd4.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__embed_extreme.88434cbe.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__sheet.8a86af45.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__sheet.2daf2fb0.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__show.75463f8e.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__show.14157f9c.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__table.60aad9c2.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__editor__routers__table.29a799ed.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__model.263db0b2.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Books.cfc93cd2.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Books.8ffd07d8.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Custom.710dc957.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Custom.604bf4aa.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Embed.daf129f3.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Embed.1a8cd333.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Error403.8113c1a2.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Error403.e426da8e.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Group.a1fbd1b1.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Group.aca6ba40.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Members.c73713ca.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Members.fc9d4e92.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Migrate.e821f2d6.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Migrate.c5718315.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Recycles.724821a4.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Recycles.9b99a94d.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Statistics.e849f2e3.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Statistics.e2b4dc68.async.js", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Upgrade.a42075c1.chunk.css", "https://gw.alipayobjects.com/os/chair-script/skylark/p__group__routers__Upgrade.d80c9df1.async.js", "https://gw.alipayobjects.com/os/lib/??react/16.13.1/umd/react.production.min.js,react-dom/16.13.1/umd/react-dom.production.min.js,react-dom/16.13.1/umd/react-dom-server.browser.production.min.js,moment/2.24.0/min/moment.min.js", "https://gw.alipayobjects.com/as/g/larkgroup/lake-codemirror/6.0.2/CodeMirror.js", "https://gw.alipayobjects.com/as/g/larkgroup/lark-sheet/11.0.20/lark-sheet.css", "https://gw.alipayobjects.com/a/g/lark/??immutable/3.8.2/immutable.min.js", "https://gw.alipayobjects.com/as/g/larkgroup/lark-sheet/11.0.20/lark-sheet.js"];
self.resourceBase = "https://gw.alipayobjects.com/os/chair-script/skylark/";
self.addEventListener("install", (e => {
//预加载常用资源
Array.isArray(self.assets) && e.waitUntil(caches.open("v1").then((e => {
e.addAll(self.assets)
})))
})), self.addEventListener("activate", (e => {
Array.isArray(self.assets) && caches.open("v1").then((e => {
e.keys().then((t => {
t.forEach((t => {
//过期资源释放
self.assets.includes(t.url) || e.delete(t)
}))
}))
}))
}));
const r = [self.resourceBase, "https://at.alicdn.com/t/", "https://gw.alipayobjects.com/os/"];
self.addEventListener("fetch", (e => {
//拦截资源,满足上述域名,优先使用缓存,否则使用网络下载资源并更新资源。
r.some((t => e.request.url.startsWith(t))) && e.respondWith(caches.match(e.request).then((t => t && 200 === t.status ? t : fetch(e.request).then((t => {
if (200 !== t.status) return t;
const r = t.clone();
return caches.open("v1").then((t => {
t.put(e.request, r)
})), t
})).catch((() => {})))))
}))
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
此处只是访问首页,Cache 就已经加载了大量的 js 资源和 css 资源,总缓存占据了 64 MB,作为一个偏展示型官网,加载 64MB 显示是不科学的,更合理的解释是,在不堵塞用户使用的情况,优先缓存了后续页面所需要使用的 js 文件来加速后续页面的打开速度,比如其 TextEditor 组件就是为了后续的编辑页提前缓存的:
其它预加载使用场景
那么现在换为移动App的场景,以一个复杂的页面为例,如果Boss 今天给你的任务是秒开该工作台应用下的一个子应用,包括第一次打开,如何利用 ServiceWorker 来做到?
错误页面
当你的某个重要资源无法从网络下载时,可以使用降级策略,返回到一个 Error 页面,这个 Error 页面可以是你事先缓存的一个资源。
以上面提到的工作台场景为例,移动端最常见的故障是子应用白屏,子应用白屏时会在进入正常页面之前产生出完全白色的界面,原因是下一个页面的某个 html 或者 js 资源获取异常或者加载时间很漫长,导致了页面白屏。
如果使用了ServiceWorker 拦截了请求,就可以在无法正常返回资源的时候返回给前台一个error页面,当前如果把 error 链接设置成一个动态地址,我们就可以实现在后台做到各种策略的优雅降级了。