Skip to content

element-plus-x/x-markdown

Repository files navigation

X-Markdown

一个功能强大的 Vue 3 Markdown 渲染组件库

支持流式渲染、代码高亮、LaTeX 数学公式、Mermaid 图表等特性

NPM version NPM downloads License Vue

✨ 特性

  • 🚀 Vue 3 组合式 API - 基于 Vue 3 Composition API 构建
  • 📝 GitHub Flavored Markdown - 完整支持 GFM 语法
  • 🎨 代码高亮 - 基于 Shiki,支持 100+ 语言和多种主题
  • 🌊 流式渲染 - 支持 AI 对话场景的实时输出动画
  • 🧮 LaTeX 数学公式 - 支持行内和块级数学公式渲染
  • 📊 Mermaid 图表 - 支持流程图、时序图等多种图表
  • 🌗 深色模式 - 内置深浅色主题切换支持
  • 🔌 高度可定制 - 支持自定义渲染、插槽和属性
  • 🎭 灵活的插件系统 - 支持 remark 和 rehype 插件扩展
  • 🔒 安全可靠 - 可选的 HTML 内容清理和消毒
  • 📦 Monorepo 架构 - 使用 pnpm workspace 和 Turbo 管理

📦 安装

# pnpm (推荐)
pnpm add x-markdown-vue

# npm
npm install x-markdown-vue

# yarn
yarn add x-markdown-vue

依赖项

确保安装了对等依赖:

pnpm add vue@^3.3.0

如果需要 LaTeX 支持,还需要引入 KaTeX 样式:

import 'katex/dist/katex.min.css'

🚀 快速开始

基础用法

<template>
  <MarkdownRenderer :markdown="content" />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { MarkdownRenderer } from 'x-markdown-vue'
import 'x-markdown-vue/style'

const content = ref(`
# Hello World

This is a **markdown** renderer.
`)
</script>

异步渲染

对于大型文档,可以使用异步渲染模式:

<template>
  <Suspense>
    <MarkdownRendererAsync :markdown="content" />
    <template #fallback>
      <div>加载中...</div>
    </template>
  </Suspense>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { MarkdownRendererAsync } from 'x-markdown-vue'
import 'x-markdown-vue/style'

const content = ref('# Large Document\n...')
</script>

📖 配置选项

Props 属性

属性 类型 默认值 说明
markdown string '' Markdown 字符串内容
allowHtml boolean false 是否允许渲染 HTML
enableLatex boolean true 是否启用 LaTeX 数学公式支持
enableAnimate boolean false 是否启用流式动画效果
enableBreaks boolean true 是否将换行符转换为 <br>
isDark boolean false 是否为深色模式
showCodeBlockHeader boolean true 是否显示代码块头部
codeMaxHeight string undefined 代码块最大高度,如 '300px'
codeBlockActions CodeBlockAction[] [] 代码块自定义操作按钮
mermaidActions MermaidAction[] [] Mermaid 图表自定义操作按钮
codeXRender object {} 自定义代码块渲染函数
customAttrs CustomAttrs {} 自定义属性对象
remarkPlugins PluggableList [] remark 插件列表
rehypePlugins PluggableList [] rehype 插件列表
sanitize boolean false 是否启用内容清洗
sanitizeOptions SanitizeOptions {} 清洗配置选项

CodeXProps 代码块配置

interface CodeXProps {
  codeLightTheme?: string        // 亮色主题,默认 'vitesse-light'
  codeDarkTheme?: string         // 暗色主题,默认 'vitesse-dark'
  showCodeBlockHeader?: boolean  // 是否显示代码块头部
  codeMaxHeight?: string         // 代码块最大高度,如 '300px'
  enableAnimate?: boolean        // 是否启用代码块动画
  codeBlockActions?: CodeBlockAction[]  // 代码块自定义操作按钮
  mermaidActions?: MermaidAction[]  // Mermaid 图表自定义操作按钮
}

interface CodeBlockAction {
  key: string                    // 唯一标识符
  title: string                  // 按钮标题
  icon: string                   // 按钮图标(SVG 或文本)
  onClick: (props: any) => void  // 点击回调函数
  show?: (props: any) => boolean // 条件显示函数(可选)
}

