Skip to content

组件 API 文档显示源码指南

本文档说明如何在 VitePress 文档中同时显示 React 组件的预览和源码。

快速开始

1. 基本使用

在你的组件文档 .md 文件中使用 CodePreview 组件:

vue
<script setup>
import ButtonDemo from '../../demos/button-basic.tsx'
import ButtonDemoSource from '../../demos/button-basic.tsx?raw'
import CodePreview from '../../.vitepress/theme/components/CodePreview.vue'
</script>

<CodePreview :component="ButtonDemo" :source="ButtonDemoSource" :default-show-code="false" />

2. 参数说明

CodePreview 组件参数

参数类型默认值描述
componentComponent必需React 组件(直接导入)
sourcestring-组件源码字符串
default-show-codebooleanfalse是否默认显示源码

3. 工作原理

导入方式

javascript
// 导入 React 组件(用于渲染)
import ButtonDemo from '../../demos/button-basic.tsx'

// 导入源码文本(用于显示)- 注意 ?raw 后缀
import ButtonDemoSource from '../../demos/button-basic.tsx?raw'
  • ?raw 查询参数会触发自定义 Vite 插件
  • 插件会读取源文件内容并作为字符串导出
  • 源码字符串通过 Vue 的 props 传递给组件
  • 组件内部进行 HTML 转义和语法高亮

功能特性

1. 可折叠源码

  • 默认只显示组件预览
  • 点击"显示代码"按钮展开源码
  • 点击"隐藏代码"按钮收起源码

2. 代码复制

  • 在显示源码时,右上角会出现"复制代码"按钮
  • 点击后会复制完整源码到剪贴板
  • 复制成功后按钮文本会短暂显示"已复制!"

3. 主题适配

  • 自动适配浅色/深色主题
  • 使用 VitePress 的 CSS 变量
  • 保持与文档样式一致

完整示例

创建一个新组件文档:

markdown
# MyComponent 我的组件

组件描述...

## 示例

<script setup>
import MyDemo from '../../demos/my-component-demo.tsx'
import MyDemoSource from '../../demos/my-component-demo.tsx?raw'
import CodePreview from '../../.vitepress/theme/components/CodePreview.vue'
</script>

<CodePreview :component="MyDemo" :source="MyDemoSource" :default-show-code="false" />

## API 参数

| 参数 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| ... | ... | ... | ... |

技术实现

1. Vite 插件 (demo-source.ts)

typescript
// .vitepress/plugins/demo-source.ts
import fs from 'fs'
import { Plugin } from 'vite'

export function demoSourcePlugin(): Plugin {
  return {
    name: 'vitepress-demo-source',
    enforce: 'pre',
    transform(code, id) {
      // 处理 ?raw 查询参数
      if (id.includes('?raw') && id.includes('/demos/')) {
        const filePath = id.split('?')[0]
        const source = fs.readFileSync(filePath, 'utf-8')
        return {
          code: \`export default \${JSON.stringify(source)}\`,
          map: null
        }
      }
      return null
    }
  }
}

2. CodePreview 组件

位于 .vitepress/theme/components/CodePreview.vue

  • 使用 Vue 3 Composition API
  • 使用 React 18 的 createRoot API
  • 支持组件挂载和卸载的生命周期管理

3. 配置集成

.vitepress/config.ts 中注册插件:

typescript
import { demoSourcePlugin } from './plugins/demo-source'

export default defineConfig({
  vite: {
    plugins: [demoSourcePlugin()],
    // ...其他配置
  }
})

.vitepress/theme/index.ts 中注册组件:

typescript
import CodePreview from './components/CodePreview.vue'

export default {
  extends: DefaultTheme,
  enhanceApp({ app }) {
    app.component('CodePreview', CodePreview)
    // ...其他组件
  }
}

样式定制

可以通过修改 CodePreview.vue 中的样式来定制外观:

css
.code-preview {
  border: 1px solid var(--vp-c-divider);
  border-radius: 8px;
  /* 自定义样式... */
}

可用的 VitePress CSS 变量:

  • --vp-c-bg - 背景色
  • --vp-c-divider - 分割线颜色
  • --vp-c-text-1 - 主文本颜色
  • --vp-c-brand - 品牌色
  • --vp-code-block-bg - 代码块背景

常见问题

Q: 为什么要使用 ?raw 而不是直接读取文件?

A: 使用 ?raw 可以利用 Vite 的模块系统,获得以下好处:

  • HMR (热模块替换) 支持
  • 构建时优化
  • 模块缓存

Q: 可以显示多个代码示例吗?

A: 可以!只需要多次使用 CodePreview 组件:

vue
<script setup>
import Demo1 from '../../demos/demo1.tsx'
import Demo1Source from '../../demos/demo1.tsx?raw'
import Demo2 from '../../demos/demo2.tsx'
import Demo2Source from '../../demos/demo2.tsx?raw'
import CodePreview from '../../.vitepress/theme/components/CodePreview.vue'
</script>

<CodePreview :component="Demo1" :source="Demo1Source" />
<CodePreview :component="Demo2" :source="Demo2Source" />

Q: 如何默认展开源码?

A: 设置 default-show-codetrue

vue
<CodePreview
  :component="ButtonDemo"
  :source="ButtonDemoSource"
  :default-show-code="true"
/>

Q: 支持 TypeScript 高亮吗?

A: 支持!VitePress 使用 Shiki 进行代码高亮,自动识别 tsx 代码块。

迁移指南

从旧的 ReactDemo 方式迁移

旧方式:

vue
<script setup>
import ButtonDemo from '../../demos/button-basic.tsx'
import { defineComponent, h, onMounted, onBeforeUnmount } from 'vue'
import { createRoot } from 'react-dom/client'
import React from 'react'

const ReactDemo = defineComponent({
  name: 'ReactDemo',
  setup() {
    let root = null
    let container = null
    onMounted(() => {
      container = document.createElement('div')
      document.querySelector('#button-demo').appendChild(container)
      root = createRoot(container)
      root.render(React.createElement(ButtonDemo))
    })
    onBeforeUnmount(() => {
      if (root) root.unmount()
      if (container && container.parentNode) {
        container.parentNode.removeChild(container)
      }
    })
    return () => h('div', { id: 'button-demo' })
  }
})
</script>

<ReactDemo />

新方式:

vue
<script setup>
import ButtonDemo from '../../demos/button-basic.tsx'
import ButtonDemoSource from '../../demos/button-basic.tsx?raw'
import CodePreview from '../../.vitepress/theme/components/CodePreview.vue'
</script>

<CodePreview :component="ButtonDemo" :source="ButtonDemoSource" :default-show-code="false" />

优势:

  • 代码量减少 80%+
  • 自动显示源码
  • 支持代码折叠/展开
  • 内置复制功能
  • 更好的用户体验

下一步

  • 为所有组件文档添加 CodePreview
  • 根据需要自定义样式
  • 考虑添加更多功能(如代码编辑、在线运行等)

基于 MIT 许可发布