开头先交个底:我这些年整理过不少别人的开源书稿,也自己从零写过几本技术类长文档,踩过的弯路基本都集中在同一件事上——正文写得再顺,只要一涉及章节编号、图表引用、公式对齐、多格式导出,纯手工维护的文档就会开始崩。bookdown 这个框架,是我目前找到的、能把「写」和「排」彻底分开的方案里,性价比最高的一个。它本质上是把 R Markdown、knitr、pandoc 和 LaTeX 这几层粘在一起的一层薄胶水:你只管用 Markdown 写正文,编号、交叉引用、目录、参考文献、HTML/PDF/EPUB 三套输出,全部由它按规则生成。
「别人的电子书,你的电子书,都在 bookdown」这句话我特别认同。别人的书,往往是拿它写出来的,你读到的在线版、下载到的 PDF、甚至某些电子阅读器里的 EPUB,可能出自同一份源文件;而你的书,也可以从一份纯文本的源文件开始,一路长成三种形态。这篇文章我想把话说明白三件事:bookdown 到底解决了长文档写作里的哪些真实痛点,一份能长期维护的工程该怎么搭,以及那些文档里不写、只有真跑过一遍才知道的坑。不管你是刚接触 Markdown 的新手,还是已经在用其他静态站点工具写文档的老手,看完应该都能直接抄走一套结构。
1. 为什么长文档写作最后都绕回 bookdown
1.1 电子书的真实痛点:不是「写」,是「维护」
很多人对写书的想象还停留在「打开编辑器,从头敲到尾」。真做过就知道,写作只占三成精力,剩下七成全是维护成本。你今天在第 3 章加了一张图,第 7 章里那句「详见上图」的编号就得跟着改;你调整了章节顺序,全书的目录、页眉、交叉引用、参考文献编号会一起错位;你写完想同时给读者一个在线阅读版和一个可打印的 PDF,两个版本的行距、图表位置、字体还各有各的脾气。
这些活儿单看都很小,堆起来就非常要命。我见过最极端的情况是,一位同行的书稿改到第三版时,Word 里的自动编号彻底错乱,他花了两天时间逐条手工核对图号,最后干脆放弃编号,在正文里写「见后面的那张图」。这种做法在短文档里无所谓,在几十万字的书里就是灾难——读者找不到东西,作者也没法再改结构。
bookdown 的思路正好相反:它假设你会不断改结构,所以把所有「会随着改动而变化的东西」全部交给程序算。章节号、图号、表号、公式号、参考文献序号、目录、索引,你在源文件里只写一个语义化的标签,剩下的事情编译时自动生成。这一层抽象一旦建立,你的写作重心才会真正回到内容上。
1.2 bookdown 到底解决了什么
我把它的价值拆成四个层次,这样你判断自己要不要用它会更清楚。
第一层是内容与呈现分离。源文件里只有纯文本的 Markdown,图表的编号规则、PDF 的版式、网页的导航栏,全部写在配置文件里。你想换一套排版风格,改配置,不用动正文一个字。
第二层是一份源文件、多种输出。同一份稿子,可以编译成带侧边栏和搜索框的 HTML 站点、适合打印和批注的 PDF、以及供电子阅读器使用的 EPUB。这一点对技术书尤其重要——读者里有喜欢在网页上边读边跑代码的,也有必须打印出来做笔记的。
第三层是交叉引用的一体化。图和表不是靠位置猜的,而是靠标签引用。你写\@ref(fig:scatter-basic),编译时它会自动变成「图 3.2」这样的文字,并且点击可以跳转。章节重排、图和表增删,引用永远跟着走。
第四层是可复现。bookdown 本身跑在 R 的生态里,代码块可以直接执行,结果(图、表、数值)内联进正文。这对写数据分析、统计、机器学习类的内容几乎是刚需——书里的每个数字都能追到一段可运行的代码,而不是作者手抄进去的。
这四层加起来,就构成了它相对 Word、相对纯 LaTeX、相对普通静态站点生成器的差异化。Word 的问题是不适合版本管理和结构重排;纯 LaTeX 的问题是门槛高,写正文时被排版命令打断思路;普通静态站点工具的问题是擅长网页,不擅长生成排好版的 PDF 和带编号的书稿。
1.3 什么情况下适合用,什么情况下别用
这里我得说点泼冷水的话,免得你花三天搭完环境发现方向不对。
适合的场景:章节之间有强结构关系、需要统一编号和交叉引用;需要同时产出网页版和 PDF;内容里含可运行的代码和由代码生成的图表;多人协作、希望用文本 diff 而不是二进制文件比对改动;内容要长期迭代,三五年后还要改。
不太适合的场景:篇幅在五千字以内、只有一两个章节的短文,用普通 Markdown 编辑器就够了,搭工程属于过度设计;以大量复杂手工排版为主的画册、杂志式版式,这活儿该交给专业排版工具;完全不想碰命令行、也不想装任何开发环境的人,前期配置的学习成本会劝退。
一个很实用的判断标准:你会不会在半年后回头改它的结构。会,就值得上 bookdown;不会,随便一个 Markdown 编辑器导出 PDF 就完事。
2. 整体设计与技术栈拆解
2.1 四层结构:内容层、编译层、排版层、发布层
我把整个工程在心里分成四层,理解这四层之后,配置文件的每一行你都能对上号。
内容层是index.Rmd加上各个章节的.Rmd文件,纯文本,只关心「写什么」。编译层是 knitr,负责把代码块跑出结果、把图片存到指定目录、把 R Markdown 转成标准 Markdown。排版层是 pandoc 加 LaTeX(输出 PDF 时)或 HTML 模板(输出网页时),负责把 Markdown 变成最终形态。发布层是输出目录_book,里面是可以直接上传到静态托管服务的成品。
为什么要拆这么细?因为出问题时你能快速定位是哪一层挂了。图没出来,多半是编译层;PDF 报字体错误,是排版层;网页样式乱,是发布层的 CSS。很多人一遇到报错就从头到尾瞎改,效率极低。
2.2 为什么选 Markdown 而不是 Word 或纯 LaTeX
Markdown 的核心优势是纯文本。纯文本意味着可以用版本管理工具记录每一次改动,可以看到「第 42 行改了什么」,可以多人同时改不同章节然后合并。Word 的二进制文件做不到这一点,两个人同时改一份 docx,最后只能二选一。
另一层优势是结构显式。Markdown 里#就是一级标题,##就是二级标题,标题的层级是写死在文本里的,不是靠字号加粗模拟出来的。这一点对生成目录和编号至关重要——程序能准确知道你的一级标题在哪,才能正确分配章节号。
那为什么不直接上 LaTeX?因为写正文的时候,LaTeX 的命令会不断打断思路。你写到兴头上,突然要查一个表格环境的写法,节奏就断了。bookdown 的做法是让你在九成的时间里只用 Markdown,只在需要精细控制的地方(比如公式、特殊排版)才落回 LaTeX 语法。这是一个很务实的折中。
2.3 单文件与多文件:为什么一定要拆章
新手最容易犯的错是把整本书塞进一个.Rmd文件。写到两万字的时候你会发现:编辑器开始卡;每次编译要重跑全部代码块,等五分钟;两个人没法同时改;想快速定位某一节得靠搜索。
拆章之后好处立刻显现:单章文件通常几千字,编辑器流畅;配合缓存机制,只重跑改动过的章节;章节顺序由配置文件显式声明,想调整顺序改一行就行;协作时一人一章,冲突概率大幅下降。
拆章的粒度我一般按「一章一个文件」来。如果某一章特别长(比如超过三万字),可以再拆成05-a.Rmd、05-b.Rmd,但要注意这种拆法会让章节编号变成「第 5 章」和「第 6 章」两章,除非你在文件里手动指定章节号。所以更稳妥的做法是控制单章体量,而不是靠拆文件来解决长章节问题。
3. 环境搭建与最小可运行工程
3.1 依赖清单与版本注意事项
需要装的东西不多,但版本配合挺关键。我的建议清单如下。
| 组件 | 作用 | 注意点 |
|---|---|---|
| R 运行环境 | 执行代码块、驱动整个构建流程 | 建议 4.x 以上的较新版本 |
| bookdown 包 | 提供构建函数和输出格式 | 从官方镜像仓库安装,别装来路不明的包 |
| rmarkdown 包 | 渲染单个 Rmd 文件 | 与 bookdown 版本大致匹配即可 |
| pandoc | Markdown 转各种格式的核心引擎 | 版本太老会导致部分语法不识别 |
| LaTeX 发行版 | 仅输出 PDF 时需要 | 建议装完整版,别装精简版 |
| 中文字体 | PDF 中文显示 | 至少准备一套衬线、一套无衬线 |
安装命令不长:
install.packages(c("bookdown", "rmarkdown", "knitr"))LaTeX 那一步是最容易卡住的。精简版发行版经常缺宏包,编译到一半报「File xxx.sty not found」,然后你就得一个个补装,非常折磨。我的经验是直接装完整版,占几个 G 的硬盘,换来的是后面两年不用再为宏包发愁。如果实在空间紧张,至少确保装了中文支持、表格增强、字体相关这几类常用宏包。
字体这块,输出 PDF 时如果正文用中文字体而代码块用等宽英文字体,需要分别指定。系统里没装对应字体的话,编译会直接失败或者静默回退成方块。
3.2 一个能跑起来的目录结构
我习惯的目录长这样:
my-book/ ├── index.Rmd ├── 01-basics.Rmd ├── 02-design.Rmd ├── 03-practice.Rmd ├── 04-appendix.Rmd ├── _bookdown.yml ├── _output.yml ├── style.css ├── book.bib ├── images/ │ └── cover.png └── _book/index.Rmd是整本书的入口,除了承载前言内容,还要放 YAML 元信息。_bookdown.yml管构建行为,_output.yml管输出格式,两个文件分工明确:前者回答「书叫什么、包含哪些章节、输出到哪」,后者回答「每种格式长什么样」。
提示:
_book是构建产物目录,务必加进版本管理的忽略列表。它体积大、每次构建都变,混进提交历史会让仓库迅速膨胀。
3.3 index.Rmd 与两个配置文件的写法
index.Rmd的头部我一般这么写:
--- title: "一份长文档的工程化实践" author: "某开发者" date: "`r Sys.Date()`" site: "bookdown::bookdown_site" documentclass: book bibliography: [book.bib] link-citations: yes description: "把零散笔记整理成可维护书稿的完整记录" ---几个字段值得解释。site告诉构建系统这是个站点型项目,会生成完整的目录结构;documentclass: book决定 PDF 用书籍类文档模板,章节号从「第 1 章」开始;bibliography挂上文献库文件,正文里就能用引用语法;link-citations: yes让网页版里的引用变成可点击的跳转。
如果书里中文占大头,PDF 部分建议把文档类换成中文书籍类:
documentclass: ctexbook这样章节标题、图表标题里的「图」「表」「第 X 章」都会自动变成中文,不用你到处改文案。
_bookdown.yml我通常这么配:
book_filename: "my-book" delete_merged_file: true new_session: yes output_dir: "_book" language: ui: chapter_name: "第 " rmd_files: - index.Rmd - 01-basics.Rmd - 02-design.Rmd - 03-practice.Rmd - 04-appendix.Rmdnew_session: yes是个容易被忽略但很重要的开关。它让每一章在独立的 R 会话里执行,避免上一章定义的变量污染下一章。代价是启动开销略增,换来的是更高的可复现性。我见过不少人因为没开这个选项,单独渲染某一章时结果和整书渲染不一致,排查半天。
rmd_files是显式声明章节顺序的地方。有了它,文件名前缀的数字就只是给人看的,真正的顺序以这里为准。这么做的好处是,你可以在不改文件名的情况下调整章节位置。
_output.yml定义输出格式:
bookdown::gitbook: css: style.css split_by: section number_sections: true config: toc: collapse: subsection scroll_highlight: true search: true download: ["pdf", "epub"] bookdown::pdf_book: latex_engine: xelatex keep_tex: yes dev: "cairo_pdf" bookdown::epub_book: stylesheet: style.csssplit_by: section决定网页按节切分成多个页面,而不是一整页塞完。这对长篇内容很关键——单页几万字,浏览器渲染和阅读体验都会崩。keep_tex: yes保留中间生成的.tex文件,PDF 编译报错时你可以直接去看它,定位问题快到飞起。
3.4 第一次构建与单章预览
构建整本书:
bookdown::render_book("index.Rmd", "bookdown::gitbook")想一次性产出所有格式:
bookdown::render_book("index.Rmd", output_format = "all")日常写作时更常用的是单章预览:
bookdown::preview_chapter("02-design.Rmd")它只渲染这一章,几秒钟出结果,适合反复调整排版细节。另一个我很喜欢的命令是实时预览:
bookdown::serve_book()它起一个本地服务,你保存文件后浏览器自动刷新。写网页版的时候,这个体验比自己手动点构建舒服太多。
第一次构建大概率不会一次成功。我的排查顺序是:先看报错信息里提到的文件名和行号,那通常就是问题所在;再看_book目录里有没有半成品,有的话说明前几步是通的,问题在后面的格式转换;最后看中间的合并文件_main.Rmd,它是所有章节拼起来的完整稿,打开它你能直观看到章节有没有漏掉、顺序对不对。
4. 核心细节:交叉引用、编号与多格式输出
4.1 图表公式的交叉引用怎么用
这是 bookdown 最值钱的部分,也是最容易用错的地方,我把规则讲透。
引用图。在代码块的选项里给一个标签,再写图注:
```{r scatter-basic, fig.cap="样本分布散点图", fig.width=6, fig.height=4} plot(x, y) ```正文里引用它:
从 \@ref(fig:scatter-basic) 可以看出,两个变量呈明显的正相关。编译后,那句话会变成「从图 3.2 可以看出……」,图号自动算,点击可跳转。注意标签里的fig:前缀是必须的,它告诉系统你引用的是一个图对象。
引用表。表通常用knitr::kable生成:
```{r param-table} knitr::kable(param_df, caption = "关键参数对照表") ```引用写法是\@ref(tab:param-table)。这里有个坑:表格的标签来自代码块的标签,而不是caption参数,很多人搞混,写成了引用表注文字,结果引用不上。
引用公式。块级公式用这个写法:
\begin{equation} y = \beta_0 + \beta_1 x + \varepsilon (\#eq:linear-model) \end{equation}引用用\@ref(eq:linear-model)。注意(\#eq:xxx)必须写在公式环境内部,写在外部不生效。这个语法对新手来说确实别扭,但用两次就记住了。
引用章节。给标题加标签:
# 实验设计 {#exp-design}引用写\@ref(exp-design)。这里没有sec:前缀,直接是标签名,这是它和其他引用类型不太一致的地方,容易记混。
定理类环境。bookdown 内置了一套定理环境,写法是:
::: {.theorem #thm-consistency} 在大样本条件下,该估计量满足一致性。 :::引用用\@ref(thm:thm-consistency)。支持的类型包括定理、引理、推论、命题、例、练习、假设、证明、注、解,覆盖了大部分技术写作场景。
注意:交叉引用在网页版里是超链接跳转,在 PDF 里是页码引用,两者文字形式不同。所以正文里千万不要写「见第 42 页」这种硬编码的位置描述,结构一改就失效。
4.2 编号规则背后的逻辑
很多人被编号搞晕,其实规则只有一句话:编号跟着结构走,不跟着文件走。
一级标题就是章,编号 1、2、3;二级标题是节,编号 1.1、1.2;三级标题是 1.1.1。图和表在章内独立连续编号,公式同理。这一切在number_sections: true打开后自动生效。
有个细节值得说:前言部分不编号。index.Rmd里的一级标题如果写成了带编号的章节,它会占用第 1 章的位置,导致你的正文从第 2 章开始。解决办法是把前言写成不带编号的形式,或者干脆用二级标题。这个问题第一次遇到时很容易懵,因为编译不报错,只是编号莫名其妙错了一位。
另一个细节是附录。附录的编号规则和正文不同,通常是字母 A、B、C。在 bookdown 里把附录章节放到rmd_files的最后,并在标题里做相应处理即可保持一致性。如果你的书有附录,建议先拿两个空章节试一下编号效果,确认无误再往里填内容。
4.3 一套源文件三种输出的差异点
同一份源文件输出三种格式,做不到完全一致,接受这一点能省下很多纠结。
| 差异点 | HTML 站点 | EPUB | |
|---|---|---|---|
| 公式渲染 | 前端数学库,依赖网络或本地资源 | LaTeX 原生渲染,质量最高 | 支持有限,复杂公式易失真 |
| 表格宽度 | 自适应,可横向滚动 | 受版心限制,宽表会溢出 | 排版引擎能力弱,宽表常崩 |
| 字体 | 用系统字体或网络字体 | 必须内嵌,缺字体直接报错 | 依赖阅读器,可控性低 |
| 图表位置 | 随文本流 | 可浮动到页面顶部或底部 | 随文本流 |
| 交互功能 | 搜索、折叠、跳转 | 无 | 无 |
策略上的建议:把 HTML 当作主发布形态,功能最全、改起来最快;PDF 当作打印和归档形态,重点保证公式和分页质量;EPUB 当作附赠形态,别指望它完美,遇到宽表就考虑改成图片或者拆成两个窄表。
宽表这个问题我吃过亏。一张十列的数据表在网页上能横向滚动,读者无感;在 PDF 里直接溢出到页面外,打印出来右边两列全没了。解决方式是给超宽表设置更小的字号,或者拆成两张表,或者在 PDF 版本里改用横向页面。这些都可以通过条件判断在不同输出格式下走不同分支。
4.4 中文排版必须处理的几件事
中文写作者的坑比英文多,集中说几个。
PDF 字体。用xelatex引擎,然后显式指定字体:
bookdown::pdf_book: latex_engine: xelatex mainfont: "Noto Serif CJK SC" sansfont: "Noto Sans CJK SC" monofont: "Noto Sans Mono CJK SC"字体名要写系统里真实存在的名字,写错了不会报「字体不存在」,而是静默回退,最后你发现整本书变成了默认字体,中文全成方块。所以第一次配置完,一定要打开 PDF 检查正文、标题、代码块三处的字体是否都正确。
中英文混排的间距。理论上排版引擎会自动处理,实际效果看具体配置。如果发现中英文之间挤得太紧,可以在 CSS 或模板层面加一点字间距,不要用手工插空格的方式解决——一旦结构变化,手工空格全成累赘。
代码块里的中文注释。等宽字体如果只覆盖英文,中文注释会回退成别的字体,宽度对不上,看起来特别乱。选一套包含中文的等宽字体能省掉这个麻烦。
章节标题的中文编号。前面提到的ctexbook文档类,或者通过language配置覆盖文案,都能解决。我倾向于用前者,配置更少,行为更稳定。
5. 发布、协作与长期维护
5.1 构建产物的管理策略
_book目录里的东西是产物,不是源文件。我的做法是:源文件全部进版本管理,_book一律忽略,发布时由构建流程重新生成。
为什么不把产物也提交上去?因为会产生大量无意义的 diff。每次构建,HTML 里的时间戳、哈希值、资源指纹都会变,仓库体积几个月就能涨到几百兆。更麻烦的是,产物和源文件不一致时,你无法判断线上那一版到底是从哪个源文件来的。
如果托管平台只支持静态文件、不支持构建,那就把产物提交到一个单独的分支,主分支只放源文件。这样源文件的改动历史保持干净,产物分支的提交可以随便覆盖。
5.2 自动化构建的基本思路
手工点构建容易忘,尤其是改完一个小错字之后。用持续集成把构建自动化,规则可以设得很简单:推到主分支就触发构建,构建成功则发布,构建失败则保留上一次的线上版本。
关键是要让构建失败可见。我见过配置成「失败也继续发布」的流程,结果某次编译报错没被发现,线上挂了一个残缺版本好几天。正确的做法是让构建失败直接中断流程,并且把日志里的错误摘要推到通知渠道。
另一个实用技巧是缓存依赖。构建一次要跑全部代码块的话,每次都得好几分钟。把 R 包的安装结果和代码块缓存持久化下来,第二次构建能快一个数量级。不过缓存也有坑,缓存过期或者依赖版本变了之后,跑出来的结果可能是旧的,所以定期做一次不带缓存的完整构建,验证结果一致性。
5.3 多人协作时的约定
三个人以上一起写,光有工具不够,还得有约定。我总结了几条实践中真正管用的。
第一,章节文件一人一个,尽量不要两人同时改同一个文件。Markdown 的冲突合并虽然比二进制文件友好,但同一段落被两人改过之后,合并仍然需要人工介入。
第二,标签命名统一前缀。图的标签统一用fig-,表用tab-,公式用eq-,后面跟语义化的英文短名。别用fig1、fig2这种,插入新图之后所有编号都得挪。语义化命名的好处是,你半年后看fig-residual-plot立刻知道它是什么,看fig3就完全想不起来。
第三,配置文件由一个人负责。_bookdown.yml和_output.yml属于全局设置,多人同时改必然冲突。可以约定由一个人统一维护,其他人提需求。
第四,提交信息写清楚改了哪一章。刚需。回溯问题时,「改了第 4 章的表格」比「update」有用一万倍。
6. 常见问题与排查实录
6.1 高频问题速查表
下面这些是我和同行们实际踩过的,按出现频率排。
| 现象 | 最可能的原因 | 处理方式 |
|---|---|---|
| 交叉引用显示成问号或原文 | 标签拼写不一致,或缺前缀 | 核对代码块标签与引用文字是否完全一致 |
| PDF 编译到一半中断 | 缺 LaTeX 宏包或字体 | 看日志里第一个报错,补装对应宏包 |
| 中文变成方块 | 字体未指定或名称写错 | 换成系统里确实存在的字体名 |
| 图号从 1 开始重新计数 | 章节边界识别错误 | 检查标题层级,确认没有跳级 |
| 单独渲染某章结果与整书不同 | 跨章变量污染 | 打开new_session: yes |
| 网页版公式不渲染 | 数学库资源缺失 | 检查是否需要本地化数学资源 |
| 章节顺序不对 | rmd_files未更新 | 显式列出全部章节文件 |
| 删除的章节还在书里 | 合并文件残留 | 清理中间文件后重新完整构建 |
| 宽表在 PDF 里溢出 | 版心宽度不足 | 缩字号、拆表或改横向页 |
| 构建很慢 | 每次全量执行代码块 | 开缓存,并定期做一次无缓存验证 |
6.2 几个只有真跑过才知道的坑
坑一:目录里的旧文件不会自动消失。你删掉一章,重新构建,网页版的侧边栏里那一章还在。原因是输出目录里保留了上一轮的 HTML 文件,构建只做增量更新,不负责清理。解决办法很简单,删之前顺手清一下输出目录,或者干脆用脚本先删后建。我第一次遇到时以为是配置文件没生效,折腾了半小时。
坑二:缓存会让你怀疑人生。打开代码块缓存之后,某个依赖外部数据的图在数据更新后没变,你会以为代码写错了。缓存判断依据是代码内容本身,如果数据是外面读进来的,代码没变就不会重跑。所以涉及外部输入的代码块,要么关缓存,要么在缓存选项里显式声明依赖文件。
坑三:图片路径在不同输出格式下不一样。网页版的图片路径是相对 HTML 文件的,PDF 的是相对当前工作目录的。如果图放在子目录里,网页版正常、PDF 报找不到文件,八成就是这个原因。统一用相对项目根目录的路径,并且在配置里明确指定,能避开大部分问题。
坑四:某个章节的代码跑不通,整本书构建失败。这在早期很常见,尤其是写教程类内容时,示例代码本身就带错误。建议给这类代码块加上「允许报错继续」的选项,或者干脆把示例代码放在不执行的代码块里。前者适合演示错误处理,后者适合纯展示语法。
坑五:编号突然整体错位一位。十有八九是某个文件里多了一个一级标题,或者少了一个。因为章节号是按一级标题的顺序算的,多一个少一个都会连锁反应。排查方法是打开合并后的中间文件,从头扫一遍所有一级标题,一眼就能看出来。
坑六:改了 CSS 但网页没变化。浏览器缓存。强制刷新或者换个端口访问。这类问题排查起来最浪费时间,因为你会怀疑是配置文件写错了。
提示:遇到任何构建问题,第一件事是看日志里的第一个报错,而不是最后一个。后面的报错往往是前面失败的连锁反应,追着最后一个改会走很多弯路。
7. 我个人在实际操作中的几点体会
写了这么久,有几个认识是慢慢形成的,分享一下。
第一,先搭骨架再填肉。很多人拿到工具就开始写第一章,写到第五章发现结构不合理,回头改要动全身。我的做法是先建一个只有空标题的骨架工程,把所有章节标题、图表位置、引用关系先摆好,编译一遍确认目录和编号全部正确,再往里填内容。这个习惯让我少返工了无数次。
第二,别把排版问题留到最后。图表样式、字体、分页这些事,早期调一次和最后调一次,成本差好几倍。我一般在前两章写完的时候就做一次完整的 PDF 输出检查,把字体、页边距、图表位置这些基础问题一次性解决掉。后面再写就是纯内容工作,心态会轻松很多。
第三,给每个项目留一份「构建说明」。放在项目根目录的一个普通文本文件里,写清楚依赖版本、构建命令、已知问题。隔半年再回来,或者换个人接手,这份说明能省下大量摸索时间。我自己就吃过亏,隔了八个月重新打开一个项目,忘了当时为什么把某个配置项关掉,重新试错又花了一晚上。
第四,接受不同格式之间的不完美。三种输出不可能完全一致,追求完全一致是性价比最低的事情。把精力放在主发布形态上,其他形态做到「能看、能用、不出错」就够了。我现在的做法是网页版精雕细琢,PDF 保证打印质量,EPUB 只做基本验证。
最后提一个可以继续扩展的方向:如果哪天你想把这套工程变成多人长期维护的公共项目,可以在此基础上加一层自动化的格式检查和链接检查,每次提交都跑一遍,把「引用失效」「图片缺失」这类问题挡在合并之前。这一步投入不大,但对长期维护的价值非常高。