← 返回博客
观点 AI 技术

HTML 不是新的 Markdown——我们如何构建了 antirez 说的那个「更好的 Markdown」

一个 Anthropic 工程师说「HTML is the new Markdown」,Redis 之父说「我们需要更好的 Markdown」。这场争论让我们想清楚了一件事:Mark.build 在做的,正是那个更好的 Markdown 的具体实现。本文展示它是怎么工作的。


最近,Anthropic 内部一位 Claude Code 工程师在 X 上说了一句引爆开发者社区的话:

“HTML is the new Markdown。”

他的逻辑是真实的:AI 生成内容时,HTML 能表达表格、图表、交互控件和复杂布局,而标准 Markdown 做不到。他几乎停止写 .md 文件,转而让 Claude Code 替他生成 HTML。

这条推文获得了超过 1000 万次曝光,评论区撕裂成两派。

Redis 之父 antirez 的回应是我见过的最准确的诊断:

“每当我们从语义密集的格式迁移到语义稀疏的格式,就是在倒退。我能理解我们需要一个更好的 Markdown,但我无法理解为什么要用 HTML 来替代它。”

antirez 说对了。而那个”更好的 Markdown”,其实已经有了一个具体实现——就是 Mark.build 正在做的事。

本文是一次完整的展示:这套扩展机制怎么工作,为什么它比 HTML 更对。


问题是真实的,但方向走偏了

先承认那位工程师识别出的问题是真实的:当你用 AI 生成技术文档、项目报告、产品说明时,标准 Markdown 的表达能力确实不够用。你想展示数据指标,只能塞进一个普通表格;想区分不同语言的代码示例,没有标签页;想加一个警告提示框,只能用 > 引用块将就;想做一个定价页面,没有任何选择。

于是 AI 生成 HTML。结果是:

token 成本翻 3-4 倍。 一组定价卡片的 HTML 容易超过 200 行。等效的声明式配置,20 行够了。

人无法直接编辑。 改一个价格数字需要重新提示 AI,或者在开发者工具里找到那行 HTML 手动改。

版本控制失效。 git diff 里一个段落改动可能带出几十行标签变化,完全不可读。

格式绑定渲染环境。 HTML 文件离开浏览器什么都不是。.md 文件在任何编辑器、终端、代码审查工具里都能直接阅读。

用一个更重的格式来解决表达能力不足的问题,代价是真实的,方向是错的。


围栏代码块是唯一的扩展点

正确的答案已经被 Mermaid 验证过了。

Mermaid 的做法很简单:用围栏代码块,语言标识符告诉渲染器”这不是普通代码,这是一种声明式描述”。

Markdown
```mermaid
graph LR
    A[用户请求] --> B{认证}
    B -->|通过| C[处理逻辑]
    B -->|拒绝| D[返回 401]
    C --> E[返回结果]
```

这个模式可以推广到所有场景,而不只是图表。Mark.build 所做的,就是把这个模式系统化:任何内容类型都是一个代码块,语言标识符是唯一的区分点。

不需要新语法,不需要自定义组件系统,不需要插件注册机制。.md 文件还是 .md 文件,围栏代码块还是围栏代码块。变化的只是语言标识符的含义。


功能一览:目前已经实现的

以下是 Mark.build 当前实现的全部特殊代码块,每一个都可以在编辑器里直接试用。

Mermaid 图表

14 种图表类型,包括流程图、时序图、ER 图、甘特图、Git 历史、思维导图等。渲染结果支持平移缩放,可下载 SVG 和 PNG。

Markdown
```mermaid
sequenceDiagram
    participant U as 用户
    participant API as API 服务
    participant DB as 数据库
 
    U->>API: POST /auth/login
    API->>DB: 查询用户记录
    DB-->>API: 返回用户数据
    API-->>U: 200 OK {token}
```
Sequence

KaTeX 数学公式

行内公式:E=mc2E = mc^2,块级公式渲染为带卡片外壳的独立元素,支持缩放查看和 LaTeX 复制。

Math
ex2dx=π\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
Math
×B=μ0J+μ0ε0Et\nabla \times \mathbf{B} = \mu_0 \mathbf{J} + \mu_0 \varepsilon_0 \frac{\partial \mathbf{E}}{\partial t}

CSV 交互表格

CSV 代码块渲染为带搜索框和列排序的交互表格,支持按 CSV/JSON 导出,右键列头可按升降序排序。

CSV
姓名职位入职时间薪资
Alice Chen后端工程师2023-0332000
Bob Liu前端工程师2022-1128000
Carol Zhang产品经理2023-0735000
David Wu数据工程师2021-0640000
Eve Wang设计师2024-0126000

HTTP 请求文档

http 代码块把 HTTP 请求格式化为语义化卡片,方法、路径、头部、请求体各有独立样式,支持直接发送请求查看响应。

