Skip to content

Monorepo 文档管理

在 Monorepo 架构中,多个包可能需要共享文档或各自维护文档。本文介绍如何在 Monorepo 中高效管理 VitePress 文档。

常见架构模式

模式一:集中式文档

text
monorepo/
├── packages/
│   ├── core/
│   ├── utils/
│   └── ui/
└── docs/                    # 集中式文档站
    ├── .vitepress/
    ├── guide/
    ├── packages/            # 各包的文档
    │   ├── core.md
    │   ├── utils.md
    │   └── ui.md
    └── index.md

适用场景:包之间关联紧密,文档需要交叉引用。

模式二:分布式文档

text
monorepo/
├── packages/
│   ├── core/
│   │   ├── src/
│   │   └── docs/            # core 的独立文档
│   │       ├── .vitepress/
│   │       └── index.md
│   ├── utils/
│   │   └── docs/            # utils 的独立文档
│   │       ├── .vitepress/
│   │       └── index.md
│   └── ui/
│       └── docs/            # ui 的独立文档
│           ├── .vitepress/
│           └── index.md
└── docs/                    # 根文档站(汇总入口)
    ├── .vitepress/
    └── index.md

适用场景:各包相对独立,需要独立部署文档。

模式三:混合模式

text
monorepo/
├── packages/
│   ├── core/
│   │   └── docs/            # 核心包有独立文档
│   └── ui/
└── docs/                    # 主文档站(包含共享内容和子包引用)
    ├── .vitepress/
    ├── packages/
    │   ├── core.md          # 可从 core/docs 同步
    │   └── ui.md
    └── index.md

适用场景:部分包需要独立文档,部分包共享主文档站。

pnpm Workspace 方案

项目结构

text
monorepo/
├── pnpm-workspace.yaml
├── package.json
├── packages/
│   ├── core/
│   └── ui/
└── docs/
    ├── package.json
    └── .vitepress/

workspace 配置

yaml
# pnpm-workspace.yaml
packages:
  - 'packages/*'
  - 'docs'

docs 的 package.json

json
{
  "name": "@myrepo/docs",
  "private": true,
  "scripts": {
    "dev": "vitepress dev",
    "build": "vitepress build",
    "preview": "vitepress preview"
  },
  "dependencies": {
    "@myrepo/core": "workspace:*",
    "@myrepo/ui": "workspace:*"
  },
  "devDependencies": {
    "vitepress": "^2.0.0"
  }
}

workspace 协议

使用 workspace:* 协议直接引用 Monorepo 内的其他包,无需发布到 npm。

在文档中引用包内容

typescript
// docs/.vitepress/config.mts
import { version } from '@myrepo/core/package.json'

export default defineConfig({
  themeConfig: {
    nav: [
      { text: `Core v${version}`, link: '/packages/core' }
    ]
  }
})

Turborepo 方案

项目结构

text
monorepo/
├── turbo.json
├── package.json
├── apps/
│   └── docs/                # 文档应用
│       ├── package.json
│       └── .vitepress/
└── packages/
    ├── core/
    └── ui/

turbo.json 配置

json
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".vitepress/dist/**"]
    },
    "dev": {
      "dependsOn": ["^build"],
      "persistent": true,
      "cache": false
    }
  }
}

文档构建任务

json
// apps/docs/package.json
{
  "name": "@myrepo/docs",
  "scripts": {
    "dev": "vitepress dev",
    "build": "vitepress build",
    "preview": "vitepress preview"
  }
}

执行构建

bash
# 构建所有包和文档
turbo build

# 仅开发文档
turbo dev --filter=@myrepo/docs

# 构建文档及其依赖
turbo build --filter=@myrepo/docs...

自动生成 API 文档

从源码提取 API 文档

typescript
// scripts/gen-api-docs.mts
import fs from 'fs'
import path from 'path'

interface ApiEntry {
  name: string
  type: 'function' | 'interface' | 'type' | 'class'
  description: string
  params?: { name: string; type: string; description: string }[]
  returns?: string
  example?: string
}

function generateApiMarkdown(entries: ApiEntry[], packageName: string): string {
  let md = `---\ntitle: ${packageName} API\ndescription: ${packageName} 包的 API 参考\ntags:\n  - API\n  - ${packageName}\n---\n\n`
  md += `# ${packageName} API\n\n`

  for (const entry of entries) {
    md += `## ${entry.name}\n\n`
    md += `${entry.description}\n\n`

    if (entry.type === 'function' && entry.params) {
      md += `### 参数\n\n`
      md += `| 参数 | 类型 | 说明 |\n`
      md += `|------|------|------|\n`
      for (const param of entry.params) {
        md += `| \`${param.name}\` | \`${param.type}\` | ${param.description} |\n`
      }
      md += '\n'
    }

    if (entry.returns) {
      md += `### 返回值\n\n\`${entry.returns}\`\n\n`
    }

    if (entry.example) {
      md += `### 示例\n\n\`\`\`typescript\n${entry.example}\n\`\`\`\n\n`
    }
  }

  return md
}

