博客构建链路改造记录
博客构建链路改造记录
2026年8月,我把这个博客的构建与部署链路做了一次大改造:从”本地构建 + 提交产物”变成”仓库只存原料 + GitHub Actions 自动构建发布”。这篇文章记录改造动机、关键变更、以及过程中踩到的坑,方便日后回溯。
背景:改造前的链路
改造前的流程是:
- 在
blog_builder/(一个独立于仓库的 Hexo 工程目录)里跑hexo generate - 用脚本把产物 rsync 到仓库的
docs/ - 手动提交
docs/并 push
这套流程的问题:
docs/里全是构建产物(HTML/CSS/JS),每次重建都产生大量无意义的 diff 和历史- 图片(约 17MB)作为普通文件躺在 git 里,仓库体积被撑大
- Hexo 工程本身没有进仓库,换机器需要重新手工搭建环境
目标架构
1 | 仓库(只存原料) |
关键点:Pages 直接以 LFS 指针文件形式暴露会挂(Pages 官方不支持 Git LFS,分支里的 LFS 文件只会以指针文本形式服务出去),所以图片必须经 CI 的 checkout(lfs: true) 拉成真实文件、构建进产物、再以 artifact 形式发布。
主要变更(对应 git 记录)
1. 重构:builder 工程入仓,docs 退役(commit 73e5752)
- 把
blog_builder/的完整 Hexo 工程(_config.yml、_config.nexmoe.yml、package.json、scaffolds/、bin/manager、check_tags.js)合入仓库builder/ - 图片从
docs/img/迁移到builder/source/img/(真实文件),favicon 同理 docs/整体删除并加入.gitignore——从此它是纯构建产物,不再进版本库- 新增
.gitattributes:栅格图片(png/jpg/jpeg/gif/webp/bmp/tiff/ico)走 Git LFS
2. 历史重写(filter-repo + lfs migrate)
git filter-repo --path docs --invert-paths:把全部历史中的docs/产物抹掉(md 文章历史原样保留)git lfs migrate import:把图片 blob 转成 LFS 对象- 效果:git pack 从 ~25MB 缩到 ~7MB,图片作为 59 个 LFS 对象单独存储
3. 修复:CI 里的 mtime 脚本(commit b9dae31)
见下方”踩坑记录”第 2 条。
4. 安全:依赖升级消除全部漏洞(commit 5e53e3b)
hexo-filter-mathjax0.9.1 → 0.11.1hexo-generator-feed3.0.0 → 4.0.0- 用
overrides消除嵌套的 hexo 6.3.0(<7.2.0有高危漏洞) npm audit归零
5. 修复:mathjax ams 扩展缺失(commit 9d5a956)
见下方”踩坑记录”第 4 条。
踩坑记录
1. 仓库里现成的软链是坏的
改造前仓库的 builder/source/ 里就有 _posts -> ../raw/ 软链,但解析结果是 builder/raw/(不存在)——正确写法是 ../../raw/。builder/source/img、favicon.png 同理。教训:相对软链的层级要在迁移时实测 realpath 验证。
2. CI 里 git ls-files 的引号陷阱
为了在 CI 里恢复文件的 mtime(_config.yml 的 updated_option: mtime 依赖它),我写了:
1 | git ls-files 'raw/*.md' | while read -r f; do touch -d "$(git log -1 --format=%cI -- "$f")" "$f"; done |
结果第一个 CI 构建直接失败。原因:git ls-files 对含空格/中文的文件名会输出带引号 + 八进制转义的形式(如 "raw/Ubuntu\347\232\204advantage tools\345\244\261\350\264\245.md"),read -r 拿到的是字面量引号串,touch 找不到文件,循环退出码非零 → 整个 job 失败。
修复:改用 NUL 分隔:
1 | git ls-files -z 'raw/*.md' | while IFS= read -r -d '' f; do touch -d "$(git log -1 --format=%cI -- "$f")" "$f"; done |
教训:git 输出文件列表给 shell 处理时,一律用 -z + read -d ''。
3. 主题的”过时依赖检查”会硬性 kill 构建
升级依赖时,npm audit 报告嵌套的 hexo@6.3.0(<7.2.0 有高危漏洞)——它来自 hexo-theme-nexmoe@4.2.2 声明 hexo: ^6.0.0。我用 npm overrides 把嵌套 hexo 强制统一成 8.1.2,结果构建立刻失败:
1 | Package hexo's version (8.1.2) does not satisfy the required version (^6.0.0). |
原因是主题自带 include/dependency.js,会对 hexo 版本做 semver.satisfies 校验,不满足就 process.exit(-1)。这不是真正的不兼容——主题代码本身在 hexo 8 下运行完全正常,只是声明过时 + 检查粗暴。
解决方案:用 patch-package 固化一个补丁,把主题 package.json 里的 hexo: ^6.0.0 改成 ^8.1.1,配合 postinstall: patch-package 在 npm ci 后自动应用。这样既消除了漏洞,又绕过了过时检查。
教训:npm overrides 能改依赖树,但改不掉包自身的运行时断言;npm 生态遇到这种可以 patch 源码文件。
4. mathjax 0.11 的 ams 扩展缺失
升级 hexo-filter-mathjax 到 0.11.1 后,含 align/bmatrix/pmatrix 的页面出现大量 Unknown environment 'align'。
原因:0.11 版的默认包加载行为变了——defaultPackages() 只加载 ['require', 'autoload'],而 ams 扩展(align/aligned/bmatrix/pmatrix 等环境全在 @mathjax/src/mjs/input/tex/ams/AmsMappings.js)只在 tags: 'ams' 时加载。当前配置是 tags: none,所以 ams 没被加载;autoload 的动态加载在此环境下又因 Package.resolvePath 解析失败而不起作用。
修复:在 _config.yml 的 mathjax 段显式加载 ams:
1 | mathjax: |
验证:三个受影响页面的 Unknown environment 从 86+ 处归零,tags: none 保持则没有公式编号副作用。
5. filter-repo 会移除 origin remote
git filter-repo 出于安全考虑会自动删除 origin remote,重写历史后需要重新 git remote add origin ...。教训:filter-repo 后别急着 push,先确认 remote 还在。
新工作流
现在的日常操作极简:
1 | # 写文章 |
push 后 GitHub Actions 自动完成:拉 LFS 图片 → npm ci(含 patch 应用)→ hexo generate → 发布到 Pages。docs/ 不再需要人管。
数学公式与 mathjax 的现状
关于公式渲染,早前在2023年4月折腾Hexo和Node里记录过 mathjax + pandoc 的组合(hexo-renderer-pandoc + hexo-filter-mathjax,pandoc 传 --mathjax 参数),这套组合目前仍然离不开:
pandoc负责 markdown → HTML 的转换(含 LaTeX 语法透传)hexo-filter-mathjax在生成期用 MathJax 做服务端 SVG 渲染(全后端,无需前端 JS)
本次把 hexo-filter-mathjax 升到 0.11.1 后,公式渲染更完整(比如一步状态转移概率矩阵里 bmatrix 环境恢复渲染),但副作用是 @mathjax/src 及其字体包让本地 node_modules 膨胀了约 99MB。目前选择接受(本地磁盘不敏感),后续若在意体积再研究字体裁剪。
结语
这次改造把”构建”从本地手工劳动变成了 CI 的自动化环节,仓库历史从此只记录有意义的内容变更。过程中踩的坑(软链、引号、主题断言、ams 扩展)都写在了上面,希望下次折腾时能少走弯路。
Knighthana & DeepSeek
2026/08/08