blackfriday 已归档弃用,推荐使用 goldmark:API 清晰、安全默认、兼容 CommonMark;需谨慎配置 WithUnsafe(),自定义渲染应实现 NodeRenderer 接口,AST 可缓存但须深拷贝。

blackfriday 已被弃用,别再新项目里用了
Go 生态里最常被搜到的 Markdown 解析库 blackfriday 在 2021 年就正式归档(archived),不再接受任何更新或安全修复。它底层用 C 风格指针操作和不带 context 的同步解析,在现代 Go 项目里容易引发 panic、内存越界或 goroutine 泄漏——尤其当你传入恶意构造的长嵌套列表或超长 inline HTML 时。
替代方案很明确:goldmark 是当前官方推荐、社区维护最活跃的库,API 清晰、扩展性强、默认安全(自动转义 HTML、限制递归深度),且完全兼容 CommonMark 规范。
用 goldmark 把字符串转成安全 HTML
最基础的转换只需三行代码,但关键在配置细节。默认行为会把原始 Markdown 中的 <script> 或 <iframe> 当作纯文本输出,不会执行;但如果你开了 WithUnsafe(),又没配白名单,就等于打开 XSS 入口。
-
goldmark.New()默认禁用所有 HTML 标签,连<em>和<strong>都会被转义——这不是 bug,是设计 - 要允许常见内联 HTML(如
<img>、<a>),必须显式调用parser.WithHTML()和renderer.WithUnsafe() - 若需进一步限制,比如只放行
<a href="https://...">,得自己写parser.ASTTransformer拦截节点,而不是依赖正则替换
md := goldmark.New(
goldmark.WithExtensions(extension.GFM),
goldmark.WithParserOptions(
parser.WithInlineParsers(inlineParsers...),
),
goldmark.WithRendererOptions(
renderer.WithUnsafe(), // 注意:仅当确认输入可信时才开
),
)
自定义渲染器加 class 或 data 属性
静态博客或 CMS 前端常需要给标题加锚点、给代码块加语言标识、给图片加懒加载属性——这些不能靠 post-process 字符串,得在 AST 渲染阶段注入。
立即学习“go语言免费学习笔记(深入)”;
goldmark 提供了 renderer.NodeRenderer 接口,你可以只重写某类节点(比如 *ast.Heading)的渲染逻辑,其余保持默认。重点是别直接改 node 结构体字段,而要用 ast.Walk 遍历后返回新节点。
- 给所有
<h2>加id="xxx":实现RenderHeading方法,调用textutil.Slugify生成 ID - 让代码块自动带
data-language="go":检查node的Info字段,提取语言名后写进ast.AppendChild的 attr map - 错误示范:在
RenderText里做全局字符串替换——会破坏已转义的实体(如把&又变成&)
性能敏感场景下怎么避免重复解析
如果同一份 Markdown 内容要反复渲染(比如模板中多次引用同一文档),别每次调用 Convert 都走完整解析流程。AST 是可复用的,但要注意生命周期。
- 解析结果
ast.Node不是线程安全的,goroutine 并发渲染前必须深拷贝(用ast.Copy) - 缓存建议按原始内容哈希(如
sha256.Sum256)为 key,value 存ast.Node+ 渲染器配置指纹 - 如果只是微调样式(比如换字体大小),不要重新解析,直接用
renderer.Render传入已有 AST 和新配置 - 注意:
extension.GFM里的表格、脚注等扩展会显著增加解析耗时,非必要不开
真正难的不是写对第一版,而是当需求从“显示文档”变成“支持用户实时编辑+预览+导出 PDF”时,AST 节点怎么跨渲染器复用、怎么跟前端 sync、怎么处理增量更新——这些不在 goldmark 范围内,得自己搭胶水层。











