Hexo NexT 主题支持自定义 CDN 服务商

abyss

事情的起因就是 JSDelivr 在 2021-12-20 号这天 ICP 许可证(备案)被取消,因大陆 CDN 服务只能为有 ICP 许可证的网址提供服务,所以大陆 CDN 也因此失效,好在 JSDelivr 官方响应很快,经过短暂的波动后立马切换到了全球 CDN 上,目前大陆访问只是速度慢一点。

本博客使用 Hexo 框架和 NexT 主题,默认使用的是 JSDelivr 作为 JS、CSS 资源的 CDN 服务提供商,因为上述原因,所以需要切换到大陆 CDN,NexT 主题目前只支持 local | jsdelivr | unpkg | cdnjs ,很遗憾目前除了 local,其他都无法支持大陆加速,在 NexT 新版本未支持前,先做了一些小的改动以支持切换 CDN 服务提供商。

Hexo v8.9.0 已经原生支持自定义 CDN 服务商配置了。

vendors:
  # The CDN provider of NexT internal scripts.
  # Available values: local | jsdelivr | unpkg | cdnjs | custom
  # Warning: If you are using the latest master branch of NexT, please set `internal: local`
  internal: custom
  # The default CDN provider of third-party plugins.
  # Available values: local | jsdelivr | unpkg | cdnjs | custom
  # Dependencies for `plugins: local`: https://github.com/next-theme/plugins
  plugins: custom
  # Custom CDN URL
  # For example:
  # custom_cdn_url: https://cdn.jsdelivr.net/npm/${npm_name}@${version}/${minified}
  # custom_cdn_url: https://cdnjs.cloudflare.com/ajax/libs/${cdnjs_name}/${version}/${cdnjs_file}
  custom_cdn_url: https://unpkg.zhimg.com/${npm_name}@${version}/${npm_file}

方案本质上修改 NexT 的 CDN Settings 配置,拓展选项,用以支持个性化 CDN 服务商,但是因为涉及的资源太多,如果随意替换可能出现资源找不到的情况,所以暂时选择了 jsdelivr | unpkg | cdnjs 其中的大陆镜像站点,这样改动最小并且还保证了兼容性。

配置结构没变化,只是配置项成了自定义服务商,这里只是一个代号,比如:cdn-internal :

vendors:
  # The CDN provider of NexT internal scripts.
  # Available values: local | jsdelivr | unpkg | cdnjs
  # Warning: If you are using the latest master branch of NexT, please set `internal: local`
  internal: cdn-internal
  # The default CDN provider of third-party plugins.
  # Available values: local | jsdelivr | unpkg | cdnjs
  # Dependencies for `plugins: local`: https://github.com/next-theme/plugins
  plugins: cdn-internal

安装 patch-package 依赖:

npm install patch-package

修改 NexT 源码以支持个性化 CDN 服务商,需要修改 2 个文件:

一是修改 node_modules/hexo-theme-next/scripts/events/lib/vendors.js 一处,你用的哪类 CDN 的大陆镜像站点,就复制对应的类别改一下地址就行,比如我用的是 UNPKG 大陆镜像站点,cdn-internal 的格式则为 https://xxxx//${name}@${version}/${file}

const links = {
  local : url_for.call(hexo, `lib/${name}/${file}`),
  cdn-internal : `https://unpkg.zhimg.com/${name}@${version}/${file}`,
  jsdelivr: `https://cdn.jsdelivr.net/npm/${name}@${version}/${file}`,
  unpkg : `https://unpkg.com/${name}@${version}/${file}`,
  cdnjs : `https://cdnjs.cloudflare.com/ajax/libs/${alias || name}/${version}/${file.replace(/^(dist|lib|)\/(browser\/|)/, '')}`
};

二是修改 node_modules/hexo-theme-next/scripts/helpers/engine.js 两处,修改方式同上面一样。

hexo.extend.helper.register('next_js', function(file, pjax = false) {
  const { next_version } = this;
  const { internal } = this.theme.vendors;
  const minified_file = file.endsWith('.js') && !file.endsWith('.min.js') ? file.slice(0, -3) + '.min.js' : file;
  const links = {
    local : this.url_for(`${this.theme.js}/${file}`),
    cdn-internal : `https://unpkg.zhimg.com/hexo-theme-next@${next_version}/source/js/${file}`,
    jsdelivr: `https://cdn.jsdelivr.net/npm/hexo-theme-next@${next_version}/source/js/${minified_file}`,
    unpkg : `https://unpkg.com/hexo-theme-next@${next_version}/source/js/${file}`,
    cdnjs : `https://cdnjs.cloudflare.com/ajax/libs/hexo-theme-next/${next_version}/${minified_file}`
  };
  const src = links[internal] || links.local;
  return `<script ${pjax ? 'data-pjax ' : ''}src="${src}"></script>`;
});
hexo.extend.helper.register('next_pre', function() {
  const { preconnect } = this.theme;
  if (!preconnect) return '';
  const { enable, host } = this.theme.font;
  const { internal, plugins } = this.theme.vendors;
  const links = {
    local : '',
    cdn-internal : 'https://unpkg.zhimg.com',
    jsdelivr: 'https://cdn.jsdelivr.net',
    unpkg : 'https://unpkg.com',
    cdnjs : 'https://cdnjs.cloudflare.com'
  };
  const h = enable ? host || 'https://fonts.googleapis.com' : '';
  const i = links[internal];
  const p = links[plugins];
  const results = [...new Set([h, i, p].filter(origin => origin))].map(
    origin => `<link rel="preconnect" href="${origin}" crossorigin>`
  );
  return results.join('\n');
});

修改完毕后执行一下 npx patch-package hexo-theme-next,会在目录下生成一个 patches 文件,记录的是你上述的变更。

最后在 package.json 文件内添加一个命令,这样在每次安装完依赖的时候就会自动帮你把变更应用到指定文件了,Hexo 自带部署命令也都能正常生效。

{
  "scripts": {
    "build": "hexo generate",
    "clean": "hexo clean",
    "deploy": "hexo deploy",
    "server": "hexo server",
    "postinstall": "patch-package"
  }
}

暂时用的是知乎的,它用的阿里云的全球 CDN,效果良好,因为官方没有明确说是可以对外提供的,所以存在一定风险,不排除滥用后直接收敛权限。

unpkg-zhimg-chengxiaobai-com-network

同时我也看了下 UNPKG 的服务提供能力,比 JSDelivr 简单但是也有滥用可能性,如果再出意外,那就只能自建了。

回顾 JSDelivr 事件,背后原因不作深究,毕竟它作为公益性质的服务,是没有精力对上面的内容做审查的,并且它支持直接访问 GitHub 仓库,本身是一项超级赞的功能。免费、方便、速度快,这些既是互联网工程师们所最求的目标,同时某种程度上也是它的原罪,以至于它被各路「大神」玩得花样百出,最后就是我们现在看到的样子。

Unfortunately today jsDelivr unexpectedly lost its ICP license in China. As effect the regional CDN disabled our account.
This resulted in the extended outage we had in mainland China and Taiwan.
Other regions were unaffected.
We understand how difficult it was for our users to experience this unique situation.
From now on all Chinese traffic will be served by “near China” locations provided by global CDN providers.
This will have the additional benefit of better failover logic in the future.

静观后续发展。

当你在凝视深渊的时候,深渊也正在凝视着你。