Perl School Publishing 幕后

发布: (2025年12月15日 GMT+8 01:46)
9 min read
原文: Dev.to

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 拒绝加载。

一个快速(但不可扩展)的修复

在时间紧迫的情况下,最快的诊断验证步骤是:

  1. 解压生成的 EPUB。
  2. 打开出错的 XHTML 文件。
  3. 手动把 <br> 改为 <br/>(改几处)。
  4. 重新压缩 EPUB。
  5. 再次运行 epubcheck
  6. 在 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 仍然是最终的合规判官。

现在的流水线如下:

  1. Lint 手稿 HTML —— 在转换前捕获破损的原始 HTML/XHTML。
  2. 通过 make_book 构建 PDF + EPUB
  3. 对 EPUB 运行 epubcheck —— 确保最终文件符合标准。
  4. 只有在这一步通过后才上传至 Leanpub 和 Amazon。

以后任何改动(新 CSS、模板、元数据等)仍会走同样的检查环节,流水线会在读者看到问题之前先提醒我。

Docker 与 GitHub Actions:实现可复现

拥有一套好用的 Perl 脚本和本地安装的工具对个人项目来说已经足够,但如果出现以下情况就不太理想:

  • 其他作者可能想自行生成草稿,或
  • 我希望构建过程能够在 CI 中自动完成。

于是下一步是把所有东西封装进 Docker 镜像,并接入 GitHub Actions。

Docker 镜像内容

  • Perl + cpanm + 仓库 cpanfile 中列出的所有 CPAN 模块
  • pandoc
  • wkhtmltopdf
  • Java + epubcheck
  • Perl School 的实用脚本(make_bookcheck_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 会被发布。

Back to Blog

相关文章

阅读更多 »