interface MermaidAction {
  key: string                    // 唯一标识符
  title: string                  // 按钮标题
  icon: string                   // 按钮图标(SVG 或文本)
  onClick: (props: any) => void  // 点击回调函数
  show?: (props: any) => boolean // 条件显示函数(可选)
}
<MarkdownRenderer
  :markdown="content"
  :is-dark="isDark"
  :code-x-props="{
    codeLightTheme: 'github-light',
    codeDarkTheme: 'github-dark',
    showCodeBlockHeader: true,
    codeMaxHeight: '400px'
  }"
/>

🎨 主题配置

深色模式

通过 isDark 属性控制整体主题:

<template>
  <MarkdownRenderer :markdown="content" :is-dark="isDark" />
</template>

<script setup>
import { ref } from 'vue'

const isDark = ref(false)

const toggleTheme = () => {
  isDark.value = !isDark.value
}
</script>

代码高亮主题

支持所有 Shiki 内置主题

<MarkdownRenderer
  :markdown="content"
  :code-x-props="{
    codeLightTheme: 'github-light',
    codeDarkTheme: 'one-dark-pro'
  }"
/>

🔧 自定义渲染

自定义属性

通过 customAttrs 为 Markdown 元素添加自定义属性:

<MarkdownRenderer
  :markdown="content"
  :custom-attrs="{
    heading: (node, { level }) => ({
      class: ['heading', `heading-${level}`],
      id: `heading-${level}`
    }),
    a: (node) => ({
      target: '_blank',
      rel: 'noopener noreferrer'
    })
  }"
/>

自定义插槽

组件提供了强大的插槽系统,可以自定义任何 Markdown 元素的渲染:

<MarkdownRenderer :markdown="content">
  <!-- 自定义标题渲染 -->
  <template #heading="{ node, level, children }">
    <component :is="`h${level}`" class="custom-heading">
      <a :href="`#heading-${level}`" class="anchor">#</a>
      <component :is="children" />
    </component>
  </template>

  <!-- 自定义引用块渲染 -->
  <template #blockquote="{ children }">
    <blockquote class="custom-blockquote">
      <div class="quote-icon">💬</div>
      <component :is="children" />
    </blockquote>
  </template>

  <!-- 自定义链接渲染 -->
  <template #a="{ node, children }">
    <a :href="node?.properties?.href" target="_blank" class="custom-link">
      <component :is="children" />
      <span class="external-icon">↗</span>
    </a>
  </template>
</MarkdownRenderer>

支持的插槽类型

  • heading / h1 ~ h6 - 标题
  • code / inline-code / block-code - 代码
  • blockquote - 引用块
  • list / ul / ol / li / list-item - 列表
  • table / thead / tbody / tr / td / th - 表格
  • a / img / p / strong / em - 行内元素
  • 以及所有标准 HTML 标签名

自定义代码块渲染器

通过 codeXRender 自定义特定语言的代码块渲染:

<script setup>
import { h } from 'vue'
import EchartsRenderer from './EchartsRenderer.vue'

const codeXRender = {
  // 自定义 echarts 代码块渲染
  echarts: (props) => h(EchartsRenderer, { code: props.raw.content }),
  // 自定义行内代码渲染
  inline: (props) => h('code', { class: 'custom-inline' }, props.raw.content)
}
</script>

<template>
  <MarkdownRenderer :markdown="content" :code-x-render="codeXRender" />
</template>

代码块插槽

通过 codeXSlots 自定义代码块的头部区域:

<script setup>
import { h } from 'vue'

const codeXSlots = {
  'header-left': ({ language }) => h('span', { class: 'lang-badge' }, language),
  'header-right': ({ code, copy }) => h('button', { onClick: () => copy(code) }, '📋 复制')
}
</script>

<template>
  <MarkdownRenderer :markdown="content" :code-x-slots="codeXSlots" />
</template>

🌊 流式渲染动画

启用 enableAnimate 属性后,代码块中的每个 token 会添加 x-md-animated-word class,可配合 CSS 实现流式输出动画效果:

<MarkdownRenderer :markdown="content" :enable-animate="true" />
/* 自定义动画样式 */
.x-md-animated-word {
  animation: fadeIn 0.3s ease-in-out;
}

@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

🔌 插件系统

remark 插件

<script setup>
import remarkEmoji from 'remark-emoji'

const remarkPlugins = [remarkEmoji]
</script>

<template>
  <MarkdownRenderer :markdown="content" :remark-plugins="remarkPlugins" />
</template>

rehype 插件

<script setup>
import rehypeSlug from 'rehype-slug'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'

const rehypePlugins = [rehypeSlug, rehypeAutolinkHeadings]
</script>

