如果你还在手动复制粘贴 Markdown 到 Word、用浏览器打印 PDF、或者为每次报告格式调整浪费半小时——那你需要认识 Pandoc。作为 Hacker News 上 361 分热议的开发者工具,Pandoc 的模板引擎能让你用一条命令把 Markdown 源文件转换成精美的 HTML、PDF、DOCX、EPUB 甚至幻灯片。本文不是 Pandoc 的入门科普,而是从模板系统原理到企业级自动化流水线,带你掌握用代码驱动文档生成的完整技术栈。
📝 一、Pandoc 模板系统核心原理
1.1 为什么开发者需要 Pandoc?
大多数开发者对文档工具的认知停留在「Markdown → HTML」的单向转换。但实际开发中,文档需求远比这复杂:
- ✅ API 文档需要同时输出 HTML(在线浏览)和 PDF(离线阅读)
- ✅ 技术方案需要生成 DOCX 发给产品经理审阅
- ✅ 发布说明需要从 Git commit 自动生成 CHANGELOG
- ✅ 产品报告需要统一的品牌样式(Logo、字体、页眉页脚)
传统做法是维护多份格式文件,或者用 Word 手动排版。Pandoc 的模板系统彻底解决了这个问题——一份 Markdown 源文件 + 一套模板 = 任意格式输出。
📌 记住:Pandoc 的核心价值不是「格式转换」,而是内容与样式的分离。你只需专注于写作内容,模板负责控制最终呈现。
1.2 模板变量系统
Pandoc 模板使用 $ 包裹的变量语法,支持条件判断和循环。理解变量系统是掌握模板引擎的第一步:
| 变量 | 类型 | 说明 | 示例 |
|---|---|---|---|
$title$ |
string | 文档标题 | 从 YAML metadata 读取 |
$author$ |
list | 作者列表 | 支持多作者 |
$date$ |
string | 日期 | 默认格式 YYYY-MM-DD |
$body$ |
string | 文档正文 | Markdown 转换后的内容 |
$toc$ |
boolean | 是否生成目录 | 通过 --toc 启用 |
$meta.title$ |
string | 自定义 metadata | YAML frontmatter 中的字段 |
$if(variable)$ |
- | 条件判断 | 变量存在时渲染 |
$for(list)$ |
- | 循环 | 遍历列表变量 |
下面是一个最小的 HTML 模板示例:
<!-- 最小 Pandoc HTML 模板:pandoc-minimal.html -->
<!DOCTYPE html>
<html lang="$lang$">
<head>
<meta charset="utf-8">
<title>$title$</title>
$if(css)$
<link rel="stylesheet" href="$css$">
$endif$
$if(math)$
$math$
$endif$
</head>
<body>
$if(toc)$
<nav id="TOC">
$toc$
</nav>
$endif$
<main>
$body$
</main>
</body>
</html>
使用命令:
# 用自定义模板将 Markdown 转为 HTML
pandoc input.md \
--template=pandoc-minimal.html \
--toc \
--metadata title="API 文档 v2.0" \
-o output.html
💡 提示:
--template参数指定模板文件路径,模板中的$body$会被替换为 Markdown 转换后的 HTML 内容。所有 YAML frontmatter 中的字段都可以通过$meta.fieldname$访问。
1.3 Metadata(元数据)系统
Pandoc 支持通过 YAML frontmatter 注入自定义元数据,这些元数据在模板中可以自由引用:
# Markdown 文件的 YAML frontmatter 示例
---
title: "用户管理系统 API 文档"
version: "2.1.0"
author:
- name: "张三"
email: "zhangsan@example.com"
- name: "李四"
email: "lisi@example.com"
date: "2026-05-31"
lang: "zh-CN"
company: "示例科技有限公司"
logo: "./assets/logo.png"
header-includes:
- \usepackage{fancyhdr}
- \pagestyle{fancy}
---
在模板中这样使用:
<!-- 引用自定义 metadata 的模板片段 -->
<header>
$if(logo)$
<img src="$logo$" alt="$company$ logo" class="logo">
$endif$
<h1>$title$</h1>
<p class="version">版本:$version$</p>
<div class="authors">
$for(author)$
<span>$author.name$ <$author.email$></span>
$endfor$
</div>
<time>$date$</time>
</header>
⚡ **关键结论:**YAML frontmatter 是 Pandoc 模板系统的「数据层」。把所有可变信息都放在 frontmatter 中,模板只负责渲染,这是实现「一份源文件、多种输出格式」的核心设计模式。
🔧 二、自定义模板实战:从 HTML 到 PDF
2.1 企业级 HTML 模板
下面是一个生产可用的 HTML 文档模板,包含响应式布局、代码高亮、目录导航等特性:
<!-- 企业级 Pandoc HTML 模板:enterprise.html -->
<!DOCTYPE html>
<html lang="$lang$">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>$title$ — $company$</title>
<style>
:root {
--primary: #2563eb;
--bg: #ffffff;
--text: #1f2937;
--code-bg: #f3f4f6;
--border: #e5e7eb;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, "Noto Sans SC", sans-serif;
max-width: 900px; margin: 0 auto;
padding: 2rem; color: var(--text);
line-height: 1.8;
}
header { border-bottom: 2px solid var(--primary); padding-bottom: 1rem; margin-bottom: 2rem; }
header h1 { color: var(--primary); font-size: 1.8rem; }
.meta { color: #6b7280; font-size: 0.9rem; margin-top: 0.5rem; }
nav#TOC {
background: var(--code-bg); border-radius: 8px;
padding: 1.5rem; margin-bottom: 2rem;
}
nav#TOC ul { list-style: none; padding-left: 1.2rem; }
nav#TOC > ul { padding-left: 0; }
nav#TOC a { color: var(--primary); text-decoration: none; }
nav#TOC a:hover { text-decoration: underline; }
h2 { color: var(--primary); margin: 2rem 0 1rem; border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; }
h3 { margin: 1.5rem 0 0.75rem; }
pre {
background: var(--code-bg); border-radius: 6px;
padding: 1rem; overflow-x: auto; margin: 1rem 0;
}
code { font-family: "JetBrains Mono", "Fira Code", monospace; font-size: 0.9em; }
:not(pre) > code { background: var(--code-bg); padding: 0.2em 0.4em; border-radius: 3px; }
table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
th, td { border: 1px solid var(--border); padding: 0.6rem 1rem; text-align: left; }
th { background: var(--code-bg); font-weight: 600; }
blockquote { border-left: 4px solid var(--primary); padding: 0.5rem 1rem; margin: 1rem 0; background: #eff6ff; }
@media print {
body { max-width: 100%; }
nav#TOC { break-after: page; }
pre { white-space: pre-wrap; word-wrap: break-word; }
}
$if(highlighting-css)$
$highlighting-css$
$endif$
</style>
$for(header-includes)$
$header-includes$
$endfor$
</head>
<body>
<header>
<h1>$title$</h1>
<div class="meta">
$if(version)$版本 $version$ · $endif$
$for(author)$$author.name$$sep$, $endfor$ · $date$
$if(company)$ · $company$$endif$
</div>
</header>
$if(toc)$
<nav id="TOC">
<h2>目录</h2>
$toc$
</nav>
$endif$
<main>
$body$
</main>
<footer style="margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--border); color: #9ca3af; font-size: 0.85rem;">
由 Pandoc 自动生成 · $date$
</footer>
</body>
</html>
使用方式:
# 生成带目录的 HTML 文档
pandoc api-doc.md \
--template=enterprise.html \
--toc \
--toc-depth=3 \
--highlight-style=tango \
--metadata company="示例科技" \
-o api-doc.html
2.2 PDF 模板(通过 LaTeX)
Pandoc 生成 PDF 的核心是通过 LaTeX 引擎。中文文档需要特别配置字体和编码:
% 企业级 Pandoc PDF 模板:enterprise-pdf.tex
\documentclass[$if(fontsize)$$fontsize$$else$12pt$endif$]{article}
% === 中文支持 ===
\usepackage[UTF8]{ctex}
\usepackage{fontspec}
\setmainfont{Noto Serif CJK SC}
\setsansfont{Noto Sans CJK SC}
\setmonofont{JetBrains Mono}[Scale=0.85]
% === 页面布局 ===
\usepackage[a4paper, margin=2.5cm]{geometry}
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\fancyhead[L]{$if(company)$$company$$endif$}
\fancyhead[R]{$title$}
\fancyfoot[C]{\thepage}
\renewcommand{\headrulewidth}{0.4pt}
% === 样式 ===
\usepackage{xcolor}
\definecolor{primary}{RGB}{37, 99, 235}
\usepackage{hyperref}
\hypersetup{colorlinks=true, linkcolor=primary, urlcolor=primary}
\usepackage{booktabs} % 更好看的表格
\usepackage{listings} % 代码块
\lstset{
basicstyle=\small\ttfamily,
backgroundcolor=\color{gray!10},
frame=single,
breaklines=true
}
% === 标题信息 ===
\title{\color{primary}\Large\bfseries $title$}
\author{$for(author)$$author.name$ \\ \small $author.email$$sep$ \and $endfor$}
\date{$date$}
\begin{document}
$if(highlighting-macros)$
$highlighting-macros$
$endif$
\maketitle
$if(toc)$
\tableofcontents
\newpage
$endif$
$body$
\end{document}
生成 PDF 的命令:
# 用 XeLaTeX 引擎生成中文 PDF(推荐)
pandoc api-doc.md \
--template=enterprise-pdf.tex \
--pdf-engine=xelatex \
--toc \
--highlight-style=tango \
-V fontsize=11pt \
-o api-doc.pdf
⚠️ **警告:**生成中文 PDF 必须使用
xelatex或lualatex引擎,pdflatex不支持中文。同时确保系统安装了中文字体(如 Noto CJK 系列)。Ubuntu/Debian 下执行sudo apt install texlive-xetex fonts-noto-cjk。
2.3 多格式输出对比
| 输出格式 | 模板类型 | 引擎 | 适用场景 | 复杂度 |
|---|---|---|---|---|
| HTML | .html 模板 |
Pandoc 内置 | 在线文档、博客 | ⭐ 低 |
.tex LaTeX 模板 |
XeLaTeX | 正式报告、合同 | ⭐⭐⭐ 高 | |
| DOCX | 参考文档 .docx |
Pandoc 内置 | 协作审阅、交付 | ⭐⭐ 中 |
| EPUB | .html 模板 + CSS |
Pandoc 内置 | 电子书 | ⭐⭐ 中 |
| 幻灯片 | .html reveal.js |
Pandoc 内置 | 技术分享 | ⭐⭐ 中 |
DOCX 格式的模板比较特殊——它使用一个参考文档(reference doc)而非文本模板:
# 生成参考文档(只需执行一次)
pandoc -o custom-reference.docx --print-default-data-file reference.docx
# 用 Word 打开 custom-reference.docx 修改样式
# 然后用它作为模板
pandoc input.md --reference-doc=custom-reference.docx -o output.docx
🚀 三、Lua Filter 与自动化流水线
3.1 Lua Filter:Pandoc 的「插件系统」
Lua Filter 是 Pandoc 最强大的扩展机制——它允许你在转换过程中拦截和修改文档的 AST(抽象语法树)。这比简单的模板变量灵活得多。
场景一:自动为外部链接添加图标和 target 属性
-- filter: external-links.lua
-- 自动为外部链接添加 target="_blank" 和 CSS class
function Link(el)
if el.target:match("^https?://") then
el.attributes["target"] = "_blank"
el.attributes["rel"] = "noopener noreferrer"
el.classes:insert("external-link")
end
return el
end
# 使用 Lua Filter
pandoc input.md --lua-filter=external-links.lua -o output.html
场景二:自动为代码块添加行号和复制按钮
-- filter: code-enhance.lua
-- 为代码块添加行号和复制按钮(HTML 输出)
local code_block_count = 0
function CodeBlock(block)
code_block_count = code_block_count + 1
local id = "code-block-" .. code_block_count
local lang = block.classes[1] or "text"
local wrapper = '<div class="code-block" id="' .. id .. '">'
wrapper = wrapper .. '<div class="code-header">'
wrapper = wrapper .. '<span class="lang">' .. lang .. '</span>'
wrapper = wrapper .. '<button onclick="copyCode(\'' .. id .. '\')">复制</button>'
wrapper = wrapper .. '</div>'
wrapper = wrapper .. '<pre><code class="language-' .. lang .. '">'
wrapper = wrapper .. block.text
wrapper = wrapper .. '</code></pre></div>'
return pandoc.RawBlock("html", wrapper)
end
场景三:自动生成 API 端点表格
-- filter: api-table.lua
-- 从特殊的代码块自动生成 API 端点表格
-- 使用方法:在 Markdown 中写 ```api-endpoint 代码块
function CodeBlock(block)
if block.classes[1] == "api-endpoint" then
local rows = {}
for line in block.text:gmatch("[^\n]+") do
local method, path, desc = line:match("^(%u+)%s+(%S+)%s+(.+)$")
if method then
table.insert(rows, {method, path, desc})
end
end
local header = pandoc.Row({
pandoc.Cell({pandoc.Plain({pandoc.Strong(pandoc.Str("方法"))})}),
pandoc.Cell({pandoc.Plain({pandoc.Strong(pandoc.Str("路径"))})}),
pandoc.Cell({pandoc.Plain({pandoc.Strong(pandoc.Str("说明"))})}),
})
local body_rows = {}
for _, row in ipairs(rows) do
table.insert(body_rows, pandoc.Row({
pandoc.Cell({pandoc.Plain({pandoc.Str(row[1])})}),
pandoc.Cell({pandoc.Plain({pandoc.Code(row[2])})}),
pandoc.Cell({pandoc.Plain({pandoc.Str(row[3])})}),
}))
end
local tbl = pandoc.Table(
pandoc.Caption({pandoc.Plain({pandoc.Str("API 端点列表")})}),
{pandoc.AlignLeft, pandoc.AlignLeft, pandoc.AlignLeft},
pandoc.TableHead({header}),
{pandoc.TableBody(body_rows)},
pandoc.TableFoot()
)
return tbl
end
end
在 Markdown 中这样使用:
```api-endpoint
GET /api/users 获取用户列表
POST /api/users 创建新用户
GET /api/users/:id 获取单个用户
PUT /api/users/:id 更新用户信息
DELETE /api/users/:id 删除用户
```
这会自动渲染为一个格式化的表格,无需手动维护 Markdown 表格语法。
💡 **提示:**Lua Filter 可以组合使用。
--lua-filter=a.lua --lua-filter=b.lua会按顺序执行多个 Filter,每个 Filter 处理上一个 Filter 的输出。建议将不同功能拆分为独立的 Filter 文件。
3.2 CI/CD 自动化流水线
将 Pandoc 集成到 CI/CD 中,实现文档的自动构建和发布。以下是一个完整的 GitHub Actions 配置:
# .github/workflows/docs.yml
# 文档自动构建和发布流水线
name: Build Docs
on:
push:
branches: [main]
paths: ['docs/**']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 Pandoc 和 LaTeX
run: |
sudo apt-get update
sudo apt-get install -y pandoc texlive-xetex fonts-noto-cjk
- name: 安装 Pandoc Filters
run: |
# 安装 pandoc-crossref(交叉引用)
wget https://github.com/lierdakil/pandoc-crossref/releases/latest/download/pandoc-crossref-Linux-XeLaTeX.tar.xz
tar xf pandoc-crossref-Linux-XeLaTeX.tar.xz
sudo mv pandoc-crossref /usr/local/bin/
- name: 构建 HTML 文档
run: |
for f in docs/*.md; do
name=$(basename "$f" .md)
pandoc "$f" \
--template=templates/enterprise.html \
--lua-filter=filters/external-links.lua \
--lua-filter=filters/code-enhance.lua \
--toc --toc-depth=3 \
--highlight-style=tango \
--metadata date="$(date +%Y-%m-%d)" \
-o "dist/${name}.html"
done
- name: 构建 PDF 文档
run: |
for f in docs/*.md; do
name=$(basename "$f" .md)
pandoc "$f" \
--template=templates/enterprise-pdf.tex \
--pdf-engine=xelatex \
--toc \
--highlight-style=tango \
-o "dist/${name}.pdf"
done
- name: 部署到 GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
3.3 Makefile 封装:一条命令搞定一切
# Makefile
# 文档构建自动化:make html / make pdf / make all
SOURCES := $(wildcard docs/*.md)
HTML_OUTPUT := $(patsubst docs/%.md, dist/%.html, $(SOURCES))
PDF_OUTPUT := $(patsubst docs/%.md, dist/%.pdf, $(SOURCES))
TEMPLATE_HTML := templates/enterprise.html
TEMPLATE_PDF := templates/enterprise-pdf.tex
FILTERS := --lua-filter=filters/external-links.lua --lua-filter=filters/code-enhance.lua
.PHONY: all html pdf clean
all: html pdf
html: $(HTML_OUTPUT)
pdf: $(PDF_OUTPUT)
dist/%.html: docs/%.md $(TEMPLATE_HTML) $(wildcard filters/*.lua)
@mkdir -p dist
pandoc $< \
--template=$(TEMPLATE_HTML) \
$(FILTERS) \
--toc --toc-depth=3 \
--highlight-style=tango \
--metadata date="$$(date +%Y-%m-%d)" \
-o $@
@echo "✅ 生成 HTML: $@"
dist/%.pdf: docs/%.md $(TEMPLATE_PDF)
@mkdir -p dist
pandoc $< \
--template=$(TEMPLATE_PDF) \
--pdf-engine=xelatex \
--toc \
--highlight-style=tango \
-o $@
@echo "✅ 生成 PDF: $@"
clean:
rm -rf dist/
使用方式:
make all # 构建所有格式
make html # 只构建 HTML
make pdf # 只构建 PDF
make clean # 清理输出
⚡ 关键结论:Pandoc + Make + CI/CD 的组合是目前最成熟的文档自动化方案。相比 MkDocs、Docusaurus 等专用文档工具,Pandoc 的优势在于输出格式不受限——同一份源文件可以输出 HTML、PDF、DOCX、EPUB、reveal.js 幻灯片,而专用工具通常只支持 HTML。
📊 四、Pandoc vs 其他文档工具对比
| 特性 | Pandoc | MkDocs | Docusaurus | VitePress |
|---|---|---|---|---|
| 输入格式 | Markdown/LaTeX/DOCX/… | Markdown | MDX | Markdown |
| 输出格式 | HTML/PDF/DOCX/EPUB/幻灯片 | HTML | HTML | HTML |
| PDF 输出 | ✅ 原生支持 | ❌ 需插件 | ❌ 不支持 | ❌ 不支持 |
| DOCX 输出 | ✅ 原生支持 | ❌ 不支持 | ❌ 不支持 | ❌ 不支持 |
| 模板系统 | ✅ 完整的模板引擎 | Jinja2 | React 组件 | Vue 组件 |
| 可编程性 | ✅ Lua Filter | Python 插件 | JS 插件 | Vue 插件 |
| 搜索 | ❌ 需外部方案 | ✅ 内置 | ✅ 内置 | ✅ 内置 |
| 版本控制 | ✅ 纯文本 | ✅ 纯文本 | ✅ 纯文本 | ✅ 纯文本 |
| 学习曲线 | ⭐⭐⭐ 高 | ⭐ 低 | ⭐⭐ 中 | ⭐⭐ 中 |
| 适用场景 | 多格式文档、报告、论文 | 项目文档 | 技术文档站 | 技术文档站 |
💡 **提示:**Pandoc 和专用文档工具不是互斥的。很多团队用 Pandoc 处理需要 PDF/DOCX 输出的正式文档,用 VitePress/MkDocs 搭建在线文档站。两者可以共享同一套 Markdown 源文件。
⚠️ 五、避坑指南与最佳实践
5.1 常见坑点
- ❌ 直接用
pandoc生成 PDF 却不指定--pdf-engine=xelatex— 默认的 pdflatex 不支持中文,会报编码错误 - ❌ 模板中使用未定义的变量 — Pandoc 不会报错,只是静默输出空字符串,导致页面出现空白
- ❌ 在 YAML frontmatter 中使用复杂 Markdown — 模板变量不会二次解析 Markdown,需要在模板中用
$rawAttribute$处理 - ❌ 忽略
--standalone参数 — 不加此参数时 Pandoc 只输出 body 内容,不包含完整的 HTML 文档结构
5.2 最佳实践
- ✅ 版本锁定 Pandoc — 不同版本的 Pandoc 模板语法可能有差异,在 CI/CD 中固定版本(
pandoc --version) - ✅ 模板继承 — 用
$--include-before$和$--include-after$实现模板片段复用 - ✅ 增量构建 — 用 Make 的依赖检查,只重新构建修改过的文件
- ✅ 测试模板 — 为模板编写测试用例,确保变量缺失时有合理的默认值
# 锁定 Pandoc 版本的安装方式
# macOS
brew install pandoc@3.6.4
# Ubuntu/Debian(下载特定版本 .deb)
wget https://github.com/jgm/pandoc/releases/download/3.6.4/pandoc-3.6.4-1-amd64.deb
sudo dpkg -i pandoc-3.6.4-1-amd64.deb
🎯 总结
Pandoc 模板系统的核心价值可以用一句话概括:内容与表现的彻底分离。开发者只需要维护一份 Markdown 源文件,通过模板和 Filter 的组合,可以生成任意格式、任意样式的文档输出。
实施路线图:
- **第 1 天:**安装 Pandoc,用默认模板生成 HTML 和 PDF,熟悉基本流程
- **第 3 天:**创建自定义 HTML 模板,加入品牌样式和目录导航
- **第 1 周:**编写 Lua Filter 处理自定义语法(如 API 端点表格)
- **第 2 周:**配置 CI/CD 流水线,实现文档自动构建和发布
📌 记住:不要一开始就追求完美的模板。先用 Pandoc 默认模板跑通流程,再逐步定制样式。模板系统的价值在于渐进式优化——你可以随时修改模板而不影响内容源文件。
相关工具推荐:
- ✅ Pandoc — 文档转换的「瑞士军刀」,支持 40+ 种格式互转
- ✅ Pandoc Lua Filters 官方仓库 — github.com/pandoc/lua-filters,大量现成的 Filter
- ✅ Quarto — 基于 Pandoc 的新一代科学出版系统,适合数据报告
- ✅ DocToc — 自动生成 Markdown 目录的 CLI 工具
- ✅ GitHub Actions for Pandoc — 预配置的 CI/CD 模板