ARTICLE · 1214738
每周被下载 8166 万次的 Markdown 翻译官:marked 凭什么火了 16 年
QUOTE
浏览器只认识 HTML,总得有个东西把 Markdown 翻译成网页——这个翻译官,很可能是 marked。
—— 蜗牛
你写完一篇 Markdown,点了保存,网页上立刻出现排版好的文章。中间发生了什么?
浏览器只认识 HTML,不认识 # 标题和 **加粗**。总得有个东西把 Markdown “翻译”成 HTML——这个翻译官,很可能是 marked。
它不是今年冒出来的新玩具:项目从 2011 年就在写了,到现在 16 年,GitHub 上 3.7 万+ Star;npm 官方数据显示,过去一周它被下载了 8166 万次——这个数字,比很多爆款前端框架还夸张。
今天我们不看官方那句“为速度而生”的广告词,用数据和代码,把这个 16 岁的老库拆开看一遍:它靠什么活到今天、内部怎么工作、怎么上手、和同类库比该怎么选,以及一个很多人踩过的安全坑。
本文看点
01
数字画像:8166 万周下载背后的六个数字
02
内部流水线:Lexer 到 Token 树再到 HTML
03
安全红线:官方不消毒,XSS 坑在哪
01
NUMBERS
先看数字画像:一个“老”得离谱的库
先看仓库实拍(下图就是 github.com/markedjs/marked首页):37.2k Star、3.8k Fork、225 个 Tag、3881 次提交——一个 16 岁的项目,维护节奏到现在还是每周都有提交。

— marked GitHub 仓库页(github.com/markedjs/marked)
再把 npm 官方数据摆出来(2026 年 10 月抓取):
| 约 8166 万次 | |
| 3.7 万+ | |
| 0 | |

— marked 数字画像示意(数据:npm / GitHub,2026-10)
“0 依赖”在 npm 生态里是稀有物种。装一个包拖进来几百个依赖是常态,而 marked 一个依赖都没有——体积小、安装快、不污染你的依赖树。这对体量敏感的前端项目,本身就是卖点。
8166 万次/周是什么概念?同赛道的 markdown-it 周下载约 2100 万次,marked 差不多是它的4 倍。博客、文档站、在线编辑器、Markdown 预览插件……大量你叫不出名字的产品,背后都在用同一个翻译官。
02
WHY ALIVE
它凭什么活了 16 年
开源项目死掉的原因通常是同一个:没人维护了。marked 能活 16 年,我把它归结为三个“没变”:
没变的是定位:只做一件事,且做到底
仓库简介一句话:A markdown parser and compiler. Built for speed.——只把 Markdown 变成 HTML,不为别的分心。它是低层编译器(low-level compiler),不做缓存、不长时间阻塞主线程,渲染完就走。
没变的是对规范的较真
官方文档给出了合规测试数据:Markdown 1.0 通过率 100%,CommonMark 0.31 通过率 100%(仅 Links 一项 99%),GFM(GitHub 风格)0.29 通过率 99%,其中表格、任务列表、删除线这三个 GitHub 常用扩展全部 100%。你平时写的语法,它基本都接得住。
没变的是“吃自己的狗粮”
下图是 marked 的官方文档首页(marked.js.org)——这个文档站本身就是用 marked 渲染的。文档团队天天用自己写的解析器,这比任何宣传都有说服力。类似的还有 zero-md、TeXMe、StrapDown.js、Homebrewery 等一批工具都建立在它之上——生态粘性就是这么攒下来的。

— marked 官方文档首页(文档站自身用 marked 渲染)
顺带一个冷知识:官方在首页很诚实地标注了一句——仍在完善性能对比基准的度量。也就是说,“最快”至今没有官方基准背书。网上那些“速度极快”的说法,更多来自社区口碑和它的轻量设计。
03
PIPELINE
内部流水线:它到底怎么把 Markdown 变成 HTML
你不用把源码读一遍,记住这条流水线就够了:

