为 Chirpy 添加 Twikoo 评论:一次插件开发记录
Chirpy 是个出色的主题,但它原生不支持 Twikoo 评论。我想在现有的 Giscus 系统旁添加 Twikoo,又不想修改主题源码。解决方案经过多次迭代:一开始使用 Jekyll 插件,后来尝试 footer.html 模板注入,最终采用本地构建后推送的工作流程,以确保所有部署平台的行为完全一致。
为什么选择 Twikoo?
Giscus 很好——免费、无广告、评论存储在 GitHub Discussions 中。但有个问题:读者需要 GitHub 账号才能评论。对于一些朋友,尤其是网络受限地区的用户,或者单纯没有 GitHub 账号的人,这造成了不必要的障碍。比如一位来自中国的读者,可能能稳定访问你的博客,却难以加载 GitHub 的认证页面。另一位读者可能只是想留个言,不想为此再注册一个在线账号。
Twikoo 提供了更简单的替代方案。它支持匿名评论,无需第三方账号,提供轻量级的评论体验。代价是你需要自己托管一个 Twikoo 后端(可以使用腾讯云 CloudBase、Vercel 或自建方案),但对于很多博主来说,为了给读者提供灵活性,这个代价是值得的。通过同时提供 Giscus 和 Twikoo,读者可以根据自己的情况选择最合适的方式。
问题所在
Chirpy 使用 Giscus 作为评论系统,通过 _config.yml 配置。主题的模板被打包在 Ruby gem 里,这意味着你不能直接在项目目录中编辑它们。你可以 fork 主题然后修改,但这会带来维护负担——每次主题更新,你都需要把更新合并到你的 fork 中。
我想要一个能在主题更新后继续工作的方案,不需要维护单独的 fork。目标是在不触碰主题核心文件的情况下,将 Twikoo 注入到文章页面中。
解决方案:多次迭代的历程
我的实现经过几个阶段的迭代才找到正确的方法。每种方法都有其局限性,促使我转向下一个方法。
第一次尝试:后渲染钩子
我最初创建了一个 Jekyll 插件,使用后渲染钩子在构建完成后将 Twikoo 的 HTML 和 JavaScript 注入到每篇文章页面中。这个钩子在 Jekyll 完成每个文档渲染后运行。它检查输出是否为 HTML,验证文档是否为文章,然后将 Twikoo 容器注入到 </body> 标签之前。
这种方法在本地和某些托管平台上有效,但我发现 GitHub Actions 的构建环境处理 Jekyll 插件的方式不同。插件在 GitHub Pages 构建上根本不会执行,尽管它在 Cloudflare Pages 和本地运行良好。经过数周的调试——更改钩子类型、添加错误处理、清理缓存、尝试各种配置——我意识到问题根本在于 GitHub Pages 加载自定义插件的方式。
完整源码可在我的 GitHub 仓库中找到,文件位置是
_plugins/twikoo-inject.rb。
第二次尝试:footer.html 模板
插件方法在 GitHub Pages 上失败后,我尝试使用 Liquid 模板将 Twikoo 直接嵌入到 _includes/footer.html 中。这种方法完全绕过了插件系统——由于 footer.html 是站点模板文件的一部分,Jekyll 在正常构建周期中会处理它,无论插件加载行为如何。
这个想法是合理的,但结果不一致。GitHub Actions 的构建环境处理模板 includes 的方式与其他平台不同。footer include 在不同部署场景中没有一致地处理,这意味着评论系统在一些构建上会出现,但在另一些上不会。
第三次尝试:本地构建工作流程
经过两次失败的尝试,我意识到核心问题:不同的构建环境会产生不同的结果。即使使用相同的源代码和相同的 Jekyll 版本,平台配置其构建运行器的细微差异也可能导致输出不一致。
我最终采用的解决方案是本地构建后推送工作流程。不再依赖托管平台构建站点,而是先在本地构建,然后将预生成的 _site 文件夹推送到部署仓库。这确保了无论在哪个平台托管,输出都完全一致——由于站点是预构建的,主机只需要提供静态文件,而不需要运行 Jekyll。
实现细节
插件注入(第一次尝试)
最初的插件方法使用多个检查来确保可靠的注入。它检查输出是否为 HTML,验证文档是否为文章,并通过查找现有标记来防止重复注入。
防止重复注入
使用独特的数据属性作为标记,可以避免文章本身包含提及注入目标的代码示例时出现误判。这是撰写关于自己插件的技术文档时常见的陷阱。
切换 UI 设计
由于 Giscus 无法隐藏(由主题渲染),我创建了一个切换按钮来显示/隐藏 Twikoo 区域。设计采用了水平分隔线配居中文字的方式——这是出版中常见的模式,用于分隔内容的不同部分。
切换按钮有实际用途:它为不想评论的读者保持页面整洁,同时为想评论的读者提供选项。按钮文字从”展开 Twikoo 评论”变为”收起 Twikoo 评论”,清楚地指示当前状态。
懒加载
Twikoo 只在用户点击切换按钮时才初始化,节省资源。没有懒加载的话,每次页面加载都会获取 Twikoo JavaScript 库并初始化评论系统,即使读者从未打算留言。有了懒加载,这个开销只在实际需要时才会发生。
样式考量
实现使用 CSS 变量(--border-color、--text-muted、--link-color)来匹配 Chirpy 的主题。这确保切换按钮在浅色和深色模式下都保持一致的外观,不需要为每种模式编写单独的样式。如果 Chirpy 在未来更新其配色方案,Twikoo UI 会自动适应。
宽度限制为 max-width: 800px,与内容区域对齐。这种对齐让评论部分在视觉上与上方的文章相连。边距设置为 0.5rem auto 2rem auto,顶部留出呼吸空间,底部提供更充分的分隔,因为那里是 footer 的开始位置。
配置
由于 Chirpy 主题原生不支持 Twikoo,官方只支持 Giscus、Disqus 和 Utterances。因此,如果你只想使用 Twikoo,不要填写 provider 字段,否则主题会尝试渲染不支持的评论系统导致报错。Twikoo 的注入是通过 footer.html 和插件完成的,不需要依赖 Chirpy 的评论配置。
只需要确保在 _config.yml 中添加 plugins_dir:
1
plugins_dir: _plugins
这确保 Jekyll 从 _plugins 目录加载插件。没有这一行,Jekyll 可能找不到你的自定义插件,具体取决于它的调用方式。
如果你想同时使用 Giscus 和 Twikoo(像我的博客这样),可以将 provider 保留为 giscus,这样主题会渲染 Giscus,而 Twikoo 通过我们的自定义注入显示。
设置本地构建工作流程
我的做法是这样的:源代码仓库和 _site 目录在同一个仓库中,源码仓库包含完整的博客源码和构建后的 _site 文件夹。本地构建完成后,通过 GitHub Actions 将 _site 目录推送到 GitHub Pages 进行托管。对于 Cloudflare Pages 和 Netlify,则将构建产物推送到它们的 Pages 仓库,并设置它们不进行构建,只负责托管静态文件。
步骤 1:启用 GitHub Pages
在 GitHub 仓库设置中启用 GitHub Pages,选择 “GitHub Actions” 作为构建源。这样 GitHub 会自动运行我们配置的 Actions 工作流。
步骤 2:配置 Cloudflare Pages
在 Cloudflare 控制台中创建 Pages 项目,连接到你的 GitHub 仓库。然后在设置中做两件事:第一,关闭”构建”选项,只保留”部署”功能;第二,将部署目录设置为 _site。这样 Cloudflare 只会读取我们推送的 _site 目录中的静态文件,而不会推送整个仓库。
步骤 3:配置 Netlify
同样在 Netlify 中创建站点,连接到 GitHub 仓库。在站点设置中做两件事:第一,关闭”构建”命令,不执行任何构建脚本;第二,将发布目录设置为 _site。这样 Netlify 只会托管 _site 中的静态文件。同时还有个额外好处:Netlify 免费版每月有 300 分钟的构建时间限制,跳过构建步骤可以节省这些额度,留给其他项目使用。
步骤 4:本地构建和推送
当你想部署时,在本地运行以下命令:
1
2
3
4
5
6
7
8
9
10
# 构建站点
bundle exec jekyll build
# 切换到 deploy 仓库的文件夹
cd _site
# 如果需要则初始化 git,然后提交和推送
git add .
git commit -m "Deploy site"
git push deploy main
步骤 5:用脚本自动化
为了使部署更方便,创建一个部署脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
set -e
echo "Building Jekyll site..."
bundle exec jekyll build
cd _site
echo "Committing to deploy repository..."
git add .
git commit -m "Deploy $(date)"
echo "Pushing to deploy repository..."
git push deploy main
echo "Done!"
用 chmod +x deploy.sh 使其可执行,然后每当你想部署时运行 ./deploy.sh。
平台兼容性
使用本地构建工作流程,平台兼容性不再是问题。由于你在本地构建并只推送输出,托管平台只需提供静态文件。这适用于任何可以提供 HTML 文件的平台:
| 托管平台 | 本地构建可用? | 备注 |
|---|---|---|
| GitHub Pages | 是 | 推送到 yourusername.github.io |
| Cloudflare Pages | 是 | 连接部署仓库 |
| Netlify | 是 | 连接部署仓库 |
| Vercel | 是 | 连接部署仓库 |
| 任何静态主机 | 是 | 上传 _site 内容 |
关键优势是你不需要在托管平台上配置构建命令或 Ruby 版本。你不是在要求平台构建任何东西——你给它的是准备好的文件。
为什么这种方法更好
在本地构建然后推送输出解决了困扰基于平台的构建的几个问题。
首先,一致性得到保证。当你在本地构建时,每次都使用相同的 Ruby 版本、相同的 Jekyll 版本和相同的 gem 版本。输出是确定性的。你推送到部署仓库的文件正是你在本地运行 jekyll build 时看到的文件。
其次,调试变得更容易。如果生产环境中出现问题,你可以将本地构建输出与已部署的内容进行比较。由于两者来自相同的构建过程,任何差异必定是由于托管平台的配置,而不是构建本身。
第三,部署速度更快。基于平台的构建可能需要几分钟,特别是如果 Jekyll 需要重建整个站点时。有了本地构建,你已经准备好了输出。推送到 git 仓库只需几秒钟,大多数托管平台在几秒或几分钟内检测到新提交。
第四,你获得了对构建环境的完全控制。平台的构建环境可能会在未经通知的情况下发生变化——平台可能会升级 Ruby 或 Jekyll,或更改其配置构建运行器的方式。当你在本地构建时,你决定何时升级,并且可以在部署前进行充分测试。
最终效果
现在每篇文章都有两个评论选项,而且无论你托管在哪里,它们的工作方式都完全一致。Giscus 默认显示,集成在主题的设计中。Twikoo 默认隐藏,通过每篇文章底部的切换按钮访问。读者可以选择自己偏好的评论系统,或者如果只是浏览的话,忽略两者。
实现干净且非侵入式。它不需要 fork 主题,不会使部署过程复杂化,尊重 Chirpy 的设计语言。当主题更新时,Twikoo 集成继续工作,不需要任何更改。
本地构建工作流程为部署增加了一个额外的步骤——你在本地构建而不是让平台为你构建——但这个权衡是值得的。你获得了一致的结果、更快的部署,以及对构建过程的完全控制。
获取源代码
如果你有兴趣在自己的 Chirpy 博客上实现这个功能,完整的源代码可以在我的 GitHub 仓库中找到。你需要:
_includes/footer.html- 通过模板的 Twikoo 集成_plugins/twikoo-inject.rb- 插件注入(在兼容的平台上工作)_config.yml- Twikoo 配置
更新日志
| 日期 | 更新内容 |
|---|---|
| 2026-04-25 | 初始版本,包含切换 UI、懒加载、CSS 变量支持 |
| 2026-04-25 | 将钩子从 :site, :post_render 改为 :documents, :post_render 以提高兼容性 |
| 2026-05-02 | 添加显式文章集合检查,确保可靠定位 |
| 2026-05-02 | 添加 plugins_dir 配置要求 |
| 2026-05-02 | 添加构建环境对比及 Cloudflare Pages 推荐说明 |
| 2026-05-02 | 更新 GitHub Actions 工作流,添加详细构建输出用于调试 |
| 2026-05-02 | 修复自我排除 Bug,使用独特的 data 属性作为标记 |
| 2026-05-02 | 删除冗余代码示例以防止自我排除 |
| 2026-05-07 | 尝试使用 footer.html 模板注入作为插件的替代方案 |
| 2026-05-07 | 发现 footer.html 在 GitHub Actions 上也不一致 |
| 2026-05-08 | 实现本地构建后推送工作流程以确保一致性 |