多版本文档管理
本文档介绍如何在 VitePress 中实现多版本文档管理,让用户可以在不同版本之间切换。
概述
为什么需要多版本文档?
- 框架库文档:如 Vue 2 / Vue 3
- API 文档:v1.x / v2.x 接口
- 产品文档:不同产品版本的功能说明
- 历史归档:保留旧版本文档供用户查阅
实现方式
| 方式 | 适用场景 | 复杂度 |
|---|---|---|
| 子目录方式 | 简单多版本 | ⭐ |
| 多仓库方式 | 大型项目 | ⭐⭐⭐ |
| 部署重定向 | 需要独立域名 | ⭐⭐ |
子目录方式(推荐)
目录结构
docs/
├── index.md # 首页(版本选择)
├── v1/ # v1 版本
│ ├── index.md
│ ├── guide/
│ └── api/
├── v2/ # v2 版本
│ ├── index.md
│ ├── guide/
│ └── api/
└── .vitepress/
└── config.mts配置文件
ts
// docs/.vitepress/config.mts
import { defineConfig } from 'vitepress'
export default defineConfig({
title: 'My Project',
// 多语言/多版本配置
locales: {
'/': {
label: 'v2.x',
lang: 'zh-CN',
title: 'My Project v2',
description: '最新版本',
themeConfig: {
nav: [
{ text: '指南', link: '/guide/' },
{ text: 'API', link: '/api/' }
],
sidebar: {
'/guide/': [
{
text: '开始',
items: [
{ text: '介绍', link: '/guide/' },
{ text: '快速开始', link: '/guide/getting-started' }
]
}
]
}
}
},
'/v1/': {
label: 'v1.x',
lang: 'zh-CN',
title: 'My Project v1',
description: '历史版本',
themeConfig: {
nav: [
{ text: '指南', link: '/v1/guide/' },
{ text: 'API', link: '/v1/api/' }
],
sidebar: {
'/v1/guide/': [
{
text: '开始',
items: [
{ text: '介绍', link: '/v1/guide/' },
{ text: '快速开始', link: '/v1/guide/getting-started' }
]
}
]
}
}
}
},
themeConfig: {
// 版本切换器(使用 locales 的 label)
}
})版本选择首页
markdown
<!-- docs/index.md -->
---
layout: home
hero:
name: My Project
text: 多版本文档
tagline: 选择您需要的版本
actions:
- theme: brand
text: v2.x (最新)
link: /guide/
- theme: alt
text: v1.x (稳定)
link: /v1/guide/
---独立配置文件方式
对于差异较大的版本,可以使用独立配置:
目录结构
docs/
├── v1/
│ ├── index.md
│ ├── .vitepress/
│ │ └── config.mts # v1 独立配置
│ └── guide/
├── v2/
│ ├── index.md
│ ├── .vitepress/
│ │ └── config.mts # v2 独立配置
│ └── guide/
└── index.md # 版本选择页独立构建脚本
json
// package.json
{
"scripts": {
"build": "npm run build:v1 && npm run build:v2",
"build:v1": "vitepress build docs/v1 --outDir ../dist/v1",
"build:v2": "vitepress build docs/v2 --outDir ../dist",
"dev:v1": "vitepress dev docs/v1",
"dev:v2": "vitepress dev docs/v2"
}
}版本选择器组件
自定义版本选择器
vue
<!-- docs/.vitepress/theme/components/VersionSelector.vue -->
<script setup>
import { ref } from 'vue'
import { useData } from 'vitepress'
const { lang } = useData()
const versions = [
{ label: 'v2.x (最新)', link: '/guide/' },
{ label: 'v1.x (稳定)', link: '/v1/guide/' },
{ label: 'v0.x (归档)', link: '/v0/guide/' }
]
const isOpen = ref(false)
function toggleDropdown() {
isOpen.value = !isOpen.value
}
function selectVersion(link) {
isOpen.value = false
window.location.href = link
}
</script>
<template>
<div class="version-selector">
<button class="selector-button" @click="toggleDropdown">
<span>{{ lang === 'v1' ? 'v1.x' : 'v2.x' }}</span>
<span class="arrow">▼</span>
</button>
<ul v-show="isOpen" class="dropdown">
<li
v-for="v in versions"
:key="v.link"
@click="selectVersion(v.link)"
>
{{ v.label }}
</li>
</ul>
</div>
</template>
<style scoped>
.version-selector {
position: relative;
margin-right: 12px;
}
.selector-button {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg);
cursor: pointer;
font-size: 14px;
}
.arrow {
font-size: 10px;
}
.dropdown {
position: absolute;
top: 100%;
left: 0;
min-width: 150px;
padding: 8px 0;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
box-shadow: var(--vp-shadow-2);
list-style: none;
margin: 4px 0 0;
z-index: 100;
}
.dropdown li {
padding: 8px 16px;
cursor: pointer;
transition: background 0.2s;
}
.dropdown li:hover {
background: var(--vp-c-bg-soft);
}
</style>集成到导航栏
ts
// docs/.vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import VersionSelector from './components/VersionSelector.vue'
import { h } from 'vue'
export default {
extends: DefaultTheme,
Layout: () => h(DefaultTheme.Layout, null, {
'nav-bar-content-before': () => h(VersionSelector)
})
}版本徽章
添加版本徽章
vue
<!-- docs/.vitepress/theme/components/VersionBadge.vue -->
<script setup>
defineProps({
version: {
type: String,
default: 'latest'
}
})
const versionColors = {
latest: 'tip',
stable: 'info',
deprecated: 'warning',
archived: 'danger'
}
</script>
<template>
<span
class="version-badge"
:class="versionColors[version]"
>
{{ version }}
</span>
</template>
<style scoped>
.version-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
.tip {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
}
.info {
background: var(--vp-c-info-soft);
color: var(--vp-c-info-1);
}
.warning {
background: var(--vp-c-warning-soft);
color: var(--vp-c-warning-1);
}
.danger {
background: var(--vp-c-danger-soft);
color: var(--vp-c-danger-1);
}
</style>在页面中使用
markdown
# 快速开始 <VersionBadge version="latest" />
本章节介绍最新版本的使用方法。版本间链接处理
跨版本链接警告
vue
<!-- docs/.vitepress/theme/components/VersionLink.vue -->
<script setup>
import { useData } from 'vitepress'
const props = defineProps({
href: String,
text: String
})
const { lang } = useData()
function handleClick(e) {
// 检测是否跨版本
const isCrossVersion = props.href.startsWith('/v') &&
!props.href.startsWith(`/${lang.value}`)
if (isCrossVersion) {
const confirmed = confirm('即将跳转到不同版本的文档,确定继续?')
if (!confirmed) {
e.preventDefault()
}
}
}
</script>
<template>
<a :href="href" @click="handleClick">{{ text }}</a>
</template>版本提示横幅
vue
<!-- docs/.vitepress/theme/components/VersionBanner.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import { useData } from 'vitepress'
const { lang } = useData()
const showBanner = ref(false)
onMounted(() => {
// 检测是否为旧版本
if (lang.value === 'v1') {
showBanner.value = true
}
})
function dismiss() {
showBanner.value = false
}
</script>
<template>
<div v-if="showBanner" class="version-banner">
<span>📚 你正在查看旧版本文档,</span>
<a href="/guide/">查看最新版本</a>
<button @click="dismiss">×</button>
</div>
</template>
<style scoped>
.version-banner {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 24px;
background: var(--vp-c-warning-soft);
border-bottom: 1px solid var(--vp-c-divider);
font-size: 14px;
}
.version-banner a {
color: var(--vp-c-brand-1);
text-decoration: underline;
}
.version-banner button {
margin-left: auto;
background: none;
border: none;
font-size: 20px;
cursor: pointer;
opacity: 0.6;
}
.version-banner button:hover {
opacity: 1;
}
</style>自动版本检测
检测 package.json 版本
ts
// docs/.vitepress/utils/version.ts
import { readFileSync } from 'fs'
import { resolve } from 'path'
export function getPackageVersion() {
try {
const pkg = JSON.parse(
readFileSync(resolve(__dirname, '../../../package.json'), 'utf-8')
)
return pkg.version
} catch {
return 'unknown'
}
}在配置中使用:
ts
// docs/.vitepress/config.mts
import { getPackageVersion } from './utils/version'
export default defineConfig({
define: {
__VERSION__: JSON.stringify(getPackageVersion())
}
})在组件中使用:
vue
<script setup>
declare const __VERSION__: string
</script>
<template>
<footer>当前版本: {{ __VERSION__ }}</footer>
</template>构建与部署
构建脚本
bash
#!/bin/bash
# build.sh
# 清理输出目录
rm -rf dist
# 构建所有版本
echo "Building v2..."
vitepress build docs --outDir dist
echo "Building v1..."
vitepress build docs/v1 --outDir dist/v1
echo "Building v0..."
vitepress build docs/v0 --outDir dist/v0
echo "All versions built!"GitHub Actions 配置
yaml
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
- run: pnpm install
- name: Build all versions
run: |
pnpm build
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./distURL 重定向
Nginx 配置
nginx
# 默认重定向到最新版本
server {
listen 80;
server_name docs.example.com;
location = / {
return 301 /guide/;
}
# v1 版本
location /v1/ {
alias /var/www/v1/;
}
# v2 版本(默认)
location / {
alias /var/www/v2/;
}
}Vercel 配置
json
// vercel.json
{
"rewrites": [
{ "source": "/v1/:path*", "destination": "/v1/:path*" },
{ "source": "/:path*", "destination": "/:path*" }
],
"redirects": [
{ "source": "/", "destination": "/guide/", "permanent": true }
]
}内容同步策略
共享组件
docs/
├── shared/ # 共享内容
│ ├── components/ # 共享组件
│ └── styles/ # 共享样式
├── v1/
│ └── .vitepress/
│ └── config.mts # 引用 shared
└── v2/
└── .vitepress/
└── config.mts # 引用 shared引用共享组件
ts
// docs/v2/.vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import SharedComponent from '../../shared/components/SharedComponent.vue'
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component('SharedComponent', SharedComponent)
}
}最佳实践
1. 版本命名规范
latest- 最新版本v2.x- 主版本v1.x- 旧版本next- 预览版
2. 版本生命周期
| 状态 | 说明 | 徽章颜色 |
|---|---|---|
| Latest | 最新稳定版 | 绿色 |
| Stable | 稳定维护版 | 蓝色 |
| LTS | 长期支持版 | 紫色 |
| Deprecated | 即将废弃 | 橙色 |
| Archived | 已归档 | 灰色 |
3. 版本提示
- 旧版本显示升级提示
- 废弃版本显示迁移指南
- 归档版本标记为只读
4. SEO 考虑
html
<!-- 在旧版本页面添加 canonical 链接 -->
<link rel="canonical" href="https://docs.example.com/guide/" />
<!-- 添加 alternate 链接 -->
<link rel="alternate" hreflang="x-default" href="https://docs.example.com/guide/" />
<link rel="alternate" hreflang="en" href="https://docs.example.com/v1/guide/" />常见问题
Q: 如何自动同步版本间的内容?
A: 可以编写脚本从主版本复制到其他版本:
bash
#!/bin/bash
# sync-content.sh
# 从 v2 复制共享内容到 v1
cp -r docs/shared/* docs/v1/
cp -r docs/shared/* docs/v2/Q: 版本过多导致构建时间长怎么办?
A: 可以并行构建:
json
{
"scripts": {
"build": "npm-run-all --parallel build:*",
"build:v1": "vitepress build docs/v1 --outDir ../dist/v1",
"build:v2": "vitepress build docs/v2 --outDir ../dist"
}
}下一步
学习 运行时 API 了解更多高级功能。