Markdown 引擎对比:marked / markdown-it / remark 怎么选
Markdown 引擎对比:marked / markdown-it / remark 怎么选
JavaScript / Node.js 生态里有三个主流的 Markdown 引擎,下载量都是百万级:
- marked — 2011 年问世,最早的纯 JS Markdown 解析器之一
- markdown-it — 2014 年,强调 100% CommonMark 兼容 + 插件系统
- remark — 2014 年,unified 生态的核心,AST-first
它们都能把 # Hello 转成 <h1>Hello</h1>,但在性能、可定制性、错误处理上差异巨大。我们的 Markdown 转 HTML 工具 选用了 marked + DOMPurify 的组合——下文讲讲为什么。
三种引擎的世界观
marked — 字符串到字符串
import { marked } from "marked";
const html = marked.parse("# Hello\n\nWorld");
// '<h1>Hello</h1>\n<p>World</p>\n'
API 简单到极致:进 Markdown 字符串,出 HTML 字符串。中间没有 AST、没有 token、没有 visitor pattern。需要扩展时用 marked.use({ renderer }) 重写 HTML 输出函数:
marked.use({
renderer: {
heading(text, level) {
const id = text.toLowerCase().replace(/\s+/g, "-");
return `<h${level} id="${id}">${text}</h${level}>`;
},
},
});
markdown-it — 字符串 → token 流 → 字符串
import MarkdownIt from "markdown-it";
const md = new MarkdownIt();
// 一步到位
const html = md.render("# Hello");
// 或者拿到中间 token 流自己玩
const tokens = md.parse("# Hello", {});
// [{ type: 'heading_open', tag: 'h1', ... },
// { type: 'inline', children: [{ content: 'Hello' }] },
// { type: 'heading_close', tag: 'h1', ... }]
token 流是扁平数组(不是树)。插件通过 md.use(plugin) 注册,可以修改 token 流或注入新的 render 规则。
remark — 字符串 → AST → AST → 字符串
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
const file = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify)
.process("# Hello");
const html = String(file);
remark 把 Markdown 解析成 MDAST(Markdown AST),可以经过任意多个 transformer 处理,最后转成 HAST(HTML AST)再 stringify。这是最 functional 的设计,但也最 verbose——简单 case 要写 4 个 import。
性能对比
实测把一篇 50KB 的 Markdown 文档(约 1 万词)转 HTML,三个引擎的耗时(M1 MacBook,Node 22):
| 引擎 | 单次耗时 | 内存峰值 |
|---|---|---|
| marked | 4ms | 6MB |
| markdown-it | 12ms | 9MB |
| remark(最小配置) | 38ms | 24MB |
| remark + GFM + smartypants | 65ms | 35MB |
marked 是最快的,差距能到 10 倍。原因是它跳过了中间 AST 表示——边解析边输出字符串。这也是它扩展性最差的根本:没有 AST 就没法做"路过的处理器",所有定制都要 hook 进 render 函数。
对静态生成(SSG)来说 10 倍差距不要紧——一个站点几十篇文章总耗时也就几秒。但如果你做的是用户实时输入预览(笔记应用、Notion 类工具),4ms vs 65ms 是肉眼可感的:
- 4ms:每次按键都能刷新预览,零延迟感
- 65ms:用户连续打字时会感到 UI 卡顿(特别是 Markdown 文档变长时)
我们的工具页是后者场景——用户左侧改 Markdown 右侧实时渲染,所以 marked 是更稳的选择。
CommonMark 兼容性
CommonMark 是 2014 年提出的 Markdown 形式化规范——核心目的是消除"同一段 Markdown 在不同引擎下输出不同"的混乱。
| 引擎 | CommonMark 兼容性 | GFM 支持 |
|---|---|---|
| marked | ~98% | 内置 |
| markdown-it | 100%(创立目的) | 插件 markdown-it-gfm-alerts 等 |
| remark | 100% | remark-gfm 插件 |
marked 的 2% 偏差大部分是边缘 case(嵌套列表的 lazy continuation、HTML block 边界判定等),日常使用基本碰不到。如果你的产品要导入用户从 GitHub / GitLab 复制过来的 Markdown,markdown-it 或 remark 的 100% 兼容更安全。
GFM(GitHub Flavored Markdown)扩展包括:
- 表格
- 任务列表
- [x] - 删除线
~~text~~ - URL 自动识别
- 围栏代码块的语言标识
marked 内置全部 GFM,开箱即用;markdown-it / remark 需要单独装插件。
安全性:HTML 注入与 XSS
这是 Markdown 引擎选型最容易被忽视、但最严重的问题。
Markdown 规范允许内联 HTML:
段落里有一段 <strong>HTML</strong>。
<script>alert('XSS')</script>
如果你直接 dangerouslySetInnerHTML={{ __html: marked.parse(userInput) }},那段 script 标签就执行了。
三个引擎的默认行为:
- marked:直接输出 HTML(包括
<script>、<iframe>、<img onerror>) - markdown-it:构造函数有
html: false选项,默认 不解析 内联 HTML(把<转义为<) - remark:默认 不解析 内联 HTML(行为最保守)
markdown-it 和 remark 的默认安全是优势,但仍然不够——它们对 Markdown 自带的语法没有过滤能力:
[点我](javascript:alert('XSS'))
这个链接在所有三个引擎里默认都会生成 <a href="javascript:...">。Chrome 等现代浏览器拦截 javascript: 协议,但旧浏览器和某些 webview 不拦。
结论:任何处理用户输入的 Markdown 引擎都必须配 sanitizer。最常见的搭配:
import { marked } from "marked";
import DOMPurify from "dompurify";
const html = DOMPurify.sanitize(marked.parse(userInput));
DOMPurify 是事实标准——OWASP 推荐,jsdom 兼容(可在 SSR 用),白名单可定制。我们的工具页就是这个组合:marked 出原始 HTML,DOMPurify 清洗,再渲染。
remark 生态有自己的 sanitizer(rehype-sanitize),思路类似但 API 更 verbose。markdown-it 没有官方 sanitizer,社区方案是用 markdown-it 自己的 validateLink hook 拦协议白名单——但功能不如 DOMPurify 完整。
扩展性对比
要给 Markdown 加自定义语法(如 :::warning 警告框、@mention、[[wikilink]]),三个引擎的难度差异:
| 任务 | marked | markdown-it | remark |
|---|---|---|---|
| 改 heading 渲染加 anchor | ⭐ 重写 renderer.heading | ⭐⭐ 加 plugin 改 token | ⭐⭐⭐ 写 visitor + transformer |
| 加新 inline 语法(@mention) | ⭐⭐ 用 extensions API | ⭐⭐ 标准 inline rule API | ⭐⭐ remark 自定义 micromark extension |
| 加新 block 语法(::: 块) | ⭐⭐⭐ 复杂 | ⭐⭐ markdown-it-container 插件 | ⭐⭐ remark-directive 插件 |
| AST 后处理(如自动加 footnote) | ❌ 不支持 | 🟡 token 数组操作 | ✅ AST visitor 天生合适 |
简单替换 = marked 最快;复杂语法扩展 + 多步处理 = remark 最干净;中间地带 = markdown-it 最 productive。
生态系统
社区周边工具的丰富度:
remark / unified 生态最大:
- MDX(Markdown + JSX)建立在 remark 之上
- 静态站点生成器 Gatsby / Astro / Docusaurus 默认 remark
- VS Code Markdown lint、Prettier Markdown 都用 remark
- 1000+ 插件覆盖各种场景
markdown-it 生态成熟稳定:
- Vue / Vite 默认 Markdown 处理用 markdown-it
- VuePress、VitePress、Nuxt Content 都基于 markdown-it
- 插件不如 remark 多,但常用的都有
marked 生态小但够用:
- highlight.js / prism.js 代码高亮容易接
- 没有大型框架默认用 marked(因为可扩展性弱)
- 多用于"嵌入式 Markdown 渲染"场景
选型决策树
用 marked 当你:
- 性能是第一优先(实时预览、移动端)
- 需求简单:渲染 + 代码高亮 + GFM
- 配合 DOMPurify 处理安全
- 不需要做语法扩展或 AST 后处理
用 markdown-it 当你:
- 用 Vue / Vite / VuePress / VitePress 生态
- 需要修改部分语法行为(自定义容器、链接处理)
- 性能要够好(中等档位)
- token 流操作能满足扩展需求
用 remark / unified 当你:
- 用 Next.js / MDX / Astro / Gatsby
- 需要多步骤 AST 处理(如 TOC 生成 + footnote 收集 + smartypants 替换)
- 团队习惯 functional + 类型系统(remark 有完整 TS 类型)
- 性能不是瓶颈(静态构建场景)
一些反直觉的建议
-
不要在生产环境用
marked.parse(input, { sanitize: true })——marked 4.0 已经移除内置 sanitize,那是个已知缺陷的实现。永远用 DOMPurify 外部处理。 -
github-markdown-css 是免费午餐——三个引擎的输出 HTML 都能用 github-markdown-css 直接套样式,省去自己写 CSS。
-
代码高亮单独选——marked / markdown-it / remark 自己都不带代码高亮。常见组合:highlight.js(更小、自动识别语言)或 prism.js(功能多、token-level 控制)。Shiki 是新选择,体验最好但首次加载需下载 TextMate grammar(~500KB)。
-
MDX ≠ Markdown——如果你想在 Markdown 里直接写 React 组件(
<MyChart data={...} />),那是 MDX,必须用 remark + @mdx-js。这是 marked / markdown-it 都做不到的领域。
总结
| 维度 | marked | markdown-it | remark |
|---|---|---|---|
| 速度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| 体积 | ~50KB | ~100KB | ~150KB+ |
| 可扩展性 | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 生态 | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 上手难度 | ⭐ 极简 | ⭐⭐ 中等 | ⭐⭐⭐⭐ verbose |
| CommonMark | 98% | 100% | 100% |
没有 silver bullet——选型本质是 "速度 vs 表达力" 的取舍。marked 是"够用就好"的选择,remark 是"我要绝对掌控"的选择,markdown-it 是务实中间路线。
延伸阅读
- Markdown 转 HTML 工具:基于 marked + DOMPurify 的实战实现
- CommonMark 规范:所有引擎应该兼容的基础规范
- unified 生态总览:remark / rehype / retext 一站式文档