抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

忙活了半天终于把网站的 Service Worker 缓存配置给弄好了。(暑假放假的第一天就折腾这些,下个月我就是高三学生了)

之前(一直)觉得网站的速度不够快,总要等好几秒才加载完,背景图总是卡下才加载好。我已经尽自己能力把网络加载速度弄到最大了(在不换主题不删功能的条件下),首次加载已经做到最快加载了。也把我能控制地资源全加上了 cache-control 响应头,但第二次打开总有些图片啊、js啊没缓存,比如图片的白一下才显示出来。

今天就去折腾 Service Worker 了,使得博客的访问速度德到大大的提升,虽然不知为何有时图片等突然变得加载很慢,但总的来说比之前快了不少。

Service Worker 的代码我贴在这了,可以用作参考。

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
const swconfig = {
CACHE_VERSION: "v2.0",
runtimeCaching: [
// {
// urlPattern: /^https:\/\/cdn\.example\.com\/.*/,
// handler: "CacheFirst",
// maxAgeSeconds: 60 * 60 * 24 * 365
// },
// {
// urlPattern: /https:\/\/blog.ksable.top\//gi,
// handler: "NetworkFirst",
// maxAgeSeconds: 60 * 60 * 24 * 7
// },
{
urlPattern: RegExp('^https://www.favicon.vip/get.php'),
handler: "CacheFirst",
maxAgeSeconds: 60 * 60 * 24 * 365
},
{
urlPattern: RegExp('^https://image.thum.io/'),
handler: "CacheFirst",
maxAgeSeconds: 60 * 60 * 24 * 30
},
{
urlPattern: /https?:\/\/[^\/]+\/.*\.(png|jpg|jpeg|gif|svg|ico|woff2|ttf|js|css?)(\?.*)?/,
handler: "CacheFirst",
maxAgeSeconds: 60 * 60 * 24 * 365
},
{
urlPattern: /^https:\/\/ik.imagekit.io\//,
handler: "CacheFirst",
maxAgeSeconds: 0
},
{
urlPattern: /^https:\/\/weavatar.com\/avatar\//,
handler: "CacheFirst",
maxAgeSeconds: 60 * 60 * 24 * 30
},
{
urlPattern: /^https:\/\/assets.ksable.top\/js\/my-js.js/,
handler: "CacheFirst",
maxAgeSeconds: 60 * 60 * 24 * 1
}
],
exclude: [
/^https:\/\/blog.ksable.top\/sw.js/gi
],
precacheUrls: [
'/content.json', // 预缓存的内容JSON文件
'/offline.html' // 离线页面
]
};

const CACHE_NAME = `${swconfig.CACHE_VERSION}-cache`;
const CACHE_META_KEY = 'cache-meta';
const OFFLINE_URL = '/offline.html';

// 匹配请求对应的规则
function matchRule(request) {
const url = request.url;

// 检查排除规则
for (const pattern of swconfig.exclude) {
if (pattern.test(url)) return null;
}

// 反向遍历确保后面的规则优先级更高
for (let i = swconfig.runtimeCaching.length - 1; i >= 0; i--) {
const rule = swconfig.runtimeCaching[i];
if (rule.urlPattern.test(url)) {
return rule;
}
}

return null;
}

// 获取缓存元数据
async function getCacheMeta() {
const cache = await caches.open(CACHE_NAME);
const response = await cache.match(CACHE_META_KEY);
return response ? await response.json() : {};
}

// 更新缓存元数据
async function updateCacheMeta(url, timestamp) {
const cache = await caches.open(CACHE_NAME);
const meta = await getCacheMeta();

meta[url] = {
timestamp,
cachedAt: Date.now()
};

await cache.put(
CACHE_META_KEY,
new Response(JSON.stringify(meta), {
headers: { 'Content-Type': 'application/json' }
})
);
}

// 后台更新缓存
async function backgroundUpdate(request, rule) {
try {
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(request);

if (!cachedResponse) return;

// 准备验证头
const headers = new Headers();
const etag = cachedResponse.headers.get('ETag');
const lastModified = cachedResponse.headers.get('Last-Modified');

if (etag) headers.set('If-None-Match', etag);
if (lastModified) headers.set('If-Modified-Since', lastModified);

// 发送验证请求
const networkResponse = await fetch(request, {
headers,
cache: 'no-cache'
});

if (networkResponse.status === 304) { // 未修改
await updateCacheMeta(request.url, Date.now());
} else if (networkResponse.ok) { // 资源已更新
const responseClone = networkResponse.clone();
await cache.put(request, responseClone);
await updateCacheMeta(request.url, Date.now());
}
} catch (error) {
console.error('Background update failed:', error);
}
}

// 安装阶段 - 初始化缓存
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
// 初始化缓存元数据
return cache.put(
CACHE_META_KEY,
new Response('{}', {
headers: { 'Content-Type': 'application/json' }
})
).then(() => {
// 预缓存关键资源
return cache.addAll(swconfig.precacheUrls);
});
}).then(() => self.skipWaiting())
);
});

// 激活阶段 - 清理旧缓存
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(name => {
if (name !== CACHE_NAME && name.startsWith('v')) {
return caches.delete(name);
}
})
);
}).then(() => self.clients.claim())
);
});

// 请求处理
self.addEventListener('fetch', event => {
const request = event.request;

// 只处理GET请求
if (request.method !== 'GET') return;

// 特殊处理导航请求的离线回退
if (request.mode === 'navigate') {
event.respondWith(
(async () => {
try {
// 首先尝试网络请求
const networkResponse = await fetch(request);
return networkResponse;
} catch (error) {
// 网络失败时返回离线页面
const offlineResponse = await caches.match(OFFLINE_URL);
if (offlineResponse) {
return offlineResponse;
}
// 如果离线页面也未缓存,返回简单错误响应
return new Response('Offline', {
status: 503,
statusText: 'Service Unavailable'
});
}
})()
);
return;
}
// 匹配缓存规则
const rule = matchRule(request);
if (!rule) return;

// 处理不同缓存策略
switch (rule.handler) {
case 'NetworkOnly':
event.respondWith(fetch(request));
break;

case 'CacheOnly':
event.respondWith(
caches.match(request).then(response => response || Response.error())
);
break;

case 'NetworkFirst':
event.respondWith(
fetch(request).catch(() => caches.match(request))
);
break;

case 'CacheFirst':
default:
event.respondWith((async () => {
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(request);
const meta = await getCacheMeta();
const url = request.url;
const metaEntry = meta[url];

// 如果找到缓存
if (cachedResponse) {
// 检查是否需要后台更新
if (rule.maxAgeSeconds > 0 && metaEntry) {
const age = (Date.now() - metaEntry.cachedAt) / 1000;
if (age > rule.maxAgeSeconds) {
// 后台更新不影响当前响应
event.waitUntil(backgroundUpdate(request, rule));
}
}
return cachedResponse;
}

// 没有缓存则请求网络
try {
const networkResponse = await fetch(request);
if (networkResponse.ok) {
const responseClone = networkResponse.clone();
await cache.put(request, responseClone);
await updateCacheMeta(url, Date.now());
}
return networkResponse;
} catch (error) {
return Response.error();
}
})());
break;
}
});

评论