POST
POST /api/v1/documents HTTP/1.1
Host: api.mark.build
Authorization: Bearer sk-xxxxx
Content-Type: application/json

{
  "title": "API 集成指南",
  "content": "## 开始之前...",
  "theme": "tech-blog"
}

Terminal 命令块

bash/shell 代码块渲染为终端风格,提示符、命令、输出、错误各有颜色区分,一键复制命令行部分(不含输出)。

bash
$ pnpm add @mark-build/renderer
Packages: +47
Progress: resolved 89, reused 43, downloaded 4, added 47, done

✓ 安装完成
$ pnpm build
vite v5.4.19 building for production...
✓ 37 modules transformed.
dist/index.js    42.3 kB │ gzip: 14.1 kB
✓ Built in 1.24s

Diff/Patch 对比块

diff 代码块把增删行用绿/红背景高亮,适合展示代码变更或配置迁移。

Diff
- const config = {
-   theme: 'default',
-   syntaxHighlight: true,
- };
+ const config = defineConfig({
+   theme: 'tech-blog',
+   syntaxHighlight: false,  // 改用 rehype-pretty-code
+   markdown: {
+     remarkPlugins: [remarkMath],
+   },
+ });

Output / 程序输出块

output/stdout 代码块渲染为程序输出卡片,支持按级别过滤(error / warn / success / 普通输出)。

Output
5 行
[INFO]  服务启动,监听 :8080
[INFO]  数据库连接成功(pool: 10)
[WARN]  缓存未配置,使用内存模式
[ERROR] 邮件服务不可用:SMTP 连接超时
[INFO]  健康检查端点:/healthz

四种新类型:UI 布局类代码块

除了以上面向开发者的工具类代码块,Mark.build 还引入了四种 UI 布局类代码块,用于在文档里直接嵌入视觉组件,无需写任何 HTML。

Callout 提示框

六种类型:noteinfotipsuccesswarningdanger。每种类型对应不同的图标和配色。

Markdown
```warning
生产环境 API Key 请勿提交到公开代码仓库。一旦泄露,请立即吊销旧 Key。
```
 
```tip
开始前,先粘贴一段 Markdown 文本,然后切换主题查看排版变化。
```
 
```danger
删除账号操作不可撤销,所有文档与配置将被永久清除。
```

Warning

生产环境 API Key 请勿提交到公开代码仓库。一旦泄露,请立即在控制台吊销旧 Key 并生成新的。

Tip

开始前,先在编辑器里粘贴一段 Markdown 文本。如果你已有内容,直接切换主题就能看到排版效果的变化。

Danger

删除账号操作不可撤销。所有文档、主题配置和导出历史将被永久清除,且无法恢复。

Tabs 标签页

--- tab: Name --- 分隔不同面板,同一内容展示多语言实现时特别有用。

