前言

上一篇文章写了 Hexo 7 → 8 的升级, 这次接着记录 Butterfly 主题的升级。

本地是 5.2.2, 当前最新是 5.7.0。中间跨了 5.3、5.4、5.5、5.6、5.7 五个版本, 官方更新日志里有大量性能优化和 bug 修复, 值得升级。

但升级过程中踩到了几个坑, 其中一个是跨大版本的破坏性变更, 老插件直接罢工。这篇把过程和解决办法记下来。

先看升级收益

翻了一遍 5.6 的更新日志, 对博客体验提升比较明显的有这些:

  • TOC 目录滚动从 throttle 轮询换成 IntersectionObserver, 大幅减少滚动事件的计算量
  • 滚动处理器换成 btf.rafThrottle, 跟随浏览器渲染帧率
  • 平滑滚动从线性插值改成 easeOutQuart 缓动, 观感更自然
  • 新增 scriptCache / cssCache 机制, 防止脚本和样式重复加载
  • Mermaid 手势系统用 AbortController 重写, 修复了 Chrome 下滚动图表乱跳
  • 修复 flink.js 和 button.js 缺少 HTML 转义导致的 XSS 漏洞

性能优化是实打实的, 文章多了之后尤其明显。

障碍一: ReferenceError: getScript is not defined

这是最典型的报错, 说说组件加载不出来(比如”说说”模块)。

报错现象

1
ReferenceError: getScript is not defined

页面上对应组件的位置空白, 控制台一片红。

根因: 全局函数搬进了命名空间

Butterfly 3.x 时代, getScript 是直接挂在 window 上的:

1
window.getScript = ...

所以任何地方都能直接调。

从 5.x 开始, 这类函数被收进了 window.btf 命名空间:

1
window.btf.getScript = ...

而我用的那个第三方插件(信封/说说相关), 它注入的内联脚本里调的还是裸函数:

1
getScript('/js/xxx.js', callback)

这个裸调用现在自然就找不到了。

解决办法: 加一段惰性垫片

不想改插件源码的话, 可以在 inject.head 里加一段垫片代码。关键在于”惰性” —— 调用的时候才去取 btf, 这样就不依赖脚本的加载顺序:

1
2
3
window.getScript = window.getScript || function(u, a) {
return window.btf.getScript(u, a)
}

六行代码, 一个插件都不用改。

具体做法是在 _config.butterfly.yml 的 inject.head 下面加:

1
2
3
4
5
6
inject:
head:
- |
window.getScript = window.getScript || function(u, a) {
return window.btf.getScript(u, a)
}

这个思路挺通用的 —— 遇到”全局函数搬家”导致的报错, 都可以用同样的方式兜一层。

障碍二: 构建直接崩, 报 cover undefined

报错现象

1
TypeError: Cannot read properties of undefined (reading 'cover')

hexo g 直接中止, 构建不出来。

根因: 插件配置节点缺失

hexo-butterfly-envelope 从 1.0.13+ 开始, 把四张图片收进了 custom_pic 这个子节点里, 而代码内部不做任何存在性检查就往下读。

我的旧配置里没有这一节, 于是读 undefined.custom_pic, 构建直接崩。

解决办法: 补齐配置节点

按插件要求补上配置就行:

1
2
3
4
5
6
7
envelope:
enable: true
custom_pic:
pic_1: xxx.png
pic_2: xxx.png
pic_3: xxx.png
pic_4: xxx.png

补的时候顺手发现, 它的默认值还指向一个已经废弃的镜像地址, 一并改成了自己的图床。

教训: 老插件升级后如果直接崩, 先去看它的 changelog, 大概率是配置格式变了。

障碍三: unknown block tag: timenode

报错现象

1
ERROR Failed to render tag: unknown block tag: timenode

文章渲染失败, 但不是全部文章, 只有用了特定标签的那几篇。

根因: 同名标签, 少注册了一个

Butterfly 5 自己带了一个 timeline 标签, 但只注册了这一个。

而我用的第三方版本除了 timeline, 还注册了 timenode。我的文章里用了 30 次 timenode, 于是就报错了。

解决办法: 保留第三方版本

让第三方的标签插件覆盖官方的, 这样两个标签都能用。

这个问题的本质是同名标签的注册冲突 —— 如果你升级后遇到”某个标签突然不认识了”, 先想想是不是两个插件抢同一个标签名。

升级前: 先验证挂载点

这次升级前我做了件事, 差点白做。

我原本的判断是: 跨 5 个版本, 那些插件依赖的模板挂载点肯定早被删了, 升级必然失败。

于是我把 5.7.0 的包解开, 直接 grep 那几个挂载点:

1
2
# 解包后 grep 挂载点
grep -r "recent-posts\|sticky_layout\|aside-content\|card-widget" layout/

结果如下:

挂载点 3.7.1 5.7.0
recent-posts 5 个布局文件 6 个
sticky_layout 1 1
aside-content 1 1
card-widget 11 13

四个挂载点在新版里一个不少, 而且 card-widget 还多了 2 个。

所以我那条”最重的理由”—— 证据不支持。改口之后, 升级才真正开始。

这一步花的时间不长, 但避免了”因为一个不成立的判断而放弃升级”。预感值得说出来, 但只有证据能定案。

完整升级流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 备份 + Git 基线
git add -A && git commit -m "baseline: pre-butterfly-5.7"

# 2. 记录当前版本, 方便对比
cat themes/butterfly/package.json | grep version

# 3. 升级主题
npm install hexo-theme-butterfly@5.7.0 --save

# 4. 补齐插件配置节点(envelope 的 custom_pic)
# 5. 加 getScript 垫片到 inject.head
# 6. 验证
hexo clean && hexo g
hexo s

建议的版本锁定

升级完记得把版本锁进 package.json, 避免以后重装得到不同环境:

1
2
3
4
5
6
7
{
"dependencies": {
"hexo": "8.1.0",
"hexo-theme-butterfly": "5.7.0",
"hexo-butterfly-envelope": "1.0.13"
}
}

小结

这次跨越 5 个小版本, 全过程只撞到 3 个真实障碍, 比预期少很多:

  1. getScript 搬家 —— 用惰性垫片兜住, 不改插件源码
  2. 配置节点缺失 —— 补配置即可
  3. 同名标签冲突 —— 让第三方版本覆盖官方

三个问题的共同点是: **都不是主题本身的 bug, 而是”老插件跟不上新主题”**。

所以升级前做的那件事 —— grep 挂载点验证 —— 比事后一个一个试错要高效得多。

参考链接: