13 KiB
title, date
| title | date |
|---|---|
| VitePress简介和使用 | 2025-12-18 |
一、简介
VitePress是由 Vue 团队开发的静态网站生成器 (SSG),基于 Vite(前端构建工具)和 Vue 3 构建,专门用于快速搭建文档网站、博客或个人主页。它继承了 Vite 的极速开发体验,同时具备 Vue 的组件化能力,语法上兼容 Markdown 并支持扩展 Vue 组件,是替代 VuePress 的新一代工具。
二、快速开始
2.1 安装
pnpm add -D vitepress@next
2.2 初始化
pnpm vitepress init
::: tip
建议按照官网配置,文档目录设置为./docs。
:::
2.3 运行
pnpm run docs:dev
2.4 使用
在docs文件夹新建md文档,并在.vitepress/config.mts中配置nav和sidebar路径。
三、优化
3.1 路由组件
每次新建文档时,都需要在.vitepress/config.mts重复nav和sidebar路径,有时还需要在文档目录的index.md配置导航链接,每次都要重复写3次,很麻烦,因此可以构造一个路由配置清单和路由组件,每次只需更新一次路由配置清单即可。
在.vitepress/theme/router文件夹下新建路由清单文件:
export const routers = [
{
text: '🌐 Web前端',
items: [
{
text: '🌿 Vue',
items: [
{ text: 'Test', link: '/Web-Front/Vue/Test' }
]
},
{
text: '🔄 其他',
items: [
{ text: 'Test', link: '/Web-Front/Others/Test' }
]
},
]
},
{
text: '🖥️ Web后端',
items: [
{
text: '🍃 SpringBoot',
items: [
{ text: 'Test', link: '/Web-Backend/SpringBoot/Test' }
]
},
{
text: '🐍 FastAPI',
items: [
{ text: 'Test', link: '/Web-Backend/FastAPI/Test' }
]
},
{
text: '🔄 其他',
items: [
{ text: 'Test', link: '/Web-Backend/Others/Test' }
]
},
]
},
{
text: '🚀 DevOps',
items: [
{ text: 'Test', link: '/DevOps/Test' }
]
}
];
这里列举了有二级菜单和只有一级菜单的两种情况。
在.vitepress/theme/components文件夹下新建路由组件MenuList.vue:
<template>
<div v-if="isNested">
<div v-for="category in props.routers.items" :key="category.text">
<h3>{{ category.text }}</h3>
<ul>
<li v-for="item in category.items" :key="item.text">
<a :href="withBase(item.link)">{{ item.text }}</a>
</li>
</ul>
</div>
</div>
<div v-else>
<ul>
<li v-for="item in props.routers.items" :key="item.text">
<a :href="withBase(item.link)">{{ item.text }}</a>
</li>
</ul>
</div>
</template>
<script setup>
import { withBase } from 'vitepress'
import { defineProps } from 'vue'
const props = defineProps({
routers: {
required: true,
type: Object
},
isNested: {
type: Boolean,
default: false
}
})
</script>
这里需要使用withBase拼接路由,防止存在设置了base根路径的情况。通过isNested来控制是否存在二级菜单。
::: warning
这里只考虑了最多存在二级菜单的情况。
:::
在.vitepress/theme/index.ts注册组件:
import MenuList from './components/MenuList.vue'
import type { EnhanceAppContext } from 'vitepress'
export default {
enhanceApp({ app }: EnhanceAppContext) {
app.component("MenuList", MenuList);
},
};
在.vitepress/config.mts中配置nav和sidebar:
export default defineConfig({
themeConfig: {
nav: [
{ text: '🏠 Home', link: '/' },
...routers
],
sidebar: {
'/Web-Front/': [routers[0]],
'/Web-Backend/': [routers[1]],
'/DevOps/': [routers[2]]
},
},
lastUpdated: true,
})
在文档所在目录的index.md中配置:
---
layout: doc
title: Web前端
description: Web前端开发技术文档
---
<script setup>
import { routers } from '../.vitepress/theme/router'
</script>
<MenuList :routers=routers[0] :isNested=true />
这样后续只需要维护路由配置清单文件即可自动生成路由菜单。
3.2 博客元数据
可以通过在每个博客中添加frontmatter元数据,在最顶部添加博客属性,例如创建日期、字数、时长和是否精品等信息。
3.2.1 定义元数据
在每个博客最上面添加元数据:
---
title: Docker简介和安装
date: 2025-12-15
isGreat: true
---
这里可以自定义添加任何key: value形式的字段信息。
::: tip
建议手动加上每个博客的创建日期
:::
3.2.2 博客信息组件
在.vitepress/theme/components文件夹下新建博客信息组件ArticleMetadata.vue
<template>
<div class="title">
<span>{{ page.frontmatter.title }}</span>
</div>
<div v-if="!page.filePath.includes('index.md')" class="blog-stats">
<div v-if="page.frontmatter.isGreat" class="item">
</svg>
精品
</div>
<div class="item">
</svg>
日期: {{ page.frontmatter.date.split('T')[0] }}
</div>
<div class="item">
</svg>
字数: {{ formatNumberUnit(wordCount) }} 字
</div>
<div>
</svg>
时长: {{ readTime }} 分钟
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, onMounted, watch } from 'vue'
import { countWord, formatNumberUnit } from '../utils'
import { useData } from 'vitepress'
const wordCount = ref(0)
const imageCount = ref(0)
// 获取页面数据
const { page } = useData()
// 文字阅读时间
const wordTime = computed(() => {
return ((wordCount.value / 275) * 60)
})
// 图片阅读时间
const imageTime = computed(() => {
const n = imageCount.value
if (imageCount.value <= 10) {
// 等差数列求和
return n * 13 + (n * (n - 1)) / 2
}
return 175 + (n - 10) * 3
})
// 阅读时间
const readTime = computed(() => {
return Math.ceil((wordTime.value + imageTime.value) / 60)
})
const analyze = () => {
// 选择文档内容区域
const docDomContainer = window.document.querySelector('#VPContent')
// 统计图片数量
const imgs = docDomContainer?.querySelectorAll<HTMLImageElement>('.content-container .main img')
imageCount.value = imgs?.length || 0
// 统计文字数量(textContent:提取纯文本)
const words = docDomContainer?.querySelector('.content-container .main')?.textContent || ''
wordCount.value = countWord(words)
}
watch(() => page.value.title, () => {
// 路径变化时执行
analyze()
})
onMounted(() => {
// 初始化时执行一次
analyze()
})
</script>
<style scoped>
.blog-stats {
margin-bottom: 10px;
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.title {
margin-bottom: 5px;
padding: 10px 0;
text-align: center;
font-size: 32px;
color: #fff;
font-weight: bold;
background: -webkit-linear-gradient(10deg, #3DC8F0 5%, #8036FA 15%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.icon {
display: inline-block;
transform: translate(0px , 2px);
}
</style>
这里统计了正文部分的文字和图片数量,并通过相应规则计算阅读时间。
通过page.frontmatter获取自定义的元数据信息并在博客最上面显示。
3.2.3 注册组件
在.vitepress/theme/index.ts注册组件(参考3.1节内容)。
3.2.4 自定义布局组件
需要将博客信息组件放在自定义布局组件中。
在.vitepress/theme/components文件夹下新建自定义布局组件MyLayout.vue。
<template>
<DefaultTheme.Layout v-bind="$attrs">
<template #doc-before>
<ArticleMetadat />
</template>
</DefaultTheme.Layout>
</template>
<script setup lang="ts">
import DefaultTheme from 'vitepress/theme'
import ArticleMetadat from './ArticleMetadata.vue';
</script>
更多插槽可以参考Github源码。
同样需要注册组件。
3.2.5 配置布局组件
在.vitepress/config.mts中配置布局:
import DefaultTheme from 'vitepress/theme'
import MyLayout from './components/MyLayout.vue';
export default {
extends: DefaultTheme,
Layout: MyLayout,
};
3.3 热力图
使用vue3-calendar-heatmap组件在首页绘制博客热力图。
3.3.1 安装
pnpm install vue3-calendar-heatmap
3.3.2 获取数据
使用VitePress提供的createContentLoader来获取所有的md博客数据。
在.vitepress/data新建posts.data.js:
import { createContentLoader } from 'vitepress'
export default createContentLoader('./**/*.md', {
includeSrc: true, // 包含原始 markdown 源
render: true, // 包含渲染的整页 HTML
transform(rawData) {
return rawData.filter(page => {
return page.url.includes('.html') && page.frontmatter.date
}).map((page) => {
// 对每个页面进行处理
return {
// 返回你需要的页面数据
title: page.frontmatter.title,
date: page.frontmatter.date,
url: page.url
}
})
}
})
通过判断路径是否包含html来确认是博客文档,并且筛选出元数据中包含日期的博客,然后返回相应内容。
3.3.3 博客统计组件
在.vitepress/theme/components文件夹下新建博客信息组件StatsChart.vue:
<template>
<div v-if="!isMobile()">
<calendar-heatmap
:values="heatmapData"
:end-date="new Date()"
no-data-text="暂无记录"
tooltip-unit="篇"
:round="2"
:locale="{
less: '少于',
more: '多于',
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
days: ['日', '一', '二', '三', '四', '五', '六'],
}"
class="blog-heatmap"
/>
<div class="heatmap-footer">
⏱️ 已坚持<span class="highlight">{{ getDaysDifference(new Date(), startBlogDate) }}</span> 天
| 📝 已创作<span class="highlight">{{ data.length }}</span> 篇
</div>
</div>
</template>
<script setup lang="ts">
import { CalendarHeatmap } from 'vue3-calendar-heatmap';
import 'vue3-calendar-heatmap/dist/style.css';
import { data } from '../../data/posts.data.js'
import { isMobile, getDaysDifference, startBlogDate } from "../utils";
const dateCountMap = new Map<string, number>();
// 按日期分组统计
data.forEach(item => {
const date = item.date
if (dateCountMap.has(date)) {
dateCountMap.set(date, dateCountMap.get(date)! + 1);
} else {
dateCountMap.set(date, 1);
}
});
// 转换为目标格式
const heatmapData = Array.from(dateCountMap.entries()).map(([date, count]) => ({
date,
count
}))
</script>
<style scoped>
.blog-heatmap {
margin-top: 10px;
}
.blog-heatmap :deep(.vch__wrapper) {
font-family: 'CustomFont', sans-serif !important;
}
.blog-heatmap :deep(.vch__month__label) {
font-size: 8px !important;
}
.blog-heatmap :deep(.vch__day__label) {
font-size: 8px !important;
}
.heatmap-footer {
font-size: 15px;
color: #666;
display: flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
}
.highlight {
font-weight: 700;
color: #6366f1;
margin: 0 2px;
font-size: 16px;
}
</style>
根据posts.data.js提供的博客数据进行日期分组,并统计已坚持天数和已创作博客数。
3.3.4 注册组件
在.vitepress/theme/index.ts注册组件(参考3.1节内容)。
3.3.5 使用组件
在docs/index.md最后添加组件。
::: tip
如果在打包时遇到Named export 'CalendarHeatmap' not found. The requested module 'vue3-calendar-heatmap' is a CommonJS module, which may not support all module.exports as named exports.错误提示,说明vue3-calendar-heatmap是CommonJS模块,而你在代码中使用了ES模块的命名导入方式,需要在config.mts中添加以下配置:
export default defineConfig({
vite: {
ssr: {
noExternal: ['vue3-calendar-heatmap']
}
}
})
:::
3.4 Mermaid 图表
3.4.1 安装
pnpm i vitepress-plugin-mermaid mermaid -D
3.4.2 配置
在.vitepress/config.mts中配置:
import { withMermaid } from 'vitepress-plugin-mermaid'
// https://vitepress.dev/reference/site-config
export default withMermaid({
title: "拾光记"
})
3.4.3 使用
在Markdown中使用:
'''mermaid
graph TD
A[Vue 3] --> B[Compiler]
A --> C[Runtime]
A --> D[Reactivity]
B --> B1[Parser]
B --> B2[Transformer]
B --> B3[Codegen]
C --> C1[Virtual DOM]
C --> C2[Renderer]
C --> C3[Component]
D --> D1[Proxy]
D --> D2[Effect]
D --> D3[Dependency Graph]
B3 -->|生成| C1
C1 -->|Diff/Patch| C2
D3 -->|驱动更新| C1
'''