# 引言
cdn.jsdelivr.net 知名的免费cdn服务提供商,在去年年底由于大陆域名备案的问题,国内 cdn 解析异常时常无法连接到服务器
尤其是重度依赖 jsDelivr 的 Shoka 主题,在这种情况下由于资源无法加载完成导致网页卡顿,显示效果异常等一系列问题
于是决定探索资源本地化方案去除对 jsDelivr 的依赖
截止到目前国内的 cdn.jsdelivr.net 的 DNS 污染貌似已经解除了,不过资源本地化还是有很多好处的,记录下过程权当自己的备忘了。
# 资源本地化
# jsDelivr依赖热点
Shoka主题用到了不少第三方组件,全部基于jsDelivr加载,资源依赖配置在了 主题配置文件_config.shoka.yml的vendors内。- 依赖的组件又再次依赖
jsDelivr加载依赖valine: gh/amehime/MiniValine@4.2.2-beta10/dist/MiniValine.min.js再次依赖
# 依赖资源下载
本地化的一个非常重要的部分就是,将远程资源下载到本地。好在依赖项不是很多哈,可以慢慢手动操作一波,就是慢点 🤣~~
需要注意的是,为了最小化修改,其实应该在本地享有和链接一样的目录结构,这样可以只修改加载代码而不用修改配置
我们在博客主目录 source 下新建 cdn.jsdelivr.net 文件夹,后续下载都放在这个目录下(通过 Hexo 发布后的文件地址访问将会是 站点网址\cdn.jsdelivr.net\相对路径
vendors依赖资源下载
很多组件基本就是加载最后压缩过的单文件 *.min.js,所以直接 py 脚本一波走,把 vendors 内的配置全部下载到本地,同时根据链接在本地创建相对的目录结构
import urllib.request
import requests
import re
import os
def get_file(url):
"""
递归下载网站的文件
:param url:
:return:
"""
if isFile(url):
print(url)
try:
download(url)
except:
pass
else:
urls = get_url(url)
for u in urls:
get_file(u)
def isFile(url):
"""
判断一个链接是否是文件
:param url:
:return:
"""
if url.endswith('/'):
return False
else:
return True
def download(url):
"""
:param url:文件链接
:return: 下载文件,自动创建目录
"""
full_name = url.split('//')[-1]
filename = full_name.split('/')[-1]
dirname = "/".join(full_name.split('/')[:-1])
if os.path.exists(dirname):
pass
else:
os.makedirs(dirname, exist_ok=True)
urllib.request.urlretrieve(url, full_name)
def get_url(base_url):
"""
:param base_url:给定一个网址
:return: 获取给定网址中的所有链接
"""
text = ''
try:
text = requests.get(base_url).text
except Exception as e:
print("error - > ", base_url, e)
pass
reg = '<a href="(.*)">.*</a>'
urls = [base_url + url for url in re.findall(reg, text) if url != '../']
return urls
if __name__ == '__main__':
with open('list.txt', 'r') as f:
lines = f.readlines()
url_list = []
for line in lines:
get_file(line.strip('\n'))将需要下载的链接放在和 py 脚本同级的 list.txt 内(vendors 内部链接加上 https://cdn.jsdelivr.net/)

- 个别依赖项整包下载
个别依赖项不只是简单的需要 *.min.js 文件就够了,还需要额外依赖很多资源文件,所以对于这种我们采取手动下载整个组件源码放置到本地
目前发现有如下组件(若有遗漏,欢迎指正!)
katex
下载方式:
访问页面 https://www.jsdelivr.com/package/ + npm/katex(若需下载其他组件替换这里即可) 即可前往下载页
选择对应版本下载即可,下载完成后将对应的文件解压,放置在 步骤1 的本地文件夹内即可
- 依赖项依赖下载
个别组件还套娃依赖其他组件,这一部分也需要处理🤣🤣🤣
目前发现的有如下组件(若有遗漏,欢迎指正!)
valine
找到我们下载的 MiniValine.min.js 文件打开,全局搜索 jsdelivr,可以发现如下依赖项
第三方组件:
https://cdn.jsdelivr.net/npm/xss@1.0.11/dist/xss.min.js
https://cdn.jsdelivr.net/npm/ua-parser-js@1.0.2/dist/ua-parser.min.js
https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js
https://cdn.jsdelivr.net/npm/marked@1.2.0/lib/marked.min.js
https://cdn.jsdelivr.net/npm/blueimp-md5@2.18.0/js/md5.min.js
https://cdn.jsdelivr.net/npm/leancloud-storage@4.12.2/dist/av-min.js这儿略微麻烦一点,jsDelivr 有 combine 的功能,可以做到一次请求多个 目标文件 和批成一个文件,上面的组件以空格为分割分别分成了两个地方加载,我们需要进行如下步骤
- 合批请求保存两批
js文件
// 另存为 /cdn.jsdelivr.net/combine/valine/parse.js
https://cdn.jsdelivr.net/combine/npm/xss@1.0.11/dist/xss.min.js,npm/ua-parser-js@1.0.2/dist/ua-parser.min.js,npm/mathjax@3.2.1/es5/tex-svg.js,npm/marked@1.2.0/lib/marked.min.js
// 另存为 /cdn.jsdelivr.net/combine/valine/parse.min.js
https://cdn.jsdelivr.net/combine/npm/blueimp-md5@2.18.0/js/md5.min.js,npm/leancloud-storage@4.12.2/dist/av-min.js- 更改对应依赖的加载代码,主要是替换其中的合批下载链接为自己本地资源路径链接
(t=[],window.autosize||t.push("npm/autosize@4.0.2/dist/autosize.min.js"),window.filterXSS||t.push("npm/xss@1.0.8/dist/xss.min.js"),e.config.closeUA||window.UAParser||t.push("npm/ua-parser-js@0.7.22/src/ua-parser.min.js"),!e.math&&void 0!==e.config.math||"undefined"!=typeof MathJax||t.push("npm/mathjax@3/es5/tex-svg.js"),!e.md&&void 0!==e.config.md||window.marked||t.push("npm/marked@1.2.0/lib/marked.min.js"),(0,i.default)("https://cdn.jsdelivr.net/combine/",(function(){e.initBody(),window.MV.scriptEle=!0}),1==window.MV.scriptEle||0==t.length))
// 替换为
(t=[],window.autosize||t.push("npm/autosize@4.0.2/dist/autosize.min.js"),window.filterXSS||t.push("npm/xss@1.0.8/dist/xss.min.js"),e.config.closeUA||window.UAParser||t.push("npm/ua-parser-js@0.7.22/src/ua-parser.min.js"),!e.math&&void 0!==e.config.math||"undefined"!=typeof MathJax||t.push("npm/mathjax@3/es5/tex-svg.js"),!e.md&&void 0!==e.config.md||window.marked||t.push("npm/marked@1.2.0/lib/marked.min.js"),(0,i.default)("/cdn.jsdelivr.net/combine/valine/parse.js"+t.join(","),(function(){e.initBody(),window.MV.scriptEle=!0}),1==window.MV.scriptEle||0==t.length))
(e.config.NoRecordIP||(void 0===window.MV.ip?(0,a.default)(e):e.C.ip=window.MV.ip),t=[],window.md5||t.push("npm/blueimp-md5@2.18.0/js/md5.min.js"),window.AV||t.push("npm/leancloud-storage@4/dist/av-min.js"),(0,i.default)("https://cdn.jsdelivr.net/combine/"+t.join(","),(function(){e.initCheck(),window.MV.scriptInit=!0}),1==window.MV.scriptInit||0==t.length))
// 替换为
(e.config.NoRecordIP||(void 0===window.MV.ip?(0,a.default)(e):e.C.ip=window.MV.ip),t=[],window.md5||t.push("npm/blueimp-md5@2.18.0/js/md5.min.js"),window.AV||t.push("npm/leancloud-storage@4/dist/av-min.js"),(0,i.default)("/cdn.jsdelivr.net/combine/valine/parse.min.js",(function(){e.initCheck(),window.MV.scriptInit=!0}),1==window.MV.scriptInit||0==t.length))表情包:
https://cdn.jsdelivr.net/npm/alus@latest
https://cdn.jsdelivr.net/gh/MiniValine/qq@master
https://cdn.jsdelivr.net/gh/MiniValine/Bilibilis@master
https://cdn.jsdelivr.net/gh/MiniValine/tieba@master
https://cdn.jsdelivr.net/gh/MiniValine/twemoji@master
https://cdn.jsdelivr.net/gh/MiniValine/weibo@master表情包,直接采取 个别依赖项整包下载 中的方式整包进行下载放置在本地的资源文件夹
完成后,需要进行一个操作,替换 MiniValine.min.js 文件中的下载链接 https://cdn.jsdelivr.net/ 替换为 /cdn.jsdelivr.net/
# 资源加载代码修改
全局搜索 jsdelivr 主要观察主题文件夹内相关文件,我们需要修改如下几处
- 在主题配置文件
_config.shoka.yml内新增配置
# 本地资源文件夹
resource: '/cdn.jsdelivr.net/'
# 现有 vendors 配置新增
vendors:
# 是否使用本地化资源
locally: true- 脚本配置项修改
修改主题文件 themes/shoka/scripts/generaters/script.js,将配置文件新增的配置在代码内初始化,供后续加载使用
// 大概12行左右
var siteConfig = {
version: env['version'],
hostname: config.url,
root: config.root,
statics: theme.statics,
// 新增
resource: theme.resource,
vendors_locally: theme.vendors.locally,
// ...
}- 主题自定义辅助函数
_vendor_js
文件 themes/shoka/scripts/helpers/asset.js 找到辅助函数新增如下配置(大约在45行左右)
hexo.extend.helper.register('_vendor_js', () => {
const config = hexo.theme.config.vendors.js;
if (!config) return '';
// 新增
if (hexo.theme.config.vendors.locally) {
return htmlTag('script', { src: hexo.theme.config.resource + 'combine/js/base.js' }, '');
}
// Get a font list from config
let vendorJs = ['pace', 'pjax', 'fetch', 'anime', 'algolia', 'instantsearch', 'lazyload', 'quicklink'].map(item => {
if (config[item]) {
return config[item];
}
return '';
});
// ...
});- 主题工具脚本修改
文件 themes/shoka/source/js/_app/utils.js,函数 assetUrl 直接替换为如下代码即可
const assetUrl = function (asset, type) {
var str = CONFIG[asset][type];
if (str.indexOf("http") > -1)
return str;
if (str.indexOf("combine") > -1) {
if (CONFIG.vendors_locally) {
return CONFIG.resource + "combine/" + asset + "/" + type + "." + asset;
}
return "//cdn.jsdelivr.net/" + str;
} else if (str.indexOf("npm") > -1 || str.indexOf("gh") > -1) {
if (CONFIG.vendors_locally) {
return CONFIG.resource + str;
}
return "//cdn.jsdelivr.net/" + str;
}
return statics + str;
}# 周边配置修改
# 代码压缩白名单
本地资源文件夹是放置在了 source/cdn.jsdelivr.net/,如果启用了代码压缩,我们需要将本地资源文件夹加入 白名单
Hexo 配置文件 _config.yml 中 minify 部分修改如下
minify:
html:
enable: true
exclude: # 排除 hexo-feed 用到的模板文件
- '**/json.ejs'
- '**/atom.ejs'
- '**/rss.ejs'
- '**/cdn.jsdelivr.net/**/*.*'
css:
enable: true
exclude:
- '**/*.min.css'
- '**/cdn.jsdelivr.net/**/*.*'
js:
enable: true
mangle:
toplevel: true
output:
compress:
exclude:
- '**/*.min.js'
- '**/custom/*.js'
- '**/cdn.jsdelivr.net/**/*.*'- '**/cdn.jsdelivr.net/**/*.*' 为新增部分,排除该目录所有资源压缩
# 自建CDN(可选)
到现在的步骤,其实 jsDelivr 本地化已经完成,但是它引入了几个新的弊端
- 本地资源文件夹 其实很少更改,现在每次
Hexo推送都会把它推送一次,极大的增加了推送时间。 - 如果使用了 云服务厂商
对象存储(COS)来做静态站点,特别的还挂载了CDN回源,那一般对象存储都会配置云函数,在发生文件变更时刷新CDN缓存,在推送刷新时,会浪费刷新次数(土豪有钱请随意),使得真正需要刷新的文件可能会刷新失败。
针对这种情况,如果本地博客文件基于 GitHub 做版本管理,那么可以采取如下操作,这儿只是描述了大概思路,具体的可以灵活变通
- 本地资源文件夹单独作为一个资源库,使用
Git管理 - 使用
Git Action, 在资源文件库主分支上传时,同步到COS存储 _config.shoka.yml配置resource为CDN域名MiniValine.min.js资源路径替换为CDN相对路径访问链接- 本地博客文件仓库,使用
Git submodule引入资源文件夹 hexo三连前,使用rm -rf命令删除public文件夹下资源文件夹,避免推送
这个可以在 package.json 配置一个命令,scripts 内新增,后续使用 npm run upload 替代 hexo 三连
"scripts": {
"upload": "hexo clean && hexo g && rm -rf ./public/cdn.jsdelivr.net/ && hexo d",
},
# 结语
至此 jsDelivr 本地化完成,博客依赖项加载再也不用看 jsDelivr 的心情了,Hexo 三连即可看到效果,后续如果要增加其他资源可以直接放置在本地资源文件夹

有什么想法,欢迎留下评论。