vue-mdr
v0.1.0
Published
Tranform markdown string to vue component with `mdast-> hast-> vue node`
Maintainers
Readme
VueMarkdownRenderer
A config-driven, high-performance Markdown renderer for Vue, designed for LLM streaming, rich code blocks, and embedded visual components.
Core Idea
Unlike traditional Markdown components that rely on many reactive props, VueMarkdownRenderer uses a single static configuration to define rendering behavior.
const MarkdownRenderer = createMarkdownRenderer(config);- Rendering capabilities are defined once
- The returned renderer component is pure and predictable
- Avoids the mental overhead of “is this prop reactive?”
This design is intentional: 99% of Markdown rendering scenarios do not require runtime mutation of render rules.
Installation
npm install vue-mdrBasic Usage
import { createMarkdownRenderer } from "vue-mdr";export const MarkdownRenderer = createMarkdownRenderer({
// configuration here
});<template>
<MarkdownRenderer :source="markdownText" />
</template>Full Configuration Example
Below is a complete example matching the latest API design.
import { createMarkdownRenderer } from "vue-mdr";
import "katex/dist/katex.min.css";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import type { Plugin } from "unified";
import {
BarChart,
CodeBlockRenderer,
MermaidRenderer,
EchartRenderer,
Placeholder,
} from "./components";
export const MarkdownRenderer = createMarkdownRenderer({
/**
* Custom Vue components rendered via `component-json` blocks
*/
componentsMap: {
BarChart,
Placeholder,
},
/**
* Custom code block renderer
*/
codeBlock: {
renderer: CodeBlockRenderer,
},
/**
* ECharts support
*/
echart: {
renderer: EchartRenderer,
placeholder: Placeholder,
},
/**
* Mermaid diagrams
*/
mermaid: {
renderer: MermaidRenderer,
},
/**
* Markdown / HTML plugins
*/
remarkPlugins: [remarkMath],
rehypePlugins: [rehypeKatex as unknown as Plugin],
});Design Philosophy
1. Static Configuration over Reactive Props
createMarkdownRenderer({
mermaid: { renderer },
echart: { renderer },
codeBlock: { renderer },
});- No runtime mutation
- No watchers
- No ambiguous reactivity expectations
The renderer is configured, not controlled.
2. Capability-based Rendering
Each feature is opt-in:
| Feature | Config Key |
| -------------- | --------------------------------- |
| Code blocks | codeBlock |
| Mermaid | mermaid |
| ECharts | echart |
| Vue components | componentsMap |
| LaTeX | remarkPlugins / rehypePlugins |
If you don’t configure it, it doesn’t exist.
Custom Code Block Rendering
Your CodeBlockRenderer receives:
interface Props {
highlightVnode: VNode;
language: string;
}This allows you to implement:
- Copy buttons
- Language labels
- Custom headers
- Animations
- Streaming-friendly UI
codeBlock: {
renderer: CodeBlockRenderer,
}Rendering Vue Components (component-json)
```component-json {"placeholder": "Placeholder"}
{
"type": "BarChart",
"props": {
"chartData": {
"categories": ["A", "B", "C"],
"seriesData": [10, 20, 30]
}
}
}
```{"type":"BarChart","props":{"chartData":{"categories":["type1","type2","type3","type4","type5","type6","type7","type8","type9","type10","type11","type12","type13","type14","type15","type16","type17","type18","type19","type20"],"seriesData":[100,200,150,180,120,130,170,160,190,210,220,140,125,155,165,175,185,195,205,215]}}}Rendering ECharts
```echarts
{
"title": {
"text": "数据对比趋势变化",
"left": "center"
},
"tooltip": {
"trigger": "axis",
"axisPointer": {
"type": "cross",
"crossStyle": { "color": "#999" }
},
"formatter": "{b}<br/>{a0}: {c0}"
},
"legend": {
"data": ["本期"],
"top": "bottom"
},
"grid": {
"left": "3%",
"right": "4%",
"bottom": "10%",
"containLabel": true
},
"xAxis": [
{
"type": "category",
"data": ["xxx", "zzz"],
"axisPointer": { "type": "shadow" }
}
],
"yAxis": [
{
"type": "value",
"name": "数值",
"min": 0,
"axisLabel": { "formatter": "{value}" }
}
],
"series": [
{
"name": "本期",
"type": "bar",
"data": [5061.1429, 504.8844],
"itemStyle": { "color": "#3ba272" }
}
]
}
```Configuration:
echart: {
renderer: EchartRenderer,
placeholder: Placeholder,
}{
"title": {
"text": "数据对比趋势变化",
"left": "center"
},
"tooltip": {
"trigger": "axis",
"axisPointer": {
"type": "cross",
"crossStyle": { "color": "#999" }
},
"formatter": "{b}<br/>{a0}: {c0}"
},
"legend": {
"data": ["本期"],
"top": "bottom"
},
"grid": {
"left": "3%",
"right": "4%",
"bottom": "10%",
"containLabel": true
},
"xAxis": [
{
"type": "category",
"data": ["xxx", "zzz"],
"axisPointer": { "type": "shadow" }
}
],
"yAxis": [
{
"type": "value",
"name": "数值",
"min": 0,
"axisLabel": { "formatter": "{value}" }
}
],
"series": [
{
"name": "本期",
"type": "bar",
"data": [5061.1429, 504.8844],
"itemStyle": { "color": "#3ba272" }
}
]
}Mermaid Diagrams
```mermaid
sequenceDiagram
Alice->>Bob: Hello
Bob-->>Alice: Hi!
```mermaid: {
renderer: MermaidRenderer,
}sequenceDiagram
Alice->>Bob: Hello
Bob-->>Alice: Hi!LaTeX Support
remarkPlugins: [remarkMath],
rehypePlugins: [rehypeKatex],$$ \begin{align} \nabla \times \vec{\mathbf{B}} -, \frac1c, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \ \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \ \nabla \times \vec{\mathbf{E}}, +, \frac1c, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \ \nabla \cdot \vec{\mathbf{B}} & = 0 \end{align} $$
