VitePress 原理与架构
深入了解 VitePress 的底层原理,帮助你更好地理解框架行为、排查问题和进行高级定制。
核心架构
整体架构图
mermaid
graph TB
subgraph 构建时
MD[Markdown 文件] --> MDIt[Markdown-it 解析]
MDIt --> VueComp[Vue SFC 编译]
VueComp --> SSR[Node.js SSR]
SSR --> HTML[静态 HTML]
end
subgraph 运行时
HTML --> Hydration[客户端 Hydration]
Hydration --> SPA[Vue SPA 应用]
end
subgraph 开发模式
Vite[Vite Dev Server] --> HMR[热模块替换]
MD --> Vite
end技术栈依赖
| 技术 | 用途 | 说明 |
|---|---|---|
| Vite | 构建工具 | 提供开发服务器、HMR、生产构建 |
| Vue 3 | UI 框架 | 组件系统、响应式、SSR |
| Markdown-it | Markdown 解析 | 可扩展的解析器,支持插件 |
| Shiki | 代码高亮 | 基于 VS Code 语法的精确高亮 |
SSG 渲染流程
构建时渲染(SSR)
VitePress 在构建时通过 Node.js 端的 Vue SSR 将每个页面渲染为静态 HTML:
1. 读取 .md 文件
↓
2. Markdown-it 解析为 HTML
↓
3. 包装为 Vue SFC(单文件组件)
↓
4. Vue 编译器编译为渲染函数
↓
5. Node.js 端执行渲染函数
↓
6. 输出静态 HTML + 序列化数据客户端 Hydration
页面加载后,Vue 在浏览器端进行 Hydration,将静态 HTML "激活"为响应式应用:
typescript
// 简化的 Hydration 过程
// 1. 浏览器加载静态 HTML(首屏即时显示)
// 2. 加载 JS bundle
// 3. Vue 对比虚拟 DOM 和真实 DOM
// 4. 绑定事件监听器
// 5. 页面变为可交互的 SPA为什么 SSG 既快又交互?
SSG 结合了静态页面的加载速度和 SPA 的交互体验:
- 首次加载:直接返回预渲染的 HTML,无需等待 JS 执行
- 后续导航:Vue Router 接管,实现 SPA 式的客户端路由
Markdown 处理管线
Markdown 到 Vue 的转换
VitePress 将每个 .md 文件转换为 Vue 组件:
输入:docs/guide/getting-started.md
↓ Markdown-it 解析
↓ 提取 Frontmatter
↓ 转换自定义语法(容器、代码组等)
↓ 包装为 Vue SFC
输出:
<script setup>
// Frontmatter 数据注入
const __pageData = { ... }
</script>
<template>
<Content />
</template>Markdown-it 插件体系
VitePress 通过 Markdown-it 插件扩展 Markdown 功能:
| 插件 | 功能 | 语法示例 |
|---|---|---|
| 内置容器 | tip/warning/info 容器 | ::: tip |
| 内置代码组 | 多语言代码切换 | ::: code-group |
| 内置行高亮 | 代码行标记 | ```ts{1,3-5} |
| 内置数学公式 | LaTeX 数学 | $E=mc^2$ |
| Mermaid | 图表渲染 | ```mermaid |
| 自定义插件 | 任意扩展 | 通过 markdown.config 配置 |
自定义 Markdown-it 插件
typescript
// .vitepress/config.mts
import { defineConfig } from 'vitepress'
export default defineConfig({
markdown: {
config(md) {
// 添加自定义 Markdown-it 插件
md.use(myCustomPlugin)
// 添加自定义规则
md.inline.ruler.before('emphasis', 'myRule', (state, silent) => {
// 解析逻辑
return false
})
}
}
})路由系统
文件到路由的映射
VitePress 基于文件系统自动生成路由:
docs/
├── index.md → /
├── guide/
│ ├── index.md → /guide/
│ ├── installation.md → /guide/installation
│ └── advanced/
│ └── ssr.md → /guide/advanced/ssr
└── api/
└── index.md → /api/路由重写
通过 rewrites 配置修改路由映射:
typescript
export default defineConfig({
rewrites: {
'zh/guide/:id': 'guide/:id',
'en/guide/:id': 'en/guide/:id'
}
})动态路由
使用数据加载器创建动态路由:
typescript
// posts/[id].paths.ts
import { createContentLoader } from 'vitepress'
export default {
async paths() {
const posts = await createContentLoader('blog/*.md')
return posts.map(post => ({
params: { id: post.url.split('/').pop() },
content: post.excerpt
}))
}
}数据加载机制
createContentLoader
VitePress 提供了 createContentLoader 用于批量加载和处理 Markdown 文件:
typescript
// .vitepress/data/posts.data.ts
import { createContentLoader } from 'vitepress'
export default createContentLoader('blog/*.md', {
// 包含原文
includeSrc: true,
// 提取摘要
excerpt: true,
// 自定义转换
transform(data) {
return data
.filter(page => page.frontmatter.draft !== true)
.sort((a, b) =>
new Date(b.frontmatter.date) - new Date(a.frontmatter.date)
)
.map(page => ({
title: page.frontmatter.title,
url: page.url,
date: page.frontmatter.date,
excerpt: page.excerpt,
tags: page.frontmatter.tags || []
}))
}
})数据加载时机
mermaid
sequenceDiagram
participant Dev as 开发服务器
participant Loader as 数据加载器
participant File as Markdown 文件
Dev->>Loader: 启动/文件变更
Loader->>File: 读取所有匹配文件
File-->>Loader: 返回原始内容
Loader->>Loader: 执行 transform
Loader-->>Dev: 返回处理后的数据
Dev->>Dev: 热更新页面主题系统架构
主题入口解析
typescript
// VitePress 解析主题的顺序
// 1. 查找 .vitepress/theme/index.ts (或 .js)
// 2. 如果没有自定义主题,使用默认主题
// 3. 如果 extends: DefaultTheme,合并配置
interface Theme {
extends?: Theme // 继承的主题
Layout?: Component // 布局组件
NotFound?: Component // 404 页面
enhanceApp?: (ctx) => void // 应用增强
setup?: () => void // 主题 setup
}布局组件渲染流程
1. 路由匹配 → 确定当前页面组件
↓
2. 读取 Frontmatter → 确定 layout 类型 (doc/home/page)
↓
3. Layout 组件渲染 → 选择对应布局模板
↓
4. 填充插槽 → 自定义内容注入
↓
5. Content 组件渲染 → 页面 Markdown 内容构建优化
代码分割策略
VitePress 在构建时自动进行代码分割:
| 分割策略 | 说明 |
|---|---|
| 按页面分割 | 每个页面独立 chunk,按需加载 |
| 按组件分割 | 大型组件异步加载 |
| 框架代码分割 | Vue 运行时独立打包 |
| 样式分割 | CSS 按页面提取 |
预加载机制
typescript
// VitePress 内置的预加载策略
// 1. 当用户悬停链接时,预加载目标页面
router.onAfterPageLoad = () => {
// 预加载下一页可能需要的资源
}
// 2. 使用 <link rel="prefetch"> 预加载
// 在构建时自动为所有页面生成 prefetch 链接MPA 模式
当不需要 SPA 行为时,可以启用 MPA 模式:
typescript
export default defineConfig({
mpa: true // 多页面应用模式,无 JS 的纯静态页面
})MPA 模式特点:
| 特性 | SPA 模式 | MPA 模式 |
|---|---|---|
| 页面切换 | 客户端路由 | 完整页面刷新 |
| JS 体积 | 较大 | 极小 |
| 交互能力 | 完整 Vue 体验 | 基础功能 |
| 首屏速度 | 快 | 更快 |
| 适用场景 | 文档站、博客 | 内容站、SEO 优先 |
构建钩子详解
钩子执行顺序
mermaid
sequenceDiagram
participant Config as 配置解析
participant Head as transformHead
participant Page as transformPageData
participant HTML as transformHtml
participant Build as buildEnd
participant Post as postBuild
Config->>Page: 解析每个页面
Page->>Head: 生成 head 标签
Head->>HTML: 转换 HTML 输出
HTML->>Build: 所有页面处理完成
Build->>Post: 构建后续操作钩子详解
typescript
export default defineConfig({
// 修改每个页面的 Frontmatter
transformPageData(pageData, { siteConfig }) {
pageData.frontmatter.layout ??= 'doc'
},
// 动态添加 head 标签
async transformHead({ pageData, siteConfig }) {
return [
['meta', { property: 'og:title', content: pageData.title }]
]
},
// 修改构建输出的 HTML
transformHtml(code, id, { pageData }) {
if (id.endsWith('.html')) {
return code.replace('</head>', '<!-- injected -->\n</head>')
}
},
// 构建结束回调
async buildEnd(siteConfig) {
// 生成 sitemap、RSS 等
}
})开发模式 vs 生产模式
| 特性 | 开发模式 (vitepress dev) | 生产模式 (vitepress build) |
|---|---|---|
| Markdown 处理 | 按需编译,HMR | 全量预渲染为 HTML |
| Vue 组件 | 浏览器端编译 | 预编译 + SSR |
| 样式 | 动态注入 | 提取为 CSS 文件 |
| 数据加载 | 文件变更时重新加载 | 构建时一次性加载 |
| 路由 | Vite Dev Server 代理 | 静态 HTML 文件 |
调试技巧
查看编译后的 Vue 代码
bash
# 开发模式下,在浏览器 DevTools 中查看
# Vite 的 /@id/ 前缀可以访问编译后的模块查看数据加载结果
vue
<script setup>
import { data } from './data.data.ts'
</script>
<template>
<pre>{{ JSON.stringify(data, null, 2) }}</pre>
</template>构建分析
typescript
// .vitepress/config.mts
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
vite: {
plugins: process.env.ANALYZE
? [visualizer({ filename: 'stats.html', open: true })]
: []
}
})bash
ANALYZE=true npm run build