随着大语言模型的普及,流式输出(Streaming)已成为 对话式 AI 应用的标准交互模式:模型逐 Token 生成内容,前端实时增量渲染。此前,我们已面向 Web 端推出了流式友好的 Markdown 渲染器 @ant-design/x-markdown[1]。
然而,小程序端缺乏浏览器原生的 HTML 渲染能力,流式 Markdown 渲染会面临格式突变、频繁重解析、性能损耗等问题,我们对现有主流小程序 Markdown 渲染方案进行了研究,发现其在流式场景下存在以下核心局限。
|
|
|
|
|---|---|---|
|
|
rich-text
|
|
|
|
|
|
|
|
|
|
流式场景下,上述方案的短板集中在三个瓶颈——转换瓶颈、流式性能瓶颈、易用性瓶颈。下文的三个策略将与之一一对应,逐一拆解:
-
转换瓶颈——HTML 中间层的多余开销:现有方案普遍走 "Markdown → HTML → 再解析为节点树" 的链路,HTML 仅充当中间表示,却额外引入一轮解析开销;且 HTML 标签无法直接映射为小程序原生组件,限制了链接点击、图片预览、动画等能力,最终节点往往只是 rich-text 的只读快照。**** -
流式性能瓶颈——O(N²) 重复解析 + setData 全量传输:这是流式追加内容时叠加的两道性能损耗 -
计算侧(O(N²) 重复解析):每当 AI 追加一小段文本,现有方案将全文从 Markdown 重新解析至节点树。若 AI 回答产生 N 个增量片段,累计解析次数为 1+2+3+…+N,总工作量达到 O(N²)。 -
通信侧(setData 全量传输):小程序采用逻辑层与渲染层分离的架构,每次节点树更新需通过 setData将完整数据从逻辑层传输至渲染层。节点树越大,单次传输量越大,且传输本身占用主线程,进一步加剧卡顿。 -
易用性瓶颈——扩展能力固化,业务难以低成本接入:现有方案的语法与能力大多写死或强耦合——towxml 把公式、代码高亮内置进主包,无法裁剪也无法新增业务语法;mp-html 虽有插件机制,但作用于 HTML / 节点层、面向静态文档;wemark 则已停止维护。业务若想接入 LaTeX 公式、代码高亮,或把 <ant-button>、<ant-tag>这类自有业务组件写进正文,往往只能 fork 转换器,或退化为受限的 HTML 标签映射,成本高、易与主干冲突,且难以适配流式与多视图并发。****
前两道瓶颈在短文本下尚不明显,但在 AI 长回答(数千至数万字符)场景下会导致肉眼可见的渲染延迟和交互卡顿;而易用性瓶颈则直接决定了渲染器能否在真实业务中低成本落地。因此我们针对小程序重新设计了一套流式渲染方案:@ant-design/x-markdown-mini[5]。
npm i @ant-design/x-markdown-mini
整体架构
x-markdown-mini 的核心设计围绕一个原则展开:流式场景下,已完成的内容不应再承担任何额外开销。整体从上到下分为五层,另有一套扩展机制横向作用于解析与转换:
-
入口层: parse/render/renderNodes三个 API,默认复用共享实例,并发场景按视图各自new XMarkdownMini; -
流式引擎: StreamingProcessor负责稳定块缓存与结尾补全,仅流式路径启用; -
解析层:实例隔离的 marked.Lexer,从源文本直接产出语法树Token[],不经过 HTML 中间表示; -
转换层: renderTokensToMiniNodes是唯一的语义映射(无中间 IR),平台差异由 adapter 消化(微信a[data-href]、支付宝 https 图片 / 丢弃ol start等); -
渲染层: MiniNodeRenderer组件按node.name分发到小程序原生组件,支持微信 / 支付宝双平台; -
扩展机制: extensions注册自定义语法、components注册自定义标签白名单,均实例隔离、不污染全局。
下文的三个策略分别对应上述转换瓶颈、流式性能瓶颈、易用性瓶颈逐一拆解——策略一取消 HTML 中间层降低单次转换开销,策略二以稳定块缓存 + 增量传输同时解决 O(N²) 重复解析与 setData 全量传输,策略三以实例隔离的扩展机制打开低成本接入。
策略一:取消 HTML 中间层,直出小程序节点
现有渲染器普遍采用如下方案:
Markdown → HTML 字符串 → 再次解析为节点树 → 渲染
HTML 仅充当中间表示,却引入了额外的解析开销,并且限制了最终节点的表达能力——HTML 标签无法直接映射为小程序原生组件。
x-markdown-mini 去掉这层中转,直接从语法树拼装节点:
Markdown 字符串 → MiniNode[] → 渲染
如上图,Markdown 字符串经 marked.lexer 词法分析得到 Token[],再由 renderTokensToMiniNodes 结合平台 adapter 一步产出 MiniNode[] 节点树,最后交给 MiniNodeRenderer 按 node.name 分发到原生组件——全程不经过 HTML。去掉中间层带来两重收益:
-
性能:减少一轮字符串解析与转换,长文转换速度显著提升; -
能力:节点直接对应小程序原生组件( <text>、<view>、<image>等),链接可点击、图片可预览、可叠加动画——而非 rich-text 的只读快照。
策略二:稳定块缓存 + 增量补全 + 增量传输
这是流式场景下最核心的优化,同时解决计算侧(O(N²) 重复解析)与通信侧(setData 全量传输)两道性能损耗。
传统方案在每次追加内容后对全文重新解析,本质上假设"全文可能发生变化"。但在流式输出中,已完成的部分是语义稳定的——AI 不会回头修改已经输出的段落。
x-markdown-mini 基于这一观察,每来一段 chunk 就以"代码块外 + 空行收尾"为界扫描出稳定分界,把缓冲切成两段并行处理:
-
稳定块(Stable Block):分界之前、已完成的段落,命中缓存直接复用,不再重新解析; -
结尾 / 活跃块(Active Block):AI 仍在输出的尾部片段,可能存在未闭合的语法结构(如 **加粗缺少闭合**、代码块未关闭等)。对它做一次 remend 补全(自动闭合未完结的结构),再只重新解析这一小段结尾。
随后把缓存的稳定块节点与新生成的结尾节点合并,仅将增量 patch 通过 onPatch 回调传给渲染层,而非全量 setData 传输整棵节点树。
这一策略同时击破了流式性能瓶颈的两个子问题:
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
setData 传输完整节点树
|
onPatch
|
|
由于稳定块只解析一次、每次增量只补全并重解析尾部一小段,累计解析复杂度从 O(N²) 降到 O(N);同时 onPatch 只传递增量部分,通信开销不再随全文长度线性增长。
策略三:自定义语法/组件,直出节点
不同业务对 Markdown 的诉求不一:有的要 LaTeX 公式,有的要代码高亮、@提及,有的要在正文里塞 <ant-button>、<ant-tag> 这类业务组件。若渲染器把语法集写死,这些场景只能 fork 转换器——成本高,且容易和主干逻辑冲突。
x-markdown-mini 把扩展做成两个实例隔离的注入点,直接挂在解析 / 转换阶段,不污染全局 marked:
-
自定义语法插件( extensions):注册一个 tokenizer 识别新语法,并配一个miniRenderer(token, ctx)直接产出MiniNode。这里延续了策略一的原则——即便是用户扩展,也不产生 HTML,自定义语法同样一步直出小程序节点。内置的 LaTeX、代码高亮插件即以此实现(重依赖按需懒加载,不占主包)。
-
自定义组件映射( components):传入一个标签白名单,框架自动合成行内 tokenizer,<ant-button>X</ant-button>即被解析为名为ant-button的MiniNode,一行配置完成映射;同名用户扩展优先级高于内置合成。
两个注入点都随实例创建而生效、随实例销毁,天然支持并发多视图各自扩展,互不干扰。
易用性:扩展能力横向对比
|
|
|
|
|
x-markdown-mini |
|---|---|---|---|---|
|
|
|
|
|
extensions 注册 tokenizer,直出节点
|
|
|
|
|
|
|
|
|
|
|
|
|
<ant-button>)
|
|
|
|
components 白名单,一行映射为小程序原生组件
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
渲染效果展示
下面是 x-markdown-mini 在微信 / 支付宝真机上的实际渲染录屏,涵盖流式增量渲染、LaTeX 公式、代码高亮、自定义业务组件、图片预览 / 链接点击等效果,可直观感受"直出原生节点"带来的交互与动画能力。
实测:越长越快
实验条件:
-
测试对象:x-markdown-mini v1.0、towxml v3.x、mp-html v2.x -
测试环境:macOS 26.5.1 / Apple M3Pro,Node.js 20.x -
测试方法:同一份 Markdown 源文本,分短文(2.9 KB)、中等(11.6 KB)、长文(29 KB)三档;每档先预热 5 次消除 JIT 影响,再连续执行 20 次取均值;仅测量"Markdown → 可渲染节点树"的纯转换耗时(不含渲染与 setData 传输) -
复现方式:完整测试脚本与数据见:benchmark/experiments/2026-06-26-render-parity-results.md[6]
篇幅越长,领先越多:长文比 towxml 快 4.4 倍、比 mp-html 快 2.5 倍。
总结
@ant-design/x-markdown-mini 针对小程序流式 Markdown 渲染在转换、性能和扩展性上的三大瓶颈,提出一套流式友好的多端节点直出架构。通过取消 HTML 中间表示、稳定块缓存与尾部增量补全、插件化节点直出三项策略,实现高性能流式渲染与自定义语法扩展。框架支持微信、支付宝等小程序一套代码多端运行,基准测试下长文本转换性能较 towxml、mp-html 分别提升约 4.4× 和 2.5×。
源码与复现:github.com/ant-design/x-markdown-mini[7]
性能数据 benchmark/experiments/2026-06-26-render-parity-results.md[8]
参考资料
@ant-design/x-markdown: https://x.ant.design/x-markdowns/introduce-cn
[2]支付宝文档中心: https://opendocs.alipay.com/mini/component/rich-text
[3]towxml: https://github.com/sbfkcel/towxml
[4]wemark: https://github.com/TooBug/wemark
[5]@ant-design/x-markdown-mini: https://x-markdown-mini.ant.design/
[6]完整测试脚本与数据: https://github.com/ant-design/x-markdown-mini/blob/main/benchmark/experiments/2026-06-26-render-parity-results.md
[7]源码与复现: https://github.com/ant-design/x-markdown-mini
[8]性能数据: https://github.com/ant-design/x-markdown-mini/blob/main/benchmark/experiments/2026-06-26-render-parity-results.md

