vue-renderer-markdown
Version:
Vue 3 Markdown renderer optimized for large docs: progressive Mermaid, streaming diff code blocks, and fast real-time preview.
533 lines (365 loc) • 19.4 kB
Markdown
# vue-renderer-markdown
> 针对 Vue 3 的高性能、流式友好型 Markdown 渲染组件 — 支持渐进式 Mermaid、流式 diff 代码块以及为大文档优化的实时预览。
[](https://www.npmjs.com/package/vue-renderer-markdown)
[](README.zh-CN.md)
[](https://www.npmjs.com/package/vue-renderer-markdown)
[](https://bundlephobia.com/package/vue-renderer-markdown)
[](./LICENSE)
## 目录
- [为什么使用它?](#为什么使用它)
- [与传统 Markdown 渲染器的区别](#与传统-markdown-渲染器的区别)
- [实时演示](#-实时演示)
- [特性](#特性)
- [安装](#安装)
- [Peer 依赖](#peer-依赖)
- [服务端渲染(SSR)](#服务端渲染ssr)
- [数学公式渲染选项](#数学公式渲染选项)
- [快速开始](#快速开始)
- [选择代码块渲染风格](#选择代码块渲染风格)
- [TypeScript 使用](#typescript-使用)
- [为何选择 vue-renderer-markdown?](#为何选择-vue-renderer-markdown)
- [用法示例](#用法示例)
- [性能特性](#性能特性)
- [性能建议](#性能建议)
- [组件 Props](#组件-props)
- [新属性:`renderCodeBlocksAsPre`](#新属性-rendercodeblocksaspre)
- [高级定制](#高级定制)
- [Monaco 集成](#monaco-集成)
- [代码块头部自定义](#代码块头部自定义)
- [Mermaid:渐进式渲染示例](#mermaid渐进式渲染示例)
- [Tailwind(例如 shadcn)——样式顺序问题处理](#tailwind-eg-shadcn--fix-style-ordering-issues)
- [故障排查](#故障排查)
- [鸣谢](#鸣谢)
- [Star 历史](#star-历史)
- [许可](#许可)
## 为什么使用它?
- 渐进式 Mermaid:图表在语法达到可渲染状态时会立即显示,随后逐步完善,用户能更早看到结果。
- 流式 diff 代码块:代码差异随输入逐步更新,适合显示实时编辑或 AI 生成的逐步更改。
- 为大文档和实时场景优化:尽量减少 DOM 更新与内存占用,保持界面流畅。
## 与传统 Markdown 渲染器的区别
传统渲染器通常把完整的 Markdown 字符串转为静态 HTML 树。本库面向流式与交互式场景,提供若干传统渲染器没有的能力:
- 流式优先:支持对不完整或增量产生的 Markdown 进行局部渲染,而无需对整个文档重复解析。适用于 AI 输出或逐字符/逐 token 产生内容的编辑器。
- 流式感知的代码块与“跳转到”体验:大代码块可增量更新,渲染器能保持光标/选区上下文和细粒度编辑,提升代码编辑体验。
- 内置 diff/流式代码组件:支持按行或按 token 展示差异,最小化回流,适合实时代码审查或 AI 编辑场景。
- 渐进式图表与编辑器:Mermaid 与基于 Monaco 的预览会在语法有效时尽快渲染并在后续增量更新中完善效果。
- 灵活的代码块渲染:可选 Monaco 编辑器(完全交互)或轻量的 Shiki 高亮(只读场景)。
- 平滑交互:针对大文档优化以减少 DOM churn,保证交互体验(流式 diff、增量图表更新、编辑器集成)。
这些能力使本库特别适用于实时、AI 驱动或大文档场景,在这些场景下传统的静态 Markdown-HTML 管线会产生延迟或破坏体验。
## 🚀 实时演示
- [Streaming playground](https://vue-markdown-renderer.simonhe.me/) — 在浏览器中试用大文件、渐进式图表等特性。
- [Markdown vs v-html comparison](https://vue-markdown-renderer.simonhe.me/markdown) — 对比本库的响应式渲染与传统静态管线。
### 交互测试页面
- 试用交互式测试页面以便快速验证与调试: https://vue-markdown-renderer.simonhe.me/test
此页面提供左侧编辑器与右侧实时预览(由本库驱动)。页面包含“生成并复制分享链接”功能,会将你的输入编码到 URL 中以便分享;当输入过长无法嵌入 URL 时,会提供直接打开或预填 GitHub Issue 的回退流程。
你可以使用该页面复现渲染问题,验证数学公式 / Mermaid / 代码块的渲染行为,并快速生成可共享链接或带复现信息的 issue。
### 介绍视频
一段短视频介绍了 vue-renderer-markdown 的关键特性与使用方式。
[](https://www.bilibili.com/video/BV17Z4qzpE9c/)
在 Bilibili 上观看: [Open in Bilibili](https://www.bilibili.com/video/BV17Z4qzpE9c/)
## 特性
- ⚡ 极致性能:为流式场景设计的最小化重渲染和高效 DOM 更新
- 🌊 流式优先:原生支持不完整或频繁更新的 token 化 Markdown 内容
- 🧠 Monaco 流式更新:高性能的 Monaco 集成,支持大代码块的平滑增量更新
- 🪄 渐进式 Mermaid:图表在语法可用时即时渲染,并在后续更新中完善
- 🧩 自定义组件:允许在 Markdown 内容中嵌入自定义 Vue 组件
- 📝 完整 Markdown 支持:表格、公式、Emoji、复选框、代码块等
- 🔄 实时更新:支持增量内容而不破坏格式
- 📦 TypeScript 优先:提供完善的类型定义与智能提示
- 🔌 零配置:开箱即可在 Vue 3 项目中使用
- 🎨 灵活的代码块渲染:可选 Monaco 编辑器 (`CodeBlockNode`) 或轻量的 Shiki 高亮 (`MarkdownCodeBlockNode`)
## 安装
```bash
pnpm add vue-renderer-markdown
# or
npm install vue-renderer-markdown
# or
yarn add vue-renderer-markdown
```
### Peer 依赖
本包设计为在运行时按需加载可选依赖,缺失时会优雅退化。要打开高级功能,请按需安装下列 peer:
完整安装(推荐,用于启用全部功能):
```bash
pnpm add mermaid stream-monaco shiki
```
按需功能:
| 依赖 | 版本 | 启用功能 | 缺省回退 |
|---|---:|---|---|
| `mermaid` | >=11 | 渐进式 Mermaid 图表 | 展示原始代码块 |
| `stream-monaco` | >=0.0.33 | Monaco 编辑器的流式集成 | 以纯文本显示 |
| `stream-markdown` | >=0.0.2 | `MarkdownCodeBlockNode` 的流式高亮 | 以纯文本显示 |
| `shiki` | ^3.13.0 | Shiki 语法高亮 | 以纯文本显示 |
| `vue-i18n` | >=9 | 国际化支持 | 内置轻量同步翻译器 |
重要说明:
- KaTeX 未打包或自动注入。如需 LaTeX 渲染,请在宿主应用中安装 `katex` 并在入口处引入样式:
```bash
pnpm add katex
```
然后在 `main.ts` 中:
```ts
import 'katex/dist/katex.min.css'
```
- 工具栏图标以本地 SVG 提供,无需额外图标库。
- 可选 peer 在运行时按需懒加载,因此你可先以最小依赖开始,然后按需增加功能。
### 自定义解析钩子
对于高级用法,你可以通过 `parseMarkdownToStructure` 的 `options` 参数在解析流程中注入钩子,或者在需要 AST 时直接调用该工具。我们现在内部使用 [`markdown-it-ts`](https://www.npmjs.com/package/markdown-it-ts)(与 markdown-it 兼容的 TypeScript 发行版),因此钩子 API 与插件生态保持一致。
- `preTransformTokens?: (tokens: MarkdownToken[]) => MarkdownToken[]` —— 在 `markdown-it-ts` 生成 tokens 后立即调用,可在组件解析前重写或替换 token。
- `postTransformTokens?: (tokens: MarkdownToken[]) => MarkdownToken[]` —— 在内部修正执行后调用;若返回新的 token 数组,将重新解析成节点。
- `postTransformNodes?: (nodes: ParsedNode[]) => ParsedNode[]` —— 直接在已解析的节点树上操作,通常是调整最终输出的最简方式。
## 服务端渲染(SSR)
库的导入对 SSR 是安全的。重型依赖(Monaco、Mermaid、Worker)会在浏览器端懒加载。注意某些功能(Monaco 编辑器、渐进式 Mermaid、Web Worker)依赖浏览器 API,只能在客户端渲染。
Nuxt 3 快速示例:使用 `<client-only>` 包裹组件:
```vue
<template>
<client-only>
<MarkdownRender :content="markdown" />
</client-only>
</template>
```
Vite SSR / 自定义 SSR:延迟到 onMounted 后渲染:
```vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import MarkdownRender from 'vue-renderer-markdown'
const mounted = ref(false)
onMounted(() => {
mounted.value = true
})
</script>
<template>
<div v-if="mounted">
<MarkdownRender :content="markdown" />
</div>
<div v-else>
<pre>{{ markdown }}</pre>
</div>
</template>
```
运行 SSR 检查(仓库提供的脚本)可用:
```bash
pnpm run check:ssr
```
注意:KaTeX 在 SSR 下可用,但仅当宿主应用安装并打包了 `katex`。
## 数学公式渲染选项
本库内置轻量的数学插件,会尝试规范常见 KaTeX/TeX 命令及因字符转义造成的问题(例如字符串中意外出现的控制字符)。
你可以通过 `getMarkdown` 的 `mathOptions` 参数自定义行为:
```ts
import { getMarkdown } from 'vue-renderer-markdown'
const md = getMarkdown({
mathOptions: {
commands: ['in', 'perp', 'alpha'],
escapeExclamation: true,
}
})
```
导出工具:
- `KATEX_COMMANDS` — 默认需自动加反斜杠的命令列表。
- `normalizeStandaloneBackslashT(s, opts?)` — 内部用到的规范化函数,可用于预处理数学内容。
示例:
```ts
import { KATEX_COMMANDS, normalizeStandaloneBackslashT } from 'vue-renderer-markdown'
const raw = 'a\tb + infty'
const normalized = normalizeStandaloneBackslashT(raw, { commands: KATEX_COMMANDS })
```
### 全局/插件级默认值
安装插件时可传入 math 默认项,所有由库创建的实例都会继承这些默认值:
```ts
import { createApp } from 'vue'
import MarkdownRender, { VueRendererMarkdown } from 'vue-renderer-markdown'
const app = createApp(App)
app.use(VueRendererMarkdown, {
mathOptions: {
commands: ['in', 'perp', 'alpha'],
escapeExclamation: false,
}
})
app.mount('#app')
```
或者调用 `setDefaultMathOptions`:
```ts
import { setDefaultMathOptions } from 'vue-renderer-markdown'
setDefaultMathOptions({ commands: ['infty', 'perp'], escapeExclamation: true })
```
## 国际化(i18n)
默认 `getMarkdown` 使用英文 UI 文案。你可以通过 `i18n` 选项传入映射表或翻译函数:
```ts
// 翻译映射
// 翻译函数(例如使用 vue-i18n)
import { useI18n } from 'vue-i18n'
const md = getMarkdown('editor-1', { i18n: { 'common.copy': '复制' } })
const { t } = useI18n()
const md2 = getMarkdown('editor-1', { i18n: (key: string) => t(key) })
```
默认翻译键示例:`common.copy` — 代码块复制按钮文本。
该设计使工具函数保持纯净,可与任意 i18n 解决方案集成或直接传入静态翻译映射。
## 快速开始
1. 安装(包含 Vue):
```bash
pnpm add vue-renderer-markdown vue
```
2. 基本用法:
```vue
<script setup lang="ts">
import MarkdownRender from 'vue-renderer-markdown'
import 'vue-renderer-markdown/index.css'
const content = `
# Hello World
This is **bold** and this is *italic*.
- List item 1
- List item 2
\`\`\`javascript
console.log('Code block!')
\`\`\`
`
</script>
<template>
<MarkdownRender :content="content" />
</template>
```
### 启用可选特性示例
Mermaid:
```bash
pnpm add mermaid
```
Monaco 编辑器:
```bash
pnpm add stream-monaco
```
Shiki 高亮:
```bash
pnpm add shiki
```
### 组件属性(重要的选项说明)
组件常用 props 概览:
| 名称 | 类型 | 必需 | 描述 |
|---|---|---:|---|
| `content` | `string` | ✓ | 要渲染的 Markdown 文本 |
| `nodes` | `BaseNode[]` | | 解析好的 AST 节点(替代 `content`) |
| `renderCodeBlocksAsPre` | `boolean` | | 将所有 code_block 渲染为简单的 `<pre><code>`(轻量、无依赖) |
| `codeBlockStream` | `boolean` | | 控制 code_block 的流式行为(`true` 时逐步更新,`false` 时等待最终内容) |
| `viewportPriority` | `boolean` | | 是否优先渲染视口内/附近的重型节点,默认 `true` |
> 注意:`content` 或 `nodes` 必须提供其一。
## 新属性:`renderCodeBlocksAsPre`
- 类型:`boolean`
- 默认:`false`
说明:将 `code_block` 节点全部以简单预格式化文本渲染(库内部的 `PreCodeNode`),而不是使用可能依赖可选 peer(Monaco、Mermaid)的完整 `CodeBlockNode`。适用于需要轻量呈现(例如 AI 思考输出)且不想引入可选依赖的场景。
注意:开启后 `CodeBlockNode` 的相关 prop(例如主题、Monaco 配置等)不会生效。
示例:
```vue
<MarkdownRender :content="markdown" :render-code-blocks-as-pre="true" />
```
## 新属性:`codeBlockStream`
- 类型:`boolean`
- 默认:`true`
说明:设为 `false` 时,`code_block` 节点不会在中途增量渲染,而是保持加载占位并在最终内容准备好时一次性渲染。这在某些场景下可以降低布局抖动或避免频繁初始化 Monaco。仅在 `CodeBlockNode` 被使用时生效。
示例:
```vue
<MarkdownRender :content="markdown" :code-block-stream="false" />
```
## 新属性:`viewportPriority`
- 类型:`boolean`
- 默认:`true`
说明:默认开启时,渲染器会优先处理视口附近的重型节点(如 Mermaid、Monaco),将离屏工作延后,从而提升长文档或流式内容的可交互性。设为 `false` 会使渲染器尽快渲染全部节点(用于打印/导出等场景)。
示例:
```vue
<MarkdownRender :content="markdown" :viewport-priority="false" />
```
## 高级定制
你可以通过两种方式覆盖内部节点的渲染组件:
- 范围化(推荐):使用 `setCustomComponents(id, mapping)` 并在对应的 `MarkdownRender` 实例上传入 `custom-id`,只影响该实例。
- 全局(兼容旧用法):调用 `setCustomComponents(mapping)`,但该方式灵活性较差且不推荐用于新的用例。
示例(范围化):
```ts
import { createApp } from 'vue'
import MarkdownRender, { setCustomComponents } from 'vue-renderer-markdown'
import MyCustomNode from './components/MyCustomNode.vue'
setCustomComponents('docs-page', {
admonition: MyCustomNode,
})
// 在渲染器中使用:
// <MarkdownRender :content="markdown" custom-id="docs-page" />
```
如果动态创建/销毁映射,请使用 `removeCustomComponents(id)` 释放内存。
### MarkdownCodeBlockNode(替代轻量代码块渲染)
`MarkdownCodeBlockNode` 提供了基于 Shiki 的语法高亮替代 Monaco 的展示方式,适合只需展示且希望 SSR 友好的场景。
所需 peer:`stream-markdown` (>=0.0.2) 与 `shiki` (^3.13.0)。
安装:
```bash
pnpm add stream-markdown shiki
```
用法:
```ts
import { MarkdownCodeBlockNode, setCustomComponents } from 'vue-renderer-markdown'
setCustomComponents({ code_block: MarkdownCodeBlockNode })
```
## Vite 配置与 Worker 用法
若使用 Vite,建议将 worker 格式设置为 `es`:
```ts
// vite.config.ts
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [vue()],
worker: { format: 'es' },
})
```
为 KaTeX 与 Mermaid 使用 Web Worker:
```ts
import KatexWorker from 'vue-renderer-markdown/workers/katexRenderer.worker?worker'
import { setKaTeXWorker } from 'vue-renderer-markdown/workers/katexWorkerClient'
import MermaidWorker from 'vue-renderer-markdown/workers/mermaidParser.worker?worker'
import { setMermaidWorker } from 'vue-renderer-markdown/workers/mermaidWorkerClient'
setKaTeXWorker(new KatexWorker())
setMermaidWorker(new MermaidWorker())
```
## ImageNode 插槽(占位 / 错误)
`ImageNode` 支持两个命名插槽来自定义加载与报错态:`placeholder` 与 `error`。插槽接收以下响应式 props:
- `node` — 原始 ImageNode 对象
- `displaySrc` — 当前用于渲染的 src(可能为 fallbackSrc)
- `imageLoaded` — 图片是否已加载
- `hasError` — 是否处于错误态
- `fallbackSrc` — 传入的 fallback 地址(如有)
- `lazy` — 是否使用懒加载
- `isSvg` — 当前 displaySrc 是否为 SVG
如果不提供插槽,组件会显示内置的 CSS 旋转加载器与简单错误占位。
## TableNode loading 插槽
`TableNode` 在 `node.loading === true` 时会显示 shimmer + spinner 覆盖层,你可以通过 `loading` 插槽替换覆盖层内容,插槽 props 为 `{ isLoading: boolean }`。
## LinkNode 下划线动画与颜色定制
`LinkNode` 支持运行时配置下划线动画与颜色的若干 prop,默认保持此前的外观。可调整的 prop 包括 `color`、`underlineHeight`、`underlineBottom`、`animationDuration`、`animationOpacity`、`animationTiming`、`animationIteration`。
示例、默认值与注意事项见英文文档。
## Monaco 编辑器集成
若使用 Monaco,请配置 `vite-plugin-monaco-editor-esm` 以正确打包 worker。对于只需要 Monaco 编辑器而不需要完整渲染管线的场景,可以直接使用 `stream-monaco` 进行更轻量的集成。
建议在应用初始化或页面挂载时调用 `getUseMonaco()` 预加载 Monaco,以减少首次渲染延迟(函数会在模块不可用或 SSR 环境下安全降级):
```ts
import { getUseMonaco } from './src/components/CodeBlockNode/monaco'
getUseMonaco()
```
## Tailwind(例如 shadcn)——样式顺序问题处理
若使用 Tailwind 组件库(如 shadcn),可能遇到样式顺序导致覆盖问题。推荐把库样式放入 Tailwind 的 `components` 层以便应用样式覆盖:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
@import 'vue-renderer-markdown/index.css';
}
```
或根据需要放入 `base` 层以提高优先级。
## 故障排查(常见问题)
- Monaco worker 未找到:请配置 `vite-plugin-monaco-editor-esm` 或使用相应的 Webpack 插件以确保 worker 能被正确打包与加载。
- Mermaid 不渲染:确认已安装 `mermaid` 且语法正确;查看控制台错误以确定问题。
- 语法高亮无效:确认安装 `stream-markdown` 与 `shiki` 并将 `MarkdownCodeBlockNode` 注册为代码块渲染组件。
- TypeScript 类型问题:通过正确路径导入类型并确保 `tsconfig` 的 `moduleResolution` 设置兼容(`bundler` 或 `node16`)。
- SSR 报 `window is not defined`:将涉及浏览器 API 的渲染包裹为 client-only 或延迟到 onMounted 执行。
- 图标未显示:确认引入样式并检查构建工具是否允许从依赖中导入静态资源(SVG)。
更详细的解决方案与示例请参见英文 README 的相应章节。
## 鸣谢
本项目使用并受益于:
- [stream-monaco](https://github.com/Simon-He95/stream-monaco)
- [stream-markdown](https://github.com/Simon-He95/stream-markdown)
- [mermaid](https://mermaid-js.github.io/mermaid)
- [shiki](https://github.com/shikijs/shiki)
感谢这些项目的作者与贡献者!
## Star 历史
[](https://www.star-history.com/#Simon-He95/vue-markdown-render&Date)
## 许可
[MIT](./LICENSE) © [Simon He](https://github.com/Simon-He95)