// 示例用法
const entries: ApiEntry[] = [
  {
    name: 'createApp',
    type: 'function',
    description: '创建应用实例',
    params: [
      { name: 'config', type: 'AppConfig', description: '应用配置' }
    ],
    returns: 'Application',
    example: "const app = createApp({ title: '我的文档' })"
  }
]

const md = generateApiMarkdown(entries, '@myrepo/core')
fs.writeFileSync('docs/packages/core-api.md', md, 'utf-8')

使用 TypeDoc 生成文档

bash
npm install -D typedoc typedoc-plugin-markdown
json
// typedoc.json
{
  "entryPoints": ["packages/core/src/index.ts"],
  "out": "docs/packages/core-api",
  "plugin": ["typedoc-plugin-markdown"],
  "readme": "none",
  "excludePrivate": true,
  "excludeProtected": true
}

共享主题和组件

共享 VitePress 主题

text
monorepo/
├── packages/
│   └── docs-theme/          # 共享主题包
│       ├── package.json
│       ├── index.ts
│       ├── components/
│       └── styles/
└── apps/
    └── docs/
        └── .vitepress/
            └── theme/
                └── index.ts  # 引用共享主题
typescript
// packages/docs-theme/index.ts
import DefaultTheme from 'vitepress/theme'
import './styles/index.css'

// 导出共享组件
export { default as PackageHeader } from './components/PackageHeader.vue'
export { default as ApiEndpoint } from './components/ApiEndpoint.vue'
export { default as InstallGuide } from './components/InstallGuide.vue'

export default {
  ...DefaultTheme,
  enhanceApp({ app }) {
    // 注册共享组件
    app.component('PackageHeader', () => import('./components/PackageHeader.vue'))
    app.component('ApiEndpoint', () => import('./components/ApiEndpoint.vue'))
    app.component('InstallGuide', () => import('./components/InstallGuide.vue'))
  }
}
typescript
// apps/docs/.vitepress/theme/index.ts
import Theme from '@myrepo/docs-theme'

export default Theme

版本化文档管理

多版本侧边栏

typescript
// .vitepress/config.mts
function getVersionedSidebar(version: string) {
  return [
    {
      text: `${version} 指南`,
      items: [
        { text: '快速开始', link: `/${version}/guide/getting-started` },
        { text: 'API 参考', link: `/${version}/api/` }
      ]
    }
  ]
}

export default defineConfig({
  themeConfig: {
    nav: [
      {
        text: '版本',
        items: [
          { text: 'v3 (最新)', link: '/v3/guide/' },
          { text: 'v2', link: '/v2/guide/' },
          { text: 'v1 (已归档)', link: '/v1/guide/' }
        ]
      }
    ]
  }
})

版本切换组件

vue
<!-- packages/docs-theme/components/VersionSwitcher.vue -->
<script setup lang="ts">
import { useData } from 'vitepress'

const { site } = useData()

const versions = [
  { label: 'v3 (最新)', link: '/v3/' },
  { label: 'v2', link: '/v2/' },
  { label: 'v1', link: '/v1/' }
]
</script>

<template>
  <div class="version-switcher">
    <select @change="($event) => $window.location.href = ($event.target as HTMLSelectElement).value">
      <option
        v-for="v in versions"
        :key="v.link"
        :value="v.link"
      >
        {{ v.label }}
      </option>
    </select>
  </div>
</template>

CI/CD 配置

GitHub Actions

yaml
# .github/workflows/docs.yml
name: Deploy Docs

on:
  push:
    branches: [main]
    paths:
      - 'docs/**'
      - 'packages/*/src/**'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v4
        with:
          version: 9

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm

      - name: Install
        run: pnpm install

      - name: Build packages
        run: pnpm -r --filter='./packages/*' build

      - name: Build docs
        run: pnpm --filter @myrepo/docs build

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: apps/docs/.vitepress/dist

最佳实践

实践说明
文档就近放置各包的文档放在包目录下,便于维护
共享主题提取公共主题和组件到共享包
自动生成 API使用 TypeDoc 等工具从源码生成 API 文档
版本化管理为不同版本维护独立文档
按需构建使用 Turborepo 的 filter 功能只构建变更的文档
链接验证CI 中添加文档链接检查步骤

相关链接

贡献者

加载中...

想要成为贡献者?

在 CNB 上参与贡献