惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
IT之家
IT之家
Hugging Face - Blog
Hugging Face - Blog
J
Java Code Geeks
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 叶小钗
MyScale Blog
MyScale Blog
G
Google Developers Blog
Microsoft Azure Blog
Microsoft Azure Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
Google DeepMind News
Google DeepMind News
Engineering at Meta
Engineering at Meta
The Cloudflare Blog
Martin Fowler
Martin Fowler
酷 壳 – CoolShell
酷 壳 – CoolShell
N
Netflix TechBlog - Medium
MongoDB | Blog
MongoDB | Blog
I
InfoQ
WordPress大学
WordPress大学
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
H
Help Net Security

新锐博客

vscode连接cnb并推送仓库 - 新锐博客 twikoo不用token查询文章浏览量 - 新锐博客 基于Astro构建的支持对话与生图的Web应用 - 新锐博客 deepseek-v4-flash-0731使用体验 - 新锐博客 基元律动免费送68元额度 - 新锐博客 Linux更换软件源 - 新锐博客 华为鸿蒙7.0花粉beta版发布 - 新锐博客 使用测速脚本测一下服务器带宽 - 新锐博客 trae告别白嫖时代,auto也需排队了 - 新锐博客 流量不够用?大流量卡免费领 - 新锐博客 推荐一个免费在线图片处理网站 - 新锐博客 AI清理C盘的优缺点 - 新锐博客 twikoo不算bug的bug - 新锐博客 最近网站的一些变动 - 新锐博客 腾讯EO开始发多余套餐啦!速领 - 新锐博客 hi168停止对个人用户的运营 - 新锐博客 哪吒监控曝高危漏洞,请尽快升级 - 新锐博客 IndexDB实现大文件下载的思路 - 新锐博客 鱼鱼api使用教程 - 新锐博客 服务器安装Dokploy替代vercel - 新锐博客 对于近几年的电视剧的无力吐槽 - 新锐博客 关于网站被攻击的后续 - 新锐博客 使用sessionStorage给音乐播放进度存储 - 新锐博客 修复了一些bug,网站基本上趋于稳定了 - 新锐博客 AI模型掺水?如何辨别真假? - 新锐博客 感谢一九云给我防住了攻击 - 新锐博客 对一些新手使用AI编程的建议 - 新锐博客 Edgeone标记过期和直接删除有何区别? - 新锐博客 用Navigation Timing API给博客添加上加载时间 - 新锐博客 使用 Astro 自建博客,从零开始鏖战 14 小时 - 新锐博客
solitude主题外部调用本地化 - 新锐博客
莫忘 · 2025-08-22 · via 新锐博客

前言

solitude主题的配置项可以自定义外部调用的地址,但是一直用别人的加速,就怕有一天出现问题导致还需要重新替换

所以打算弄成本地话,本教程基于在 Solitude 上优雅的使用 CDN 加速静态资源文件修改

教程

本教程适用的版本为v3.0.20,若有不同,请自行修改脚本代码

首先就是在根目录创建一个名为plugins.js的文件,然后将如下代码放进去,其中的isNPMfoderName根据实际情况修改

#!/usr/bin/env node
/**
 * plugins.js
 * 用于下载 Hexo 主题 Solitude 中第三方的插件文件。
 * 该脚本会读取主题目录下的 plugins.yml 文件,解析其中的插件信息,
 * 并从 CDN 上下载相应的文件到指定的本地目录。
 * node plugins.js
 */

// 变量
const isNPM = false; // 是否是 NPM 安装
const foderName = 'plugins'; // 存储文件夹名称

// 模块
const fs = require('fs');
const fsPromises = require('fs/promises');
const path = require('path');
const https = require('https');

const THEME_DIR = isNPM ? path.join(__dirname, 'node_modules', 'hexo-theme-solitude') : path.join(__dirname, 'themes', 'solitude');
const PLUGIN_FILE = path.join(THEME_DIR, 'plugins.yml');
const OUTPUT_DIR = path.join(__dirname, foderName);

