序
想象一个这样的场景, 你的 AI 应用通过知识库查询,为你返回了几十条 Table 数据。产品希望用户能够对 Table 数据执行分页、筛选等操作。
你很容易想到使用你熟知的 UI 组件库, 来渲染 AI 返回的 Table 数据。效果看起来就像这样:

这个库的地址在 github.com/EralChen/vu... .
不过,你也不需要非常着急的使用它,在这篇文章中我将向你揭露一些技术细节,以便你来应对,像 ECharts 图表渲染 、流程图 、大纲 、地图 等一切业务中可能存在的需求。认真看完,希望你会有所收获.
简单实现存在的问题
众所周知, AI 倾向于使用 Markdown 文本数据流向客户端传递信息。 一个简单的渲染,就像:
html
<script setup>
// ... 省略其他配置;
const md = MarkdownIt({
highlight,
})
const htmlText = computedAsync(async () => {
return md.render(props.source)
}, '')
</script>
<template>
<div v-html="htmlText"></div>
</template>
<!-- 使用 -->
<MarkdownRender :source="source"></MarkdownRender>
v-html
是一个全量渲染的过程, 并且他仅仅渲染 html
字符串。
这意味着,你不再享有 Vue DOM 更新时的 diff 算法优化, 同时也失去了使用 Vue 组件的权力。
你所期望的调用方式
以替换 Table 这个需求为例,你可以想象最直观的调用方式,就像:
html
<!-- 这是一个简单用例, 实际实现考虑到扩展性, 会略有不同 -->
<MarkdownRender :source="source">
<template #table="{ data, columns }">
<MyTables :data="data" :columns="columns" />
</template>
</MarkdownRender>
如果将数据收集到对应的插槽的参数中,那么我们就能轻松使用任意组件库,来替换原有标签.
解析
无论你使用何种 Markdown 工具 (markdown-it、marked、remark), 要想在渲染之前提取数据, 必然绕不开对 markdown 原文本的解析.
这个过程需要封装在 MarkdownRender
中, 不被使用端感知.
这里仅以 markdown-it 为例, 你可以在 markdown-it demo 查看解析结果

要想让 markdown 解析成 Vue 组件关键是构建嵌套树状结构, 与 vDOM 结构对应
- Markdown 文本 → markdown-it tokens(扁平数组)
- markdown-it tokens → 嵌套树状结构
- 树状结构 → Vue 组件渲染(由渲染器处理)
ts
// setup
const renderItems = computed(() => tokensToTree(
md.parse(props.source, {})
))
// template
<VkRenderer :source="renderItems">
<slot />
</VkRenderer>
一个完整的解析后的数据结构示例 -- h1 标签结构, 如下:
json
{
"templateType": "GroupToken",
"tag": "h1",
"open": {}, // Markdown Tokon 省略
"close": {}, // Markdown Tokon 省略
"children": [
{
"type": "inline",
"tag": "",
"attrs": null,
"map": [
0,
1
],
"nesting": 0,
"level": 1,
"children": [
{
"type": "text",
"tag": "",
"attrs": null,
"map": null,
"nesting": 0,
"level": 0,
"children": null,
"content": "标题一级",
"markup": "",
"info": "",
"meta": null,
"block": false,
"hidden": false,
"templateType": "text"
}
],
"content": "标题一级",
"markup": "",
"info": "",
"meta": null,
"block": true,
"hidden": false,
"templateType": "inline"
}
]
}
而我们唯一要做的, 就是将解析后的结果交给用户, 让用户自定义渲染采用的组件.
我想这就是全部! 是我们最终的目标!
策略渲染
如果你认真阅读, 解析章节, 那么你将发现
ts
const renderItems = computed(() => tokensToTree( md.parse(props.source, {}) ))
// template
<VkRenderer :source="renderItems"> <slot /> </VkRenderer>
template
中的 VkRenderer
接收的source
正是解析后的树状结构.
再回看解析后的 json
数据, 解析的过程中, 每个 item 都被添加了 templateType
字段.
如果有一个组件能够匹配 templateType
字段, 来渲染内容. 那么我们将轻松
- 替换默认渲染:为任何 Markdown 元素自定义渲染逻辑
- 增强交互性:将静态内容转换为交互式组件
- 集成第三方库:集成语法高亮、图表等功能
- 保持响应式:利用 Vue 的响应式系统实现动态更新
而你只需要将策略写在组件的默认插槽内, 使用起来就像:
html
<script lang="ts" setup>
import { VkMarkdown, VkRendererTemplate } from '@vunk/markdown'
import { computed, ref } from 'vue'
const source = `# Hello, Markdown!
This is a simple example of using **Markdown** in a Vue component.
`
</script>
<template>
<VkMarkdown
:source="source"
>
<VkRendererTemplate type="text">
<template #default={ raw }>
{{ raw.content }}
</template>
</VkRendererTemplate>
<VkRendererTemplate type="GroupToken">
<!-- 省略实现 -->
</VkRendererTemplate>
<!-- 省略 inline 实现 -->
</VkMarkdown>
</template>
组件库所做的事
很感谢你能看到这里, 如你所见 解析 和 策略渲染 就是 vunk-markdown 的核心.
对于常见的渲染策略, 组件库中做了封装
TemplatesDefault 用于渲染常规 Markdown 内容
TemplateEcharts 用于 Echarts 图表渲染
TemplateMermaid 用于 mermaid 流程图渲染


同时你可以, 在 example 中看到更多自定义渲染
欢迎交流
如果你在实际开发过程中, 也遇到了渲染 AI 返回 Markdown 数据渲染的挑战.
可以通过 Issues 和我们交流.
如果你希望做贡献, 或者提供更多渲染策略的意见, 也随时欢迎~