Skip to content

自定义主题

VitePress 主题只是一个对象,包含四个属性:LayoutNotFoundenhanceAppsetup

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> // 站点级元数据
}

最小主题

要创建自定义主题,首先在 .vitepress/theme/index.js.vitepress/theme/index.ts 中创建一个主题入口文件:

js
// .vitepress/theme/index.js
import Layout from './Layout.vue'

export default {
  Layout,
  NotFound: () => 'custom 404', // <- 这是一个 Vue 3 函数组件
  enhanceApp({ app, router, siteData }) {
    // app 是 Vue 3 应用实例,来自 createApp()
    // router 是 VitePress 路由器实例
    // siteData 是当前站点级元数据的 ref
  },
  setup() {
    // 这个函数将在每个 VitePress 页面的 Vue 应用中执行,
    // 包括 md 页面和 .vue 页面
  }
}

提示

如果你使用 TypeScript,记得将 index.js 重命名为 index.ts

Layout 组件是每个页面的根组件。最小的主题需要包含一个 Layout 组件,该组件处理页面内容的渲染:

vue
<!-- .vitepress/theme/Layout.vue -->
<template>
  <h1>Custom Layout!</h1>

  <!-- 这是 markdown 内容将被渲染的地方 -->
  <Content />
</template>

<Content /> 组件显示渲染的 markdown 内容。就是这样 - 现在你有了一个自定义主题!

你也可以提供一个 NotFound 组件:

vue
<!-- .vitepress/theme/NotFound.vue -->
<template>
  <h1>Page not found</h1>
</template>
js
// .vitepress/theme/index.js
import Layout from './Layout.vue'
import NotFound from './NotFound.vue'

export default {
  Layout,
  NotFound
}

包含默认主题

如果你想扩展和自定义默认主题,你可以从 vitepress/theme 导入它并在自定义主题中扩展它。以下是一些常见的自定义示例:

注册全局组件

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

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

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

自定义 CSS

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

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

export default DefaultTheme
css
/* .vitepress/theme/custom.css */
:root {
  --vp-c-brand-1: #646cff;
  --vp-c-brand-2: #747bff;
}

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

布局插槽

默认主题的 <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

使用 View Transitions API

在外观切换时

你可以扩展默认主题以在颜色模式之间切换时提供自定义过渡。例如:

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

::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}

::view-transition-old(root),
.dark::view-transition-new(root) {
  z-index: 1;
}

::view-transition-new(root),
.dark::view-transition-old(root) {
  z-index: 9999;
}

.VPSwitchAppearance {
  view-transition-name: vp-switch-appearance;
}

在路由更改时

VitePress 对路由更改提供内置支持。将以下内容添加到你的自定义 CSS:

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

@media (prefers-reduced-motion: no-preference) {
  .VPContent {
    view-transition-name: vp-content;
  }

  .VPContent.is-appearing,
  .VPContent.is-disappearing {
    view-transition-name: vp-content;
  }
}

警告

View Transitions API 仍然是实验性的。它目前仅在基于 Chromium 的浏览器中工作,但预计将来会得到更广泛的支持。请谨慎使用。

分发自定义主题

分发自定义主题的最简单方法是将其作为模板存储库在 GitHub 上提供。

如果你想将主题作为 npm 包分发,请遵循以下步骤:

  1. 在 package.json 中将主题对象导出为默认导出。
  2. 如果适用,将主题配置类型定义导出为 ThemeConfig
  3. 如果你的主题需要调整 VitePress 配置,也要在包下的子路径中导出该配置(例如 my-theme/config),以便用户可以扩展它。
  4. 记录主题配置选项(通过配置文件和 frontmatter)。
  5. 提供清晰的说明,说明如何安装和使用你的主题(见下文)。

主题配置

用户将能够通过 .vitepress/config.js 文件中的 themeConfig 选项配置你的主题。

js
// .vitepress/config.js
export default {
  themeConfig: {
    // Type is `ThemeConfig`
  }
}

主题安装

假设你将主题发布为 my-theme,用户将通过以下方式安装它:

sh
npm add my-theme

然后在他们的主题入口中导入和使用它:

js
// .vitepress/theme/index.js
import MyTheme from 'my-theme'

export default MyTheme

就像扩展默认主题一样,用户也可以扩展你的主题:

js
// .vitepress/theme/index.js
import MyTheme from 'my-theme'

export default {
  extends: MyTheme,
  enhanceApp({ app }) {
    // ...
  }
}

vitepress开发指南