Skip to content

扩展默认主题

VitePress 的默认主题针对文档进行了优化,并且可以自定义。请查阅默认主题配置概述以获取完整的选项列表。

但是,在许多情况下,仅仅配置是不够的。例如:

  1. 你需要调整 CSS 样式;
  2. 你需要修改 Vue 应用实例,例如注册全局组件;
  3. 你需要通过布局插槽将自定义内容注入到主题中。

这些高级自定义将需要使用自定义主题来"扩展"默认主题。

提示

在继续进行自定义主题之前,请确保首先查看使用自定义 CSS主题配置页面,看看是否可以通过这些方式满足你的自定义需求。

主题解析

你可以通过在 .vitepress/theme/index.js.vitepress/theme/index.ts("主题入口文件")中创建一个主题对象来启用自定义主题:

.
├─ docs                # 项目根目录
│  ├─ .vitepress
│  │  ├─ theme
│  │  │  └─ index.js   # 主题入口
│  │  └─ config.js     # 配置文件
│  └─ index.md
└─ package.json

VitePress 主题只是一个包含四个属性的对象,定义如下:

ts
interface Theme {
  Layout: Component // Vue 3 组件
  NotFound?: Component
  enhanceApp?: (ctx: EnhanceAppContext) => Awaitable<void>
  setup?: () => void
}

interface EnhanceAppContext {
  app: App // Vue 3 应用实例
  router: Router // VitePress 路由器实例
  siteData: Ref<SiteData> // 站点级元数据
}

主题入口文件应该将主题导出为其默认导出:

js
// .vitepress/theme/index.js

// 你可以直接导入 Vue 文件
// VitePress 已预配置 @vitejs/plugin-vue
import Layout from './Layout.vue'
import NotFound from './NotFound.vue'

export default {
  Layout,
  NotFound,
  enhanceApp({ app, router, siteData }) {
    // ...
  }
}

默认导出是自定义主题的唯一约定。在你的自定义主题中,它就像一个普通的 Vite + Vue 3 应用程序一样工作。请注意,主题还需要兼容 SSR

要分发主题,只需在你的包入口中导出对象。要使用外部主题,请从自定义主题入口导入并重新导出它:

js
// .vitepress/theme/index.js
import Theme from 'awesome-vitepress-theme'

export default Theme

扩展默认主题

如果你想扩展和自定义默认主题,你可以从 vitepress/theme 导入它并在自定义主题中扩展它:

js
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'

export default DefaultTheme

现在让我们说你想添加一些自定义 CSS:

js
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
import './custom.css'

export default DefaultTheme

如果你想注册全局组件:

js
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'

export default {
  extends: DefaultTheme,
  enhanceApp({ app }) {
    // 注册自定义全局组件
    app.component('MyGlobalComponent', /* ... */)
  }
}

如果你想修改默认主题:

js
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
import MyLayout from './MyLayout.vue'

export default {
  extends: DefaultTheme,
  // 覆盖 Layout 以提供插槽
  Layout: MyLayout
}

自定义 CSS

默认主题 CSS 可以通过覆盖根级 CSS 变量来自定义:

css
/* .vitepress/theme/custom.css */

:root {
  --vp-c-brand-1: #646cff;
  --vp-c-brand-2: #747bff;
}

查看默认主题 CSS 变量,这些变量可以被覆盖。

使用不同的字体

VitePress 默认使用 Inter 作为字体,并将在构建输出中包含字体。字体也会在生产中自动预加载。但是,如果你想使用不同的主字体,这可能不是理想的。

要避免在构建输出中包含 Inter,请从 vitepress/theme-without-fonts 导入主题:

js
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme-without-fonts'
import './my-fonts.css'

export default DefaultTheme
css
/* .vitepress/theme/my-fonts.css */

/* 注册你的自定义字体系列并告诉 VitePress 使用它 */
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
:root {
  --vp-font-family-base: 'Roboto', sans-serif;
}

警告

如果你使用可选组件,如团队页面组件,请确保也从 vitepress/theme-without-fonts 导入它们!

如果你的字体是通过 @font-face 引用的本地文件,它将作为资源处理并包含在 .vitepress/dist/assets/ 下,并带有散列文件名。要预加载此文件,请使用 transformHead 构建钩子:

js
// .vitepress/config.js
export default {
  transformHead({ assets }) {
    // 相应地调整正则表达式以匹配你的字体
    const myFontFile = assets.find(file => /font-name\.\w+\.woff2/)
    if (myFontFile) {
      return [
        [
          'link',
          {
            rel: 'preload',
            href: myFontFile,
            as: 'font',
            type: 'font/woff2',
            crossorigin: ''
          }
        ]
      ]
    }
  }
}

布局插槽

默认主题的 <Layout /> 组件有一些插槽,可以用来在页面的某些位置注入内容。这里是一个在大纲前注入组件的例子:

js
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
import MyLayout from './MyLayout.vue'

export default {
  extends: DefaultTheme,
  // 覆盖 Layout 以提供插槽
  Layout: MyLayout
}
vue
<!--.vitepress/theme/MyLayout.vue-->
<script setup>
import DefaultTheme from 'vitepress/theme'

const { Layout } = DefaultTheme
</script>

<template>
  <Layout>
    <template #aside-outline-before>
      My custom sidebar top content
    </template>
  </Layout>
</template>

或者你可以使用渲染函数。

js
// .vitepress/theme/index.js
import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import MyComponent from './MyComponent.vue'

export default {
  extends: DefaultTheme,
  Layout() {
    return h(DefaultTheme.Layout, null, {
      // https://cn.vuejs.org/guide/extras/render-function.html#named-slots
      'aside-outline-before': () => h(MyComponent)
    })
  }
}

默认主题布局的完整插槽列表:

  • layout: 'doc'(默认)在 frontmatter 中激活时:
    • doc-top
    • doc-bottom
    • doc-footer-before
    • doc-before
    • doc-after
    • sidebar-nav-before
    • sidebar-nav-after
    • aside-top
    • aside-bottom
    • aside-outline-before
    • aside-outline-after
    • aside-ads-before
    • aside-ads-after
  • layout: 'home' 在 frontmatter 中激活时:
    • home-hero-before
    • home-hero-info-before
    • home-hero-info
    • home-hero-info-after
    • home-hero-actions-after
    • home-hero-image
    • home-hero-after
    • home-features-before
    • home-features-after
  • layout: 'page' 在 frontmatter 中激活时:
    • page-top
    • page-bottom
  • 在 not found (404) 页面上:
    • not-found
  • 总是:
    • layout-top
    • layout-bottom
    • nav-bar-title-before
    • nav-bar-title-after
    • nav-bar-content-before
    • nav-bar-content-after
    • nav-screen-content-before
    • nav-screen-content-after

全局组件注册

enhanceApp 函数是注册全局 Vue 组件的地方:

js
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'

export default {
  extends: DefaultTheme,
  enhanceApp({ app }) {
    app.component('VueClickAwayExample', VueClickAwayExample)
  }
}

如果你使用 TypeScript:

ts
// .vitepress/theme/index.ts
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import VueClickAwayExample from './components/VueClickAwayExample.vue'

export default {
  extends: DefaultTheme,
  enhanceApp({ app }) {
    app.component('VueClickAwayExample', VueClickAwayExample)
  }
} satisfies Theme

由于我们使用 Vite,你也可以利用 Vite 的 glob 导入功能 来自动注册组件目录。

vitepress开发指南