Perl School Publishing 幕后
Source: Dev.to
我们刚刚出版了一本新的 Perl School 书籍:Design Patterns in Modern Perl(作者 Mohammad Sajid Anwar)。
自从上一次发布新书已经有一段时间了,而在此期间电子书的生态已经发生了变化——Amazon 不再使用 .mobi,工具链也更新了,我那套“只要眯眼看就能工作”的构建流程已经开始出现裂痕。
更糟的是我们还有一个硬性期限:希望在伦敦 Perl 工作坊之前把书准备好。随着日期临近,最后时刻的修补和手动调整变得越来越让人恐慌。我们真的需要一种可靠、可复现的方式,把手稿一次性转换成“高质量 PDF + EPUB”。
于是过去几周,我从头开始重建了 Perl School 的书籍构建流水线。本文将讲述这一过程、我最终选用的工具,以及你如何把它搬到自己的书籍项目中。
旧的体系,以及它为何不够好
最初的 Perl School 流水线诞生于一个截然不同的时代:
- Amazon 需要
.mobi文件。 - EPUB 支持零散。
- 我乐于用 shell 脚本把各种东西粘在一起,然后祈祷它能跑通。
它曾经能用……直到它不行了。每本书都有略有差异的脚本、略有差异的假设,以及略有差异的临时手动调优。这显然不是可以交给新作者并说“放心使用”的东西。
在为 Design Patterns in Modern Perl 重建时,这一点变得尤为明显。这本书本身是现代且结构良好的;生成它的流水线不应像个遗留系统。
选型:Pandoc 与 wkhtmltopdf(不使用 LaTeX)
新的流水线围绕两大核心工具构建:
- Pandoc —— 文档转换的瑞士军刀。它可以接受 Markdown/Markua 加上元数据,输出 HTML、EPUB 以及更多格式。
wkhtmltopdf—— 使用无头浏览器引擎把 HTML 转换为可打印的 PDF。
为什么不使用 LaTeX? 因为我对它过敏。LaTeX 功能强大,但每次认真使用时,我都会陷入对页面断行的调试,而这并不是我喜欢的语言。HTML + CSS 我还能接受;浏览器我可以理性对待。
转换流程
PDF 路径
Markdown → HTML (via Pandoc) → PDF (via wkhtmltopdf)
EPUB 路径
Markdown → EPUB (via Pandoc) → validated with epubcheck
前置内容(封面页、标题页、版权页等)由 Template Toolkit 根据一个简易的 book-metadata.yml 文件生成,然后与章节内容拼接,形成一本外观统一的书。
这已经让我们走了很长一段路……但随后有读者发现了一个 bug。
iBooks 的 bug 报告
书籍发布后不久,一位购买了 Leanpub EPUB 并在 Apple Books(iBooks)中阅读的读者看到一个巨大的粉红色错误框:
There’s something wrong with the XHTML in this EPUB.
Apple Books 对 XHTML 中的 “X” 非常挑剔:它要求严格的 XML 结构,而不是“差不多合法的 HTML”。在处理 EPUB 时,你必须摒弃对 HTML5 那种宽容的习惯。
发现 epubcheck
epubcheck 是 EPUB 文件的权威校验工具。把它指向一个 .epub,它会解压、解析所有 XML/XHTML,检查元数据和清单,并精准报告错误所在。
在书上运行它立刻得到:
Fatal Error while parsing file: The element type `br` must be terminated by the matching end-tag `</br>`.
在 HTML 中 <br> 是合法的;但在 XHTML(XML)里必须写成 <br/>(自闭合)或 <br></br>。多个章节中都出现了这种情况。Pandoc 把原始 HTML 直接透传进了 EPUB,但这些 HTML 并非严格的 XHTML,导致 Apple Books 拒绝加载。
一个快速(但不可扩展)的修复
在时间紧迫的情况下,最快的诊断验证步骤是:
- 解压生成的 EPUB。
- 打开出错的 XHTML 文件。
- 手动把
<br>改为<br/>(改几处)。 - 重新压缩 EPUB。
- 再次运行
epubcheck。 - 在 Apple Books 中测试。
错误消失,epubcheck 报告通过,读者也确认文件可以正常打开。然而,“在文本编辑器里打开 EPUB 并手动修正 XHTML”显然不是可持续的出版策略。
HTML 与 XHTML 的区别,以及为什么需要 Linter
根本问题其实很直接:
- HTML 极其宽容;浏览器会自行修复破损的标记。
- XHTML 是 XML,毫不宽容。EPUB 3 内容文件必须是 XHTML;随意的 HTML 会导致某些阅读器(如 Apple Books)拒绝加载章节。
我在工具链中加入了一个手稿 HTML Linter,放在交给 Pandoc 或 epubcheck 之前。
大致流程如下:
- 读取手稿(忽略代码块,以免对 Perl 示例中的
<报错)。 - 提取所有原始 HTML 片段。
- 将这些片段包裹在临时根元素中。
- 使用
XML::LibXML检查其是否为合法 XML。 - 报告文件名和行号。
这并不是完整的 HTML 验证器;它只问:“如果这些 HTML 最终出现在 EPUB 中,XML 解析器会卡住吗?” 这样本来就能在书离开我的机器之前捕获 <br> 问题。
加固流水线:把 epubcheck 纳入循环
Linter 能捕获手稿中的显而易见问题;epubcheck 仍然是最终的合规判官。
现在的流水线如下:
- Lint 手稿 HTML —— 在转换前捕获破损的原始 HTML/XHTML。
- 通过
make_book构建 PDF + EPUB。 - 对 EPUB 运行
epubcheck—— 确保最终文件符合标准。 - 只有在这一步通过后才上传至 Leanpub 和 Amazon。
以后任何改动(新 CSS、模板、元数据等)仍会走同样的检查环节,流水线会在读者看到问题之前先提醒我。
Docker 与 GitHub Actions:实现可复现
拥有一套好用的 Perl 脚本和本地安装的工具对个人项目来说已经足够,但如果出现以下情况就不太理想:
- 其他作者可能想自行生成草稿,或
- 我希望构建过程能够在 CI 中自动完成。
于是下一步是把所有东西封装进 Docker 镜像,并接入 GitHub Actions。
Docker 镜像内容
- Perl +
cpanm+ 仓库cpanfile中列出的所有 CPAN 模块 pandocwkhtmltopdf- Java +
epubcheck - Perl School 的实用脚本(
make_book、check_ms_html等)
书籍仓库的典型工作流
# 将书籍的 Git 仓库挂载到 /work
docker run --rm -v "$(pwd)":/work perl-school-builder \
perl check_ms_html # lint 手稿
docker run --rm -v "$(pwd)":/work perl-school-builder \
perl make_book # 构建 built/*.pdf 和 built/*.epub
docker run --rm -v "$(pwd)":/work perl-school-builder \
java -jar /usr/local/epubcheck/epubcheck.jar built/*.epub
所有内容都容器化并自动化后,任何作者都可以复现完全相同的构建过程,而 CI 流水线则保证只有符合标准的 PDF 与 EPUB 会被发布。