Skip to content

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 3UI 框架组件系统、响应式、SSR
Markdown-itMarkdown 解析可扩展的解析器,支持插件
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

相关链接

贡献者

加载中...

想要成为贡献者?

在 CNB 上参与贡献