Antora:解决多仓库多版本技术文档的静态站点生成方案
1. 为什么我放弃了Sphinx和GitBook转向Antora先说个背景。我之前一直用Sphinx维护技术文档后来接手了一个多产品线的项目文档分散在四五个Git仓库里每个仓库一套独立的文档站点版本还各有各的标签。改一个跨产品的操作步骤要在几个站点之间来回跳着确认发布的时候更是噩梦——手动同步、版本对不上、目录结构各写各的。后来我陆续试过GitBook、Docsify、VuePress它们解决了一部分痛点但都没法同时满足三个核心诉求多仓库内容聚合到一个站点每个版本有独立访问路径旧版本不消失文档内容跟代码一起走发布流程能自动化直到我认真用了Antora才发现这东西基本就是为这类场景设计的。它不是又一个“用Markdown生成静态站”的轮子而是一个真正面向多仓库、多版本、组件化内容架构的文档站点生成器。Antora的核心概念就三个组件Component、版本Version、仓库Repository。理解这三个词就理解了大半个Antora。组件一个逻辑上的文档单元比如“用户手册”“API参考”一个站点可以由多个组件组成。版本每个组件可以有多个版本Antora会为每个版本生成独立的URL路径。仓库文档源文件所在的Git仓库Antora聚合这些仓库的内容来生成站点。如果你现在的处境和我当时类似——文档散落多仓、版本混乱、手工发布累到吐那这篇文章值得看完。我会从设计思路讲到实际落地再把我踩过的坑一并倒出来。2. 先理解Antora的内容聚合方式不是复制而是映射我第一次接触Antora时脑子里还带着“把文件拷贝到某个目录里再生成”的惯性思维结果理解上绕了不少弯路。后来搞明白了Antora做的事情不是复制内容而是通过配置文件把分散在各个Git仓库里的文档“映射”到一个统一的内容目录里。这个映射关系定义在站点根目录的antora-playbook.yml中site: title: 我的产品文档中心 url: https://docs.example.com start_page: user-guide::index.adoc content: sources: - url: https://github.com/example/user-guide.git branches: [1.0, 2.0] start_path: docs - url: https://github.com/example/api-reference.git branches: [main] start_path: docs这段配置的意思很直白content.sources声明要拉取哪些仓库以及拉取哪些分支。每个分支对应组件的一个版本。start_path仓库内哪个子目录是文档根目录。start_page站点首页指向哪个组件、哪个版本的哪一页。Antora拿到这份配置后会做这几件事克隆每个仓库到本地缓存根据分支名识别版本比如1.0分支就是1.0版本main分支默认是未发布版本解析每篇文档头和仓库内的antora.yml组件描述文件构建组件版本树计算页面间导航渲染并输出静态HTML站点所以Antora实际是一个“文件收集器 文档解析器 站点渲染器”的组合。它不要求所有文档在一个目录里而是在构建时动态聚合。也就是说只要源仓库还在重建站点随时能聚合出完整内容。2.1 版本规则分支名怎么变成访问路径Antora对版本的处理很灵活默认规则是你配置什么分支名它就生成什么版本号。比如branches: [1.0, 2.0]访问路径就是https://docs.example.com/user-guide/1.0/ https://docs.example.com/user-guide/2.0/为了区分“已发布版本”和“最新开发版”通常用main分支表示最新未发布内容。Antora会自动把main标记为pre-release版本URL里会带上latest标识但路径上不会出现“main”字样。如果你希望某个版本在站点里显示为默认版本可以这样配置content: sources: - url: https://github.com/example/user-guide.git branches: [1.0, 2.0] start_path: docs versioning: - version: 2.0 display_version: 最新版这样站点导航里“2.0”会被显示为“最新版”但URL路径仍然是2.0。默认版本的选择规则是版本号数字最大的、不是pre-release的那个。2.2 组件描述文件每份文档都要“自报家门”每个文档源仓库里都必须有一个antora.yml它声明该仓库承载的是哪个组件。比如user-guide仓库的docs/antora.ymlname: user-guide title: 用户指南 version: 2.0 start_page: index.adoc nav: - modules/ROOT/nav.adoc这里的name是组件标识title是在站点导航栏里显示的名称version会作为该组件此版本的版本号start_page指定进入该组件后默认打开哪个页面。如果你多个分支共用一份antora.yml那版本号就会一样Antora构建时可能报“版本冲突”之类的警告。我的建议是每个分支的antora.yml里写对应的版本号让版本信息和分支一一对应避免混淆。3. 目录结构Antora的module体系如何组织不同类别的内容Antora沿用了AsciiDoc生态里的一套模块化目录约定。每个组件下可以拆成多个模块module每个模块按用途分为ROOT该组件的默认模块页面可以直接通过组件名::页面名.adoc引用tasks、concepts、references按文档类型拆分方便管理和权限控制一个标准的组件目录结构是user-guide/ ├── antora.yml └── modules/ ├── ROOT/ │ ├── nav.adoc │ └── pages/ │ └── index.adoc ├── tasks/ │ ├── nav.adoc │ └── pages/ │ ├── install.adoc │ └── config.adoc └── references/ ├── nav.adoc └── pages/ └── parameters.adoc这种结构看起来层级多其实好处是不同用途的内容被物理隔开不会混在一起。我写过一段时间的Sphinx经常把“概念说明”和“操作步骤”揉在一个页面里后期维护时想拆都费劲。Antora的module机制强制你做内容分类从源头上逼着你把文档结构理清楚。引用另一个模块的页面时写法是详见 xref:tasks:install.adoc[安装指南]如果引用的页面就在当前模块里可以直接写详见 xref:config.adoc[配置说明]引用其他组件的页面需要加上组件名详见 xref:api-reference:references/endpoints.adoc[接口列表]这里的核心逻辑就是启动Antora时它会读取每个组件的antora.yml和模块结构建立完整的引用索引。所以哪怕内容散落在不同仓库最终站点的内部链接都是有效的。3.1 start_page到底指到哪里很多人初次配置start_page会写错。它指定的不是“文档源仓库里的路径”而是构建后站点里的逻辑路径。逻辑路径的语法是组件名:模块名:页面文件名。例如site: start_page: user-guide::index.adoc注意这里的::它表示ROOT模块。如果你指定的是其他模块就要写全site: start_page: user-guide:tasks:install.adoc刚上手时建议都用ROOT模块少踩路径理解的坑。等站点结构稳定后再按需拆分模块。4. 搭建一个Antora站点的完整流程现在从头走一遍实际搭建流程。假设我们有两个仓库user-guide用户指南有两个版本分支1.0、2.0api-reference接口文档只有main分支最终要生成一个包含这两个组件的站点。4.1 安装AntoraAntora基于Node.js我用的是npm全局安装npm install -g antora/cli antora/site-generator-default安装完成后可以确认一下版本antora --version如果不想全局安装也可以把它作为项目的开发依赖放在package.json里然后通过npx antora调用。团队协作时我建议后者能锁定版本避免环境差异。4.2 准备文档源仓库以user-guide仓库为例克隆到本地后在docs目录下创建antora.ymlname: user-guide title: 用户指南 version: 2.0 start_page: index.adoc nav: - modules/ROOT/nav.adoc在docs/modules/ROOT/pages/index.adoc写首页内容 用户指南 欢迎使用我们的产品。 参考 xref:tasks:install.adoc[安装说明] 开始上手。在docs/modules/tasks/pages/install.adoc写一个操作页 安装说明 在终端中执行以下命令 [source,bash] ---- npm install -g example/cli ----然后导航文件docs/modules/ROOT/nav.adoc里加上页面* xref:index.adoc[用户指南] * xref:tasks:install.adoc[安装说明]4.3 写Playbook并构建站点根目录新建antora-playbook.ymlsite: title: 产品文档中心 start_page: user-guide::index.adoc url: https://docs.example.com content: sources: - url: ./user-guide branches: [2.0] start_path: docs - url: ./api-reference branches: [main] start_path: docs ui: bundle: url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip snapshot: true然后执行antora antora-playbook.yml如果一切正常会生成build/site目录里面就是完整静态站点。用浏览器打开build/site/index.html就能看到合并后的文档首页。4.4 多版本的配置方式如果user-guide有1.0和2.0两个分支Playbook就写成content: sources: - url: ./user-guide branches: [1.0, 2.0] start_path: docsAntora会自动读取每个分支里的antora.yml把version字段写为2.0的分支归到2.0版本写为1.0的分支归到1.0版本。两个版本会生成不同的URL路径https://docs.example.com/user-guide/1.0/index.html https://docs.example.com/user-guide/2.0/index.html站点右上角的版本切换器也会随之显示这两个版本。这样做的好处是新旧版本文档同时在线用户随时能切回旧版查看兼容性说明而不用像以前那样维护两套独立站点。5. Antora实战经验我先后在三个项目上踩过的坑Antora文档写得比较全但有一些细节是文档里不太强调、实际用起来却很容易出问题的地方。5.1 分支名和组件版本不一致我早期在antora.yml里写死版本号1.0但Playbook拉取的是main分支。结果构建出来的站点里这个组件被识别为“main版本”和预期完全对不上。经验是分支名决定Antora识别的版本来源而antora.yml里的version字段决定最终展示的版本号。两者如果不一致会有两种情况antora.yml里写版本分支名不写版本按antora.yml的版本展示分支名写版本antora.yml不写按分支名展示我自己的习惯是发布分支用数字命名如1.0、2.0antora.yml里也同步写数字版本从根上杜绝混乱。5.2 nav.adoc的层级控制Antora的导航层级完全靠nav.adoc里的嵌套列表控制。如果层级嵌套不当可能出现导航里突然多出来一个子项、或者层级错位的问题。一个常用的写法* 入门指南 ** xref:tasks:install.adoc[安装] ** xref:tasks:quickstart.adoc[快速开始] * 进阶主题 ** xref:concepts:architecture.adoc[架构说明] ** xref:references:parameters.adoc[参数参考]Antora会根据这个列表生成菜单结构但注意每一层的第一个条目会被作为标题处理后面带链接的条目才是菜单项。这也是新手最容易搞混的点。5.3 页面没有出现在导航里有时候内容文件明明在pages目录下但站点里找不到入口。原因基本都是忘在对应模块的nav.adoc里添加条目。Antora不会自动扫描所有页面文件它只渲染导航文件里明确引用到的页面。我建议一开始就给每个模块维护一个简单的nav.adoc每新增页面时顺手加进去不要等文件多了再批量补。5.4 外部链接和附件资源Antora支持在页面里用link宏添加外部链接官方文档link:https://developer.mozilla.org/[MDN]如果需要在页面里嵌入图片把图片放到某个模块的images目录下然后引用image::install-flow.png[安装流程]这里要求图片文件放在页面文件同级的images目录或模块根目录的images目录中否则构建时找不到资源。5.5 构建速度慢怎么办Antora每次构建都会执行git clone或git pull。仓库很大、历史很长时构建会明显变慢。可以启用镜像缓存或者限制拉取深度content: sources: - url: ./user-guide branches: [1.0, 2.0] start_path: docs tags: []通过减少不必要的tag拉取来减轻负担。如果仓库本身就慢更好的做法是先手动git clone到统一目录Playbook里指向本地路径。6. 发布策略和自动化把Antora接进CI/CDAntora产出的是纯静态文件部署非常灵活。可以扔到Nginx、S3、GitHub Pages、腾讯云COS等任意静态资源托管平台。我的推荐是把它放进CI流程里实现“push标签即发布”。6.1 一个最简的GitHub Actions示例name: Build Docs on: push: branches: - main tags: - v* jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 20 - run: npm install -g antora/cli antora/site-generator-default - run: antora antora-playbook.yml - uses: actions/upload-pages-artifactv3 with: path: build/site这个流水线做的事情很简单代码更新或打标签时自动构建文档然后发布到GitHub Pages。如果你用的是别的CI平台思路也是一样的——无非是先拉源码、再构建、再上传产物。6.2 多仓库之间的发布顺序多仓库场景下A仓库的文档更新后可能要等B仓库也更新才能生成一份完整的站点。这时候可以把每个仓库的docs目录都作为独立资源由统一的Playbook来拉取和聚合而不是每个仓库各自维护一套发布脚本。我在实际项目里是把Playbook放在一个单独的“文档发布仓库”其他仓库只维护内容。发布仓库的CI负责拉取其他仓库的最新分支重新生成站点。这样内容所有权清晰发布入口唯一。7. 定场UI主题和品牌定制别用默认UI糊弄用户Antora默认的UI主题是Antora官方提供的基础样式功能齐全但识别度不高。如果文档站是给客户或团队外部看的我建议花点时间定制。UI定制有两条路基于默认UI包修改CSS变量基于UI源码二次开发替换布局和组件如果你只是想要品牌主色调、Logo、页脚信息改动量不大。Antora官方UI仓库是antora/antora-ui-default可以Fork后改代码再构建成zip包Playbook里指向自己的zip地址ui: bundle: url: https://example.com/custom-antora-ui.zip snapshot: true有一家公司在内部用Antora做产品文档中心只调整了颜色变量和顶栏Logo看起来就和默认主题完全不一样了。7.1 定制时的几个关键点默认UI包自带搜索功能基于lunr或pagefind如果你建设的是公开站点建议确认搜索索引是否能够覆盖所有版本导航组件默认支持折叠但如果你的导航层级很深建议在使用前先在测试站点里检查折叠交互是否顺畅自定义site.url会影响站内搜索和SEO生产环境务必绑定正式域名8. 和其他文档生成器的对比为什么最后留的是Antora我用过的方案里Sphinx最强的是Python生态的自动文档提取尤其配合autodocGitBook编辑体验好但开源自托管版早已停止维护VuePress/ Docsify适合轻量个人站点团队级多版本聚合场景还是Antora更牢靠。一个简单的对比表工具多仓库聚合多版本发布内容来源技术栈Antora原生支持原生支持Git仓库Node.jsSphinx需扩展配置需扩展配置本地目录PythonGitBook较弱较弱Git仓库Node.jsVuePress需插件需插件本地目录Node.jsDocsify不支持不支持本地目录Node.js如果你的核心诉求是“组件的多版本聚合、内容跟着代码仓库走、构建结果可直接部署”Antora的整合度最高。但也要说清楚Antora的文档源格式要求是AsciiDoc不是Markdown。很多人会因为这一点犹豫。其实AsciiDoc的学习成本不算高常见标题、列表、表格、代码块语法和Markdown差异不大。而且它天生支持交叉引用、条件内容、术语表这些恰好是复杂文档需要的特性。9. 进阶技巧组件版本混合如何展示“最新”和“历史版本”多版本并存时Antora站点会展示一个版本选择器。但有时候业务上希望“最新版”和“历史维护版”混在一起展示。这里有一个设置值得关注content: sources: - url: ./user-guide branches: [1.0, 2.0] start_path: docs versioning: - version: 2.0 display_version: 当前版本 - version: 1.0 display_version: 旧版本同时可以把历史版本标记为prerelease这样它不会成为默认展示版本但依然可访问content: sources: - url: ./user-guide branches: [2.0] start_path: docs - url: ./user-guide branches: [1.0] start_path: docs versioning: - version: 1.0 prerelease: true这样配置以后1.0还是可以在URL里直接访问但不会干扰默认体验。对于需要长期维护多个版本的B端产品这种模式特别实用。10. 一个成熟的最小站点配置参考最后放一个我可以直接套用的最小配置适合刚导团队转到Antora时使用。project-docs/ ├── antora-playbook.yml └── src/ └── user-guide/ ├── antora.yml └── modules/ ├── ROOT/ │ ├── nav.adoc │ └── pages/ │ └── index.adoc └── tasks/ ├── nav.adoc └── pages/ └── install.adocPlaybook内容是site: title: 团队文档中心 start_page: user-guide::index.adoc url: http://localhost:8080 content: sources: - url: ./src/user-guide branches: HEAD start_path: . ui: bundle: url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip snapshot: true执行构建antora antora-playbook.yml然后cd build/site python3 -m http.server 8080打开浏览器访问http://localhost:8080看效果。这套流程从搭建到预览只需几分钟适合团队内部先跑通再决定是否接入CI。我在实际使用中发现Antora最容易被低估的一点就是它“仓库复用”思想同一个组件仓库不同的分支天然对应不同版本内容维护和代码发布可以走同一条流水线。这一点在真正跑起来之后省掉的是大量重复的文档管理动作。如果你正在纠结该选什么文档工具又恰好同时有多仓库和多版本的痛点Antora值得你认真试一次。