function parsePluginsYml(text) {
  const res = {};
  let cur = null;
  text.split(/\r?\n/).forEach(l => {
    const ln = l.split('#')[0].trimEnd();
    if (!ln) return;
    const sec = ln.match(/^([a-zA-Z0-9_\-]+):\s*$/);
    if (sec) {
      cur = sec[1];
      res[cur] = {};
      return;
    }
    const kv = ln.match(/^  ([a-zA-Z0-9_\-]+):\s*(.*)$/);
    if (kv && cur) {
      res[cur][kv[1]] = kv[2].trim();
    }
  });
  return res;
}

async function mkdirp(d) {
  try {
    await fsPromises.mkdir(d, { recursive: true });
  } catch (e) {
    if (e.code !== 'EEXIST') throw e;
  }
}

function fetch(url, dest) {
  return new Promise((resolve, reject) => {
    const request = https.get(url, (response) => {
      // 处理重定向
      if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
        response.destroy();
        return fetch(response.headers.location, dest).then(resolve, reject);
      }

      if (response.statusCode !== 200) {
        response.destroy();
        return reject(new Error(`${response.statusCode} ${url}`));
      }

      const fileStream = fs.createWriteStream(dest);
      response.pipe(fileStream);

      fileStream.on('finish', () => {
        fileStream.close(resolve);
      });

      fileStream.on('error', (err) => {
        fs.unlink(dest, () => reject(err));
      });
    }).on('error', (err) => {
      reject(err);
    });
  });
}

function minFile(f) {
  return f.replace(/(?<!\.min)\.(js|css)$/i, '.min.$1');
}

function stripDistSegments(f) {
  // 移除任意位置的 dist/ 目录段(包含开头或中间)
  return f.replace(/(^|\/)dist\//g, '$1');
}

(async () => {
  try {
    const pluginsText = await fsPromises.readFile(PLUGIN_FILE, 'utf8');
    const plugins = parsePluginsYml(pluginsText);
    await mkdirp(OUTPUT_DIR);

    for (const [key, def] of Object.entries(plugins)) {
      if (!def.name || !def.file || !def.version) {
        console.warn(`[SKIP] ${key} 缺少 name/file/version`);
        continue;
      }

      const { name, file, version, other_name } = def;
      
      const dirName = `${other_name || name}@${version}`;

      const cdnFilePath = minFile(stripDistSegments(file));
      const url = `https://cdnjs.cloudflare.com/ajax/libs/${other_name || name}/${version}/${cdnFilePath}`;
      
      const dest = path.join(OUTPUT_DIR, dirName, stripDistSegments(file));
      
      try {
        await fsPromises.access(dest);
        console.log(`[SKIP] ${key}`);
        continue;
      } catch (e) {
        if (e.code !== 'ENOENT') {
          console.error(`[ERROR] ${key} 访问文件失败: ${e.message}`);
          continue;
        }
      }

      console.log(`[DOWN] ${key} → ${dest}`);
      try {
        await mkdirp(path.dirname(dest));
        await fetch(url, dest);
        console.log(`[OK]   ${key}`);
      } catch (e) {
        console.error(`[FAIL] ${key}: ${e.message}`);
      }
    }
    console.log('全部完成!');
  } catch (error) {
    console.error(`脚本执行失败: ${error.message}`);
  }
})();

然后就是运行此脚本,在终端输入node plugins.js即可下载文件到plugins文件夹中

最后就是修改调用方式了

找到_config.solitude.yml中的CDN类,将third_party改成custom

然后将custom_format改成/plugins/${cdnjs_name}@${version}/${min_cdnjs_file}即可。

有些朋友就会发现控制台会报缺少字体文件,这时候我们需要手动下载这两个字体文件并且放到plugins\font-awesome@6.7.2\webfonts文件夹中

字体文件:fa-solid-900.woff2fa-brands-400.woff2

最后一键三连即可。

莫忘

君子喻以义,小人喻以利

本文是原创文章,采用 CC BY-NC-SA 4.0 协议,完整转载请注明来自 新锐博客