<template>
  <MarkdownRenderer :markdown="content" :rehype-plugins="rehypePlugins" />
</template>

🛡️ 安全配置

启用内容清洗以防止 XSS 攻击:

<MarkdownRenderer
  :markdown="untrustedContent"
  :sanitize="true"
  :sanitize-options="{
    allowedTags: ['h1', 'h2', 'p', 'a', 'code', 'pre'],
    allowedAttributes: {
      a: ['href', 'target']
    }
  }"
/>

🌟 功能演示

代码高亮

支持 100+ 编程语言的语法高亮,基于 Shiki 引擎:

```javascript
function greet(name) {
  console.log(`Hello, ${name}!`);
}
```

```python
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)
```

LaTeX 数学公式

支持行内和块级数学公式:

行内公式: $E = mc^2$

块级公式:
$$
\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
$$

Mermaid 图表

X-Markdown 支持完整的 Mermaid 图表渲染,包括流程图、时序图、甘特图、类图、状态图、饼图、ER 图等多种图表类型,并提供丰富的交互功能。

流程图 (Flowchart)

graph TB
    A[开始] --> B{是否登录?}
    B -->|是| C[进入首页]
    B -->|否| D[跳转登录页]
    D --> E[输入账号密码]
    E --> F{验证通过?}
    F -->|是| C
    F -->|否| G[显示错误]
    G --> E
    C --> H[结束]
Loading

时序图 (Sequence Diagram)

sequenceDiagram
    participant U as 用户
    participant C as 客户端
    participant S as 服务器
    participant D as 数据库

    U->>C: 点击登录
    C->>S: POST /api/login
    S->>D: 查询用户信息
    D-->>S: 返回用户数据
    S-->>C: 返回 JWT Token
    C-->>U: 登录成功,跳转首页
Loading

甘特图 (Gantt Chart)

gantt
    title 项目开发计划
    dateFormat  YYYY-MM-DD
    section 需求分析
    需求调研           :a1, 2024-01-01, 7d
    需求文档           :after a1, 5d
    section 设计阶段
    UI 设计            :2024-01-10, 10d
    架构设计           :2024-01-12, 8d
    section 开发阶段
    前端开发           :2024-01-20, 20d
    后端开发           :2024-01-20, 25d
    section 测试上线
    集成测试           :2024-02-15, 10d
    上线部署           :2024-02-25, 3d
Loading

类图 (Class Diagram)

classDiagram
    class Animal {
        +String name
        +int age
        +makeSound()
    }
    class Dog {
        +String breed
        +bark()
        +fetch()
    }
    class Cat {
        +String color
        +meow()
        +scratch()
    }
    class Bird {
        +float wingspan
        +fly()
        +sing()
    }
    Animal <|-- Dog
    Animal <|-- Cat
    Animal <|-- Bird
Loading

状态图 (State Diagram)

stateDiagram-v2
    [*] --> 待处理
    待处理 --> 处理中 : 开始处理
    处理中 --> 已完成 : 处理成功
    处理中 --> 失败 : 处理失败
    失败 --> 处理中 : 重试
    失败 --> 已取消 : 取消
    已完成 --> [*]
    已取消 --> [*]
Loading

饼图 (Pie Chart)

pie showData
    title 技术栈使用占比
    "Vue.js" : 35
    "React" : 30
    "Angular" : 15
    "Svelte" : 10
    "其他" : 10
Loading

ER 图 (Entity Relationship)

erDiagram
    USER ||--o{ ORDER : places
    USER {
        int id PK
        string name
        string email
    }
    ORDER ||--|{ ORDER_ITEM : contains
    ORDER {
        int id PK
        date created_at
        int user_id FK
    }
    ORDER_ITEM }|--|| PRODUCT : references
    ORDER_ITEM {
        int id PK
        int quantity
        int order_id FK
        int product_id FK
    }
    PRODUCT {
        int id PK
        string name
        float price
    }
Loading

完整的配置示例

<template>
  <MarkdownRenderer
    :markdown="content"
    :is-dark="isDark"
    :enable-animate="true"
    :code-x-props="{
      codeLightTheme: 'github-light',
      codeDarkTheme: 'github-dark',
      showCodeBlockHeader: true,
      codeMaxHeight: '400px',
      enableAnimate: true,
      codeBlockActions: [
        {
          key: 'run',
          title: '运行代码',
          icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8 5v14l11-7L8 5z" fill="currentColor"/></svg>',
          onClick: (props) => {
            console.log('运行代码:', props.code)
            alert('运行代码功能(示例)')
          },
          show: (props) => ['javascript', 'typescript', 'js', 'ts'].includes(props.language)
        }
      ],
      mermaidActions: [
        {
          key: 'edit',
          title: '编辑图表',
          icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
          onClick: (props) => {
            console.log('编辑图表:', props.rawContent)
            alert('编辑图表功能(示例)')
          }
        }
      ]
    }"
    :code-x-render="codeXRender"
  />