— marked 渲染流水线示意
输入 → Lexer(词法分析)→ Token 树 → Parser(语法分析)→ HTML
Lexer:把 Markdown 字符串按规则切成一块块 token,组成一棵嵌套的 token 树——# 标题 变成 {type: "heading", depth: 1},列表、代码块、引用同理;
walkTokens(可选):按需对每个 token 做后处理;
Parser:遍历 token 树,把每个 token 喂给对应的 renderer,拼出 HTML 字符串。
关键在设计:中间那层 token 树是“可插拔”的。你想改标题的输出样式,不用重写整个解析器,override 一个 renderer 就行:
import { marked } from 'marked';
const renderer = {
heading({ tokens, depth }) {
if (depth === 1) {
return `<h1>${this.parser.parseInline(tokens)}</h1>`;
}
return false; // 其他级别走默认逻辑
}
};
marked.use({ renderer });
console.log(marked.parse('# 我的博客标题'));
// <h1>我的博客标题</h1>
想加一种全新的语法,就注册一个自定义扩展(tokenizer + renderer 成对出现);想在解析前后插手,还有 hooks(preprocess / postprocess);想要异步处理,打开 async 模式即可。官方还提供了扩展模板仓库,直接起新项目。
一句话总结它的设计哲学:解析与渲染分离,扩展点开放,核心保持轻量。这也是它能被塞进各种千奇百怪场景还不崩的原因。
04
QUICK START
五分钟上手:从安装到第一次渲染
前面讲了半天“是什么”,现在讲“怎么用”——其实简单到超出你的预期。
第一步,安装(Node 项目里)
npm install marked
第二步,三行代码跑起来
import { marked } from 'marked';
const html = marked.parse('# 你好,marked\n\n这是**加粗**的测试。');
console.log(html);
// <h1>你好,marked</h1>
// <p>这是<strong>加粗</strong>的测试。</p>
浏览器里直接用 CDN
想做个本地 Markdown 预览页,这是最快的方式:
<script src="https://cdn.jsdelivr.net/npm/marked/lib/marked.umd.js"></script>
<script>
document.getElementById('content').innerHTML =
marked.parse('# 在浏览器里也能渲染');
</script>
命令行一键转换
装全局命令后,文件进、HTML 出:
npm install -g marked
marked -i readme.md -o readme.html
常用配置
用 marked.use() 全局设置,不需要每次都传参数:
import { marked } from 'marked';
marked.use({
gfm: true, // GitHub 风格,默认就是 true
breaks: true, // 单个换行也变成 <br>,更像聊天框
async: false // 开启后 parse 返回 Promise
});
const html = marked.parse('第一行\n第二行');
只想解析一段内联内容
不想包一层 <p> 标签,用 parseInline:
marked.parse('**加粗** _斜体_');
// <p><strong>加粗</strong> <em>斜体</em></p>
marked.parseInline('**加粗** _斜体_');
// <strong>加粗</strong> <em>斜体</em>
一句话总结上手成本:装一个包,调一个函数,完事。剩下所有进阶玩法(自定义渲染、扩展语法、异步处理)都在 marked.use() 这一个入口里做文章——这也是它 16 年没被淘汰的设计红利。
05
COMPARISON
选型对照:marked、markdown-it、remark,到底用谁
JavaScript 生态里绕不开四兄弟:marked、markdown-it、remark(unified 生态)、showdown。很多人纠结怎么选,一张表说清楚:
| AST 树 | ||||
选型就三句话:
渲染、预览、转换
默认 marked。快、零依赖、够用。
要严格规范 + 大量现成插件
脚注、数学公式、自定义容器——选 markdown-it。
要对内容“动手术”
自动生成目录、检查格式、把 Markdown 转成别的格式——上 remark/unified,它的 AST 让你能编程式地处理文档。
showdown
只在老项目里见到,新项目没必要从它开始。
06
SECURITY
安全红线:marked 不管输出,坑在这里
这是参考文章绝不会告诉你的部分,但官网首页写得明明白白:
“Marked does not sanitize the output HTML.”
如果你处理的是不可信输入,必须对输出的 HTML 做过滤(sanitize),否则有 XSS 风险。
更值得玩味的是:marked 以前是有内置 sanitize 选项的,在 v8.0.0 里主动删掉了(官方 Options 文档明确写着 “Removed in v8.0.0, use a sanitize library like DOMPurify”)。为什么删?因为官方认为“消毒”和“翻译”是两件事——解析器做好解析,安全交给专业的消毒库,职责分离。
什么意思?marked 的哲学是“我只负责翻译,不负责消毒”。你给它 )或一段带 <script>的文本,它原样输出。如果你把用户输入直接喂给 marked 再塞进页面,就存在被注入脚本的风险。
正确姿势是组合使用:marked 负责翻译,DOMPurify 负责消毒:
import DOMPurify from 'dompurify';
const html = DOMPurify.sanitize(
marked.parse(userInput)
);
更优雅的玩法是用 marked 的 hooks,把消毒直接挂进渲染管线,一劳永逸:
import { marked } from 'marked';
import DOMPurify from 'isomorphic-dompurify';
marked.use({
hooks: {
postprocess(html) {
return DOMPurify.sanitize(html);
}
}
});
再补一个进阶提醒:如果处理的内容可能被恶意构造(防止 ReDoS 正则攻击),官方还建议把解析放到 worker 线程 / Web Worker里跑,超时直接终止进程,别让一个恶意字符串卡死你的主线程。
判断标准:只有你自己写、或内部可信的内容,才可以直接渲染;任何来自用户的内容,必须过一道 sanitize。
07
CHECKLIST
判断框架:你的项目该不该用 marked
最后给个能直接用的清单:
该用 marked 的场景
博客 / 文档站渲染 Markdown(八成场景就是它);
编辑器里的实时预览(快,不卡主线程);
命令行批量转换(marked -i readme.md -o readme.html);
项目对体积敏感、不想被依赖树污染。
别用 marked 的场景
需要对内容做结构化加工(自动目录、代码统计、格式 lint)——上 remark;
需要大量开箱即用的语法插件——上 markdown-it;
处理用户输入却不想配 sanitize——这不是 marked 的问题,是没读官网的问题。
回到开头那个问题:你写的 Markdown 是怎么变成网页的?大概率是一个 2011 年出生、每周被下载 8166 万次、却只有几 KB、零依赖的老库完成的。
它没有炫酷的发布会,也没有天天刷屏的更新日志,但 16 年如一日地干好“翻译”这一件事——这种项目,反而是开源世界里最稀缺的那类。
我是蜗牛,9 年 Java 开发,现在全职做独立开发,天天和开源工具打交道。这个栏目每周拆解一个值得关注的开源项目。
如果你觉得今天这篇有收获,欢迎点赞、在看、转发三连;也欢迎在评论区告诉我,你想让我拆哪个开源项目。















































































