【记录】ServiceWorker实现前端资源高可用

Last updated on February 21, 2025 am

JsDelivr 被墙后博客主题换用了国内的CDN,现在想让博客能根据连通性的不同,自动选择最快的 CDN 服务。搜索了一下,有一个 freecdn 项目,使用 Service Worker 来反代 CDN 链接,但写得太复杂了,小博客只想要个简单的,因此依葫芦画瓢,简单改造了一下主题。
目前博客首次打开时会自动测试最快返回的 CDN,使用其作为默认的 CDN;这也带来了额外的好处,失效的 CDN 会被自动切换到其他可用的 CDN。相比简单策略让前端资源实现高可用,Service Worker 方案可以透明接入,无需额外修改。

引入 sw.js

  • 编辑 HEXO 项目的 /scripts 目录,新建 CDN.js,内容如下
1
2
3
4
5
6
7
8
// freecdn
hexo.extend.injector.register('head_begin', `
<script>
const sw = navigator.serviceWorker;
sw.ready.then(() => {if(!sw.controller){location.reload();}});
sw.register('/sw.js', {scope: '/'});
</script>
`);
  • head_begin 会将 <script> 插入到页面 <head> 后的第一行,确保第一时间加载 sw.js
  • 注意作用域 scope,必须为 /,这个不仅代表请求的地址,也代表发出的请求页面
  • /sw.js 必须位于 scope 的同级目录,不能是子目录;全站需要开启 https

创建 sw.js

  • 编辑 HEXO 项目的 /source 目录,新建 sw.js,内容如下
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
const cdn_regex = /.*\/cdn\/(.+?)\//;
const cdn_list = {
"anchor-js": [
"https://cdn1/npm/anchor-js@4.3.1/",
"https://cdn2/npm/anchor-js@4.3.1/",
"https://lib.baomitu.com/anchor-js/5.0.0/",
],
"github-markdown-css": [
"https://cdn1/npm/github-markdown-css@4.0.0/",
"https://cdn2/npm/github-markdown-css@4.0.0/",
"https://lib.baomitu.com/github-markdown-css/4.0.0/",
],
"jquery": [
"https://cdn1/npm/jquery@3.6.4/dist/",
"https://cdn2/npm/jquery@3.6.4/dist/",
"https://lib.baomitu.com/jquery/3.6.4/",
],
"bootstrap": [
"https://cdn1/npm/bootstrap@4.6.1/dist/",
"https://cdn2/npm/bootstrap@4.6.1/dist/",
"https://lib.baomitu.com/twitter-bootstrap/4.6.1/",
],
"tocbot": [
"https://cdn1/npm/tocbot@4.20.1/dist/",
"https://cdn2/npm/tocbot@4.20.1/dist/",
"https://lib.baomitu.com/tocbot/4.20.1/",
],
"nprogress": [
"https://cdn1/npm/nprogress@0.2.0/",
"https://cdn2/npm/nprogress@0.2.0/",
"https://lib.baomitu.com/nprogress/0.2.0/",
],
"katex": [
"https://cdn1/npm/katex@0.16.21/dist/",
"https://cdn2/npm/katex@0.16.21/dist/",
"https://lib.baomitu.com/KaTeX/0.16.2/",
],
"clipboard-js": [
"https://cdn1/npm/clipboard-js@0.3.6/",
"https://cdn2/npm/clipboard-js@0.3.6/",
"https://lib.baomitu.com/clipboard.js/2.0.11/",
],
"hint.css": [
"https://cdn1/npm/hint.css@2.7.0/",
"https://cdn2/npm/hint.css@2.7.0/",
"https://lib.baomitu.com/hint.css/2.7.0/",
]
};
const cdn_index = new Promise((resolve) => {
async function getFastestUrl(urls) {
const testUrl = (one) => {
const url = one[0];
const id = one[1];
const startTime = performance.now();
return fetch(url, {method: 'GET', cache: 'no-cache'})
.then(() => {
return {url, id, time: performance.now() - startTime};
})
.catch(() => new Promise(() => {}));
}
const promises = urls.map(testUrl);
const fastest = await Promise.race(promises);
return fastest;
}
const urls = [
['https://cdn2/npm/anchor-js@4.3.1/anchor.min.js', 1],
['https://cdn1/npm/anchor-js@4.3.1/anchor.min.js', 0],
['https://lib.baomitu.com/anchor-js/5.0.0/anchor.min.js', 2]
];
getFastestUrl(urls).then( (fastest) => {
caches.open('freecdn.limour').then( (cache) => {
resolve(fastest.id);
console.log('最快的 URL:', fastest);
});
});
});

oninstall = (e) => {self.skipWaiting();};

onactivate = (e) => {
e.waitUntil(clients.claim());
console.log(cdn_list);
};

async function cdn_redirect(url, resolve) {
const key = url.pathname.match(cdn_regex)[1];
const newUrl = url.href.replace(cdn_regex, cdn_list[key][await cdn_index]);
console.log(newUrl);
resolve(Response.redirect(newUrl, 301));
}

onfetch = (e) => {
const url = new URL(e.request.url);
if (cdn_regex.test(url.pathname)) {
e.respondWith(new Promise( (resolve) => {
cdn_redirect(url, resolve)
}
))
}
}
  • 请将 cdn1cdn2 替换为合适的 CDN,比如 fastly.jsdelivr.netgcore.jsdelivr.net

修改博客资源地址

  • 修改 _config.fluid.yml 中的 static_prefix
  • 将资源地址改为 /cdn/<key>/

更新博客

  • 完成以上操作后,正常生成并部署博客。

附加 butterfly 主题

  • 引入 sw.js 不变
  • sw.js 修改 cdn_list 如下
1
2
3
4
5
6
7
8
const cdn_list = {
"custom": [
"https://s4.zstatic.net/ajax/libs/",
"https://cdnjs.cloudflare.com/ajax/libs/",
"https://lib.baomitu.com/",
"https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/",
],
}
  • sw.js 修改 urls 如下
1
2
3
4
5
6
const urls = [
['https://s4.zstatic.net/ajax/libs/anchor-js/5.0.0/anchor.min.js', 0],
['https://cdnjs.cloudflare.com/ajax/libs/anchor-js/5.0.0/anchor.min.js', 1],
['https://lib.baomitu.com/anchor-js/5.0.0/anchor.min.js', 2],
['https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/anchor-js/5.0.0/anchor.min.js', 3]
];
  • 修改 butterfly 的配置文件
1
2
3
4
5
CDN:
internal_provider: local
third_party_provider: custom
version: false
custom_format: /cdn/custom/${cdnjs_name}/${version}/${min_cdnjs_file}

【记录】ServiceWorker实现前端资源高可用
https://hexo.limour.top/service-worker-resource-high-availability
Author
Limour
Posted on
February 20, 2025
Updated on
February 21, 2025
Licensed under