</template>

<script setup>
// 自定义渲染器配置
const codeXRender = {
  json: ({ content, isDark }) => {
    try {
      const json = JSON.parse(content)
      return `<div class="json-viewer" style="background: ${isDark ? '#1e1e1e' : '#f5f5f5'}; padding: 12px; border-radius: 4px; font-family: monospace; white-space: pre-wrap;">${JSON.stringify(json, null, 2)}</div>`
    } catch {
      return `<div style="color: red;">JSON 解析错误</div>`
    }
  },
  echarts: ({ content, isDark }) => {
    try {
      const config = JSON.parse(content)
      const chartId = 'chart-' + Math.random().toString(36).substr(2, 9)
      return `<div id="${chartId}" style="height: 300px;"></div>
      <script>
        setTimeout(() => {
          const chart = echarts.init(document.getElementById('${chartId}'), '${isDark ? 'dark' : 'default'}')
          chart.setOption(${JSON.stringify(config)})
        }, 100)
      <\/script>`
    } catch {
      return `<div style="color: red;">ECharts 配置错误</div>`
    }
  }
}
</script>

表格

支持 GFM 表格语法:

| 特性 | 状态 |
|------|------|
| Markdown ||
| 代码高亮 ||
| LaTeX ||
| Mermaid ||

任务列表

- [x] 支持基础 Markdown
- [x] 添加语法高亮
- [x] 实现 LaTeX 支持
- [x] 添加 Mermaid 图表
- [ ] 更多功能开发中...

💡 使用场景

  • AI 对话应用 - 支持流式渲染,适合 ChatGPT 类应用
  • 技术文档站点 - 完整的 Markdown 支持,代码高亮
  • 博客系统 - 丰富的格式支持和自定义能力
  • 在线编辑器 - 实时预览 Markdown 内容
  • 知识库系统 - 支持数学公式和图表

🔧 技术栈

  • Vue 3 - 渐进式 JavaScript 框架
  • TypeScript - 类型安全的 JavaScript 超集
  • Unified - Markdown/HTML 处理生态系统
  • Shiki - 语法高亮引擎
  • KaTeX - 数学公式渲染
  • Mermaid - 图表生成
  • DOMPurify - HTML 清理工具
  • Vite - 下一代前端构建工具
  • Turbo - 高性能构建系统

📁 项目结构

x-markdown/
├── packages/
│   ├── x-markdown/          # 核心组件库
│   │   ├── src/
│   │   │   ├── components/  # Vue 组件
│   │   │   │   ├── CodeBlock/   # 代码块组件
│   │   │   │   ├── CodeLine/    # 行内代码组件
│   │   │   │   ├── CodeX/       # 代码渲染调度器
│   │   │   │   └── Mermaid/     # Mermaid 图表组件
│   │   │   ├── core/        # 核心渲染逻辑
│   │   │   ├── hooks/       # 组合式函数
│   │   │   ├── plugins/     # 内置插件
│   │   │   └── MarkdownRender/  # 主渲染组件
│   │   └── package.json
│   └── playground/          # 演示应用
├── pnpm-workspace.yaml
├── turbo.json
└── package.json

🤝 贡献

欢迎提交 Issue 和 Pull Request!

开发流程

  1. Fork 本仓库
  2. 创建你的特性分支 (git checkout -b feature/AmazingFeature)
  3. 提交你的改动 (git commit -m 'Add some AmazingFeature')
  4. 推送到分支 (git push origin feature/AmazingFeature)
  5. 提交 Pull Request

开发指南

# 克隆仓库
git clone https://github.com/element-plus-x/x-markdown.git
cd x-markdown

# 安装依赖
pnpm install

# 启动开发服务器
pnpm dev

# 构建项目
pnpm build

# 格式化代码
pnpm format

📄 License

MIT License © 2025 element-plus-x


如果这个项目对你有帮助,请给它一个 ⭐️