Markdown
```tabs
--- tab: TypeScript ---
async function fetchUsers(): Promise<User[]> {
  const res = await fetch('/api/users');
  return res.json();
}
--- tab: Python ---
def fetch_users():
    return httpx.get('/api/users').json()
--- tab: Go ---
func fetchUsers() ([]User, error) { ... }
```
async function fetchUsers(): Promise<User[]> {
  const res = await fetch('/api/users');
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

Tab 标签也支持文件名,自动从扩展名推断高亮语言:

Markdown
```tabs
--- tab: utils.ts ---
export function formatDate(d: Date): string {
  return d.toISOString().slice(0, 10);
}
--- tab: utils.py ---
from datetime import date
 
def format_date(d: date) -> str:
    return d.isoformat()
--- tab: utils.go ---
func formatDate(t time.Time) string {
    return t.Format("2006-01-02")
}
```
export function formatDate(d: Date): string {
  return d.toISOString().slice(0, 10);
}

Stats 数据指标

Pipe 分隔格式:标签 | 数值 [| 变化 [| 方向]]directionup / down 控制趋势箭头颜色,渲染为响应式指标卡片网格。

Markdown
```stats
月活用户 | 12,400 | +18% | up
付费用户 | 396   | +22% | up
周新增   | 2,840 | -3%  | down
付费转化率 | 3.2% | +0.4pp | up
```
Stats
月活用户12,400↑ +18%
付费用户396↑ +22%
周新增2,840↓ -3%
付费转化率3.2%↑ +0.4pp

Pricing 定价表

== 方案名 分段,features: / excluded: 各是逗号分隔列表,highlight: true 标记推荐方案(加边框 + “推荐”徽章)。

Markdown
```pricing
== Solo
price: 免费
features: 单用户, 所有主题, HTML 导出
excluded: 商业使用, 去水印, 云同步
 
== Pro
price: $12
period: /月
highlight: true
features: 无限连接, 去水印, 商业使用, 云同步
 
== Team
price: $29
period: /用户/月
features: 最多 20 用户, SSO, SLA 保障
```
Pricing
Solo
免费
  • 单用户
  • 5 个模型连接
  • 所有主题
  • HTML 导出
  • 商业使用
  • 去水印
  • 云同步
推荐
Pro
$12/月
  • 单用户
  • 无限模型连接
  • 所有主题
  • 去水印
  • 商业使用
  • 云同步
Team
$29/用户/月
  • 最多 20 用户
  • SSO
  • SLA 保障
  • 专属客服
  • 所有 Pro 功能

这是如何工作的

技术架构:unified + rehype 插件管道

整个渲染管道基于 unified 生态——这是 JavaScript 世界里最成熟的内容处理框架,Next.js、Astro、Docusaurus 都依赖它。

Markdown 文本
  → remark-parse(生成 MDAST 语法树)
  → remark-gfm / remark-math
  → remark-rehype(转换为 HAST HTML 树)
  → rehype-katex(渲染 KaTeX 公式)
  → rehype-mermaid-placeholder(Mermaid 转为客户端占位符)
  → rehype-terminal(拦截 bash/shell)
  → rehype-diff(拦截 diff/patch)
  → rehype-csv(拦截 csv/tsv)
  → rehype-http(拦截 http/rest/curl)
  → rehype-callout(拦截 note/warning/tip...)
  → rehype-tabs(拦截 tabs)
  → rehype-stats(拦截 stats)
  → rehype-pricing(拦截 pricing)
  → rehype-pretty-code(Shiki 语法高亮,处理剩余普通代码块)
  → rehype-code-shell(给代码块加外壳:标题栏、复制按钮)
  → rehype-stringify(输出 HTML 字符串)

每个自定义代码块类型对应一个 rehype 插件,插件在 Shiki 处理之前运行,把特定语言标识符的 <pre><code class="language-xxx"> 节点替换为结构化的 HTML 卡片。这条管道同时用于编辑器实时预览和博客页面静态构建。

为什么 YAML/DSL 比 HTML 更适合 AI 生成

有一个鲜为人知的数据:YAML 是 LLM 生成出错率最低的格式之一,而 HTML 标签嵌套层级深,在生成长 HTML 时容易出现标签漏闭合、属性值遗漏或缩进错乱。

让 Claude 生成一组定价卡片对比:

HTML 方案(200+ 行,容易出错):

Html
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; padding: 32px;">
  <div style="border: 1px solid #e5e7eb; border-radius: 12px; padding: 24px;">
    <h3 style="font-size: 20px; font-weight: 600; margin-bottom: 8px;">Solo</h3>
    <div style="font-size: 36px; font-weight: 700; margin-bottom: 16px;">免费</div>
    <ul style="list-style: none; padding: 0;">
      <li style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
        <span style="color: #16a34a;">✓</span> 单用户
      </li>
      <!-- 还有 N 行... -->
    </ul>
  </div>
  <!-- 另外两个方案... 还有 150 行 -->
</div>

代码块 DSL 方案(20 行,AI 生成稳定):

Markdown
```pricing
== Solo
price: 免费
features: 单用户, 5 个模型连接
excluded: 商业使用
 
== Pro
price: $12
period: /月
highlight: true
features: 单用户, 无限连接, 商业使用
 
== Team
price: $29
period: /用户/月
features: 20 用户, SSO, SLA
```

20 行 vs 200+ 行。token 成本约 1/10,生成可靠性显著更高,diff 可读,文件可在任意编辑器打开。

这也是为什么 Claude 生成 Mermaid 图表的质量普遍好于生成等效 HTML/SVG——道理完全相同:声明式结构命中了语言模型的优势区间。


Design Token:主题与内容解耦

另一个值得单独说的点:代码块渲染出来的效果完全受主题控制。

所有颜色、字体、圆角、边框都通过 CSS 变量(Design Token)注入,切换主题时,不只是正文样式变了,所有代码块外壳、表格、指标卡片、定价表格的颜色同步变化。

这使得同一份 Markdown 文件可以在不同的视觉风格下渲染,发布到不同平台(技术博客、学术论文、营销页面)时无需修改任何内容。


antirez 说的那个”更好的 Markdown”

antirez 的判断:不要从 Markdown 迁移到 HTML,而是扩展 Markdown 的表达能力。

Mermaid 已经验证了这条路是对的。从 mermaidchartcsvterminaldiffhttpcallouttabsstatspricing……每一个新的语言标识符,都是 Markdown 获得的一种新的表达能力。

文件还是 .md,语法还是代码块,渲染效果和精心设计的 HTML 没有差别——但 token 成本是 HTML 的 1/4 到 1/10,人可以直接编辑,git diff 是清晰的文字变化,文件在任何地方都能打开。

那位工程师发现了一个真实问题。antirez 指出了正确方向。Mark.build 在做的,就是那个方向上的具体实现。


想亲自试试? 打开编辑器,把任意 Markdown 粘贴进去 →