选 goldmark,因其活跃维护、支持 commonmark 1.0 及扩展语法(如表格、脚注),而 blackfriday 已归档且不支持现代语法;需显式启用扩展、组合 bluemonday 防 xss,并通过 ast 遍历或自定义渲染器增强代码块,同时务必缓存 html 结果以避免重复解析开销。

用 blackfriday 还是 goldmark?选错库会卡在扩展语法上
Go 生态里主流 Markdown 解析器就两个:已归档但广泛使用的 blackfriday,以及当前官方推荐、可插拔的 goldmark。别用 blackfriday——它不维护了,不支持 CommonMark 1.0,遇到表格、任务列表、脚注等现代语法直接忽略或 panic。项目刚起步就选 goldmark,哪怕它 API 看起来“重一点”,也比后期重构强。
常见错误现象:goldmark 默认不启用表格、脚注等扩展,代码块渲染成纯文本;blackfriday 遇到 ~~strikethrough~~ 直接原样输出。
-
goldmark是唯一支持自定义 AST 节点、HTML 渲染器替换、语法扩展(如 Mermaid 图)的活跃库 - 初始化时必须显式添加扩展:
goldmark.WithExtensions(goldmark.ExtensionTable, goldmark.ExtensionFootnote) - 它的
Parse()返回ast.Node,不是字符串,想拿 HTML 得走renderer.Render()流程,跳过这步会返回空
如何安全地把用户输入的 Markdown 渲染成 HTML,同时防 XSS
直接调用 goldmark 的默认 HTML 渲染器等于裸奔:它默认允许 <script></script>、onerror、javascript: href 等危险内容。不能靠“用户不会写坏 Markdown”来赌。
正确做法是组合 bluemonday 做二次过滤,而不是自己写正则删标签——HTML 结构嵌套复杂,正则根本不可靠。
- 先用
goldmark渲染出 HTML 字符串(注意传入html.WithUnsafe()才允许原始 HTML 标签通过,否则会被转义) - 再用
bluemonday.UGCPolicy().SanitizeBytes()过滤,它内置放行<p></p> <ul> <li><code><pre class="brush:php;toolbar:false;"><code></code> 等常见 Markdown 输出标签,自动剥离 script/style/onxxx 属性</pre> - 不要用
html.EscapeString()全局转义——那会让所有<code>变成文字,失去语法高亮能力
怎么让代码块支持语言标识和行号?别硬改渲染器
goldmark 自带代码块解析,但默认不识别 ```go 中的语言名,也不生成行号 DOM 结构。强行 patch 渲染器容易崩在 AST 遍历逻辑里。
标准解法是:用 goldmark 的 ast.Walk 遍历节点,在 *ast.FencedCodeBlock 上提取 Info 字段(即语言标识),然后注入自定义属性到 HTML 渲染结果中。
- 注册自定义 HTML 渲染器:实现
html.NodeRenderer接口,只重写RenderFencedCodeBlock方法 - 从
node.Info提取语言(如node.Info.Text()),加到<code class="go">里 - 行号用 CSS 实现更稳:
pre { counter-reset: line } code::before { counter-increment: line; content: counter(line) },避免 JS 注入 DOM 导致 SSR 不一致
性能瓶颈常卡在重复解析上,不是语法本身
Markdown 解析本身很快,但如果你每次 HTTP 请求都调用 goldmark.Parse() + renderer.Render(),GC 压力会明显上升——AST 节点分配频繁,且中间 HTML 字符串反复创建销毁。
真实项目里,90% 的 Markdown 内容是静态或低频更新的(比如文档页、博客正文)。缓存才是关键。
- 对确定内容(如文件路径或 ID)做 LRU 缓存:key 是原文 MD hash(
sha256.Sum256),value 是渲染后的 HTML[]byte - 别缓存
ast.Node——它含指针,序列化/反序列化开销大,且不同渲染器参数下结果不一致 - 如果内容含变量(如 {{.Title}}),先模板渲染再进 Markdown 流程,不要在 Markdown 解析器里做模板执行
扩展语法支持、XSS 过滤、代码块增强这些都能配,但最容易被忽略的是:没做缓存时,同一片文档每秒被解析上百次,CPU 消耗全花在内存分配上了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











