主题
更新: 11/16/2024 字数: 0 字 时长: 0 分钟
本节为可选阅读章节,主要针对官方文档中主题的扩展,从源码角度解释方便二次开发。
默认主题是如何实现的
官方文档布局一节告诉我们,通过 frontmatter 中的 layout 控制, 默认主题的情况下会有以下类型的页面:
doc
:文章页,会渲染md文件,同时会添加样式page
:普通页面,只会将md文件编译为html,但是不会有样式,需要自己设计样式home
:首页- 无布局:没有任何侧边栏、导航栏或页脚,完全自定义
- 自定义布局:当你注册了全局组件,可以使用注册的组件直接作为1个页面
那这些页面是如何实现的呢?在 Vue.js Devtools 中我们也可以看到整个App包裹在了Layout
组件中

翻读Layout
源码后, 我们可以发现,整个页面(顶导、侧导等)都定义在这里,读取 config 或者 frontmatter 配置的值来做出对应的改变, 上面说的页面类型也是在这里判断后做出对应的代码判断。
还可以发现无布局的实现就是Content
组件,如果再细翻VPContent
的实现, 可以发现还是使用了Content
组件,但是会包含增加 class 来增加样式。 所以md文件转换为Vue组件后就是Content
组件, 整个 VitePress 其实就是围绕Layout
来展示的
这也就解释了官方文档 自定义主题一节中的这句话:“最基本的布局组件需要包含一个 <Content />
组件”
Layout源码
vue
<template>
<div v-if="frontmatter.layout !== false" class="Layout" :class="frontmatter.pageClass">
<slot name="layout-top"/>
<VPSkipLink/>
<VPBackdrop class="backdrop" :show="isSidebarOpen" @click="closeSidebar"/>
<VPNav>
<template #nav-bar-title-before>
<slot name="nav-bar-title-before"/>
</template>
<template #nav-bar-title-after>
<slot name="nav-bar-title-after"/>
</template>
<template #nav-bar-content-before>
<slot name="nav-bar-content-before"/>
</template>
<template #nav-bar-content-after>
<slot name="nav-bar-content-after"/>
</template>
<template #nav-screen-content-before>
<slot name="nav-screen-content-before"/>
</template>
<template #nav-screen-content-after>
<slot name="nav-screen-content-after"/>
</template>
</VPNav>
<VPLocalNav :open="isSidebarOpen" @open-menu="openSidebar"/>
<VPSidebar :open="isSidebarOpen">
<template #sidebar-nav-before>
<slot name="sidebar-nav-before"/>
</template>
<template #sidebar-nav-after>
<slot name="sidebar-nav-after"/>
</template>
</VPSidebar>
<VPContent>
<template #page-top>
<slot name="page-top"/>
</template>
<template #page-bottom>
<slot name="page-bottom"/>
</template>
<template #not-found>
<slot name="not-found"/>
</template>
<template #home-hero-before>
<slot name="home-hero-before"/>
</template>
<template #home-hero-info-before>
<slot name="home-hero-info-before"/>
</template>
<template #home-hero-info>
<slot name="home-hero-info"/>
</template>
<template #home-hero-info-after>
<slot name="home-hero-info-after"/>
</template>
<template #home-hero-actions-after>
<slot name="home-hero-actions-after"/>
</template>
<template #home-hero-image>
<slot name="home-hero-image"/>
</template>
<template #home-hero-after>
<slot name="home-hero-after"/>
</template>
<template #home-features-before>
<slot name="home-features-before"/>
</template>
<template #home-features-after>
<slot name="home-features-after"/>
</template>
<template #doc-footer-before>
<slot name="doc-footer-before"/>
</template>
<template #doc-before>
<slot name="doc-before"/>
</template>
<template #doc-after>
<slot name="doc-after"/>
</template>
<template #doc-top>
<slot name="doc-top"/>
</template>
<template #doc-bottom>
<slot name="doc-bottom"/>
</template>
<template #aside-top>
<slot name="aside-top"/>
</template>
<template #aside-bottom>
<slot name="aside-bottom"/>
</template>
<template #aside-outline-before>
<slot name="aside-outline-before"/>
</template>
<template #aside-outline-after>
<slot name="aside-outline-after"/>
</template>
<template #aside-ads-before>
<slot name="aside-ads-before"/>
</template>
<template #aside-ads-after>
<slot name="aside-ads-after"/>
</template>
</VPContent>
<VPFooter/>
<slot name="layout-bottom"/>
</div>
<Content v-else/>
</template>
如何扩展默认主题
上一节中我们已经知道了Layout
组件是 VitePress 的核心,所以只要扩展这个Layout
就可以。 VitePress 提供了一个接口,在.vitepress/theme
目录下导出即可让 VitePress 使用我们定义的主题。
- 创建新的
Layout
组件,里面包含默认的Layout
组件,可以通过 slot 为默认的Layout
注入模块,所有插槽 - 可以在
theme/index.ts
中注入一些全局组件,这样我们就可以在Markdown文件中直接使用了
ts
interface Theme {
/**
* 每个页面的根布局组件
* @required
*/
Layout: Component
/**
* 增强 Vue 应用实例
* @optional
*/
enhanceApp?: (ctx: EnhanceAppContext) => Awaitable<void>
/**
* 扩展另一个主题,在我们的主题之前调用它的 `enhanceApp`
* @optional
*/
extends?: Theme
}
interface EnhanceAppContext {
app: App // Vue 应用实例
router: Router // VitePress 路由实例
siteData: Ref<SiteData> // 站点级元数据
}
ts
import DefaultTheme from 'vitepress/theme';
import type {Theme} from 'vitepress';
import Layout from './Layout.vue';
export default {
extends: DefaultTheme,
// 扩展原有主题
Layout: Layout,
enhanceApp(ctx) {
// 注册全局组件
ctx.app.component('XX', XX);
},
setup() {
// 初始化
},
} satisfies Theme;
vue
<script setup lang="ts">
import DefaultTheme from 'vitepress/theme';
const { Layout } = DefaultTheme;
</script>
<template>
<Layout>
<template #layout-bottom>
<!-- 首页底部插槽,可以添加备案号等 -->
</template>
</Layout>
</template>
如何自定义主题
其实和扩展默认主题类似,我们只要将Layout
组件替换为自己写的就可以
TIP
Layout
组件中要包含Content
组件,除非你不想渲染md文件
ts
import Layout from './Layout.vue'
export default {
Layout,
enhanceApp({ app, router, siteData }) {
// ...
}
} satisfies Theme;
vue
<script setup>
import { useData } from 'vitepress'
import NotFound from './NotFound.vue'
import Home from './Home.vue'
import Page from './Page.vue'
const { page, frontmatter } = useData()
</script>
<template>
<h1>Custom Layout!</h1>
<NotFound v-if="page.isNotFound" />
<Home v-if="frontmatter.layout === 'home'" />
<Page v-else /> <!-- <Page /> renders <Content /> -->
</template>