自定义主题
解析主题
可以通过创建一个 .vitepress-react/theme/index.ts 文件(即“主题入口文件”)来启用自定义主题:
.
├─ docs # 项目根目录
│ ├─ .vitepress-react
│ │ ├─ theme
│ │ │ └─ index.ts # 主题入口
│ │ └─ config.ts # 配置文件
│ └─ index.md
└─ package.json当检测到存在主题入口文件时,VitePress 总会使用自定义主题而不是默认主题。但你可以扩展默认主题来在其基础上实现更高级的自定义。
本项目 的主题是 React
主题入口与组件是普通的 .tsx(React)文件,构建由 Vite 完成(TSX 自动 JSX runtime,无需额外插件)。不再有 .vue 文件或 Vue 应用实例。
主题接口
VitePress 自定义主题是一个对象,该对象具有如下接口:
import type { ComponentType } from 'react'
import type { Router, SiteData } from '@10coding/vitepress-react'
interface Theme {
/** 每个页面的根布局组件(props 不限;框架以无参方式渲染) */
Layout?: ComponentType
/**
* 在客户端增强应用(可异步;服务端阶段也会在 SSR 中运行,注意 `import.meta.env.SSR`)
*/
enhanceApp?: (ctx: EnhanceAppContext) => Awaitable<void>
/** 运行在根组件 effect 中的客户端逻辑(SSR 安全:内部请自行守卫 DOM) */
setup?: () => void
/** 扩展另一个主题:先执行其 enhanceApp/setup */
extends?: Theme
}
interface EnhanceAppContext {
router: Router // VitePress 路由实例
siteData: SiteData // 站点级数据
}主题入口文件需要将主题对象作为默认导出来导出。建议用 defineTheme() 包一层(或给对象字面量加 satisfies Theme)以获得类型约束——直接写裸对象只有字面量推断,拼错键名(如 compnents)、把 components 覆盖表写成非内部组件名、字段值类型不对,都不会报错:
import { defineTheme } from '@10coding/vitepress-react'
import Layout from './Layout.tsx'
export default defineTheme({
Layout,
enhanceApp({ router }) {
// ...
}
})两种等效写法:
// 方式一:defineTheme(推荐;参数即 Theme,错误在编辑期提示)
export default defineTheme({ Layout, components: { VPNavBar: MyNavBar } })
// 方式二:satisfies Theme(需要 import type { Theme })
export default { Layout, components: { VPNavBar: MyNavBar } } satisfies Theme默认导出是自定义主题的唯一方式;Layout 也是最常用的属性——从技术上讲,一个 VitePress 主题可以只是一个 React 布局组件。注意主题同样需要保证 SSR 兼容。
构建布局
最基本的布局组件需要渲染 <Content />,它负责输出当前页面的 markdown 内容:
import { Content } from '@10coding/vitepress-react'
export default function Layout() {
return (
<div className="vp-layout">
<h1>Custom Layout!</h1>
<Content />
</div>
)
}上面的布局只是把每个页面的 markdown 渲染为 HTML。我们添加的第一个改进是处理 404 错误:
import { Content, useData } from '@10coding/vitepress-react'
export default function Layout() {
const { page } = useData()
if (page.isNotFound) {
return (
<div className="vp-layout">
<h1>Custom Layout!</h1>
<p>Custom 404 page!</p>
</div>
)
}
return (
<div className="vp-layout">
<h1>Custom Layout!</h1>
<Content />
</div>
)
}useData() 提供了全部运行时数据(返回值是当前快照的普通值,不是响应式包装),方便根据条件渲染不同布局。另一个常用字段是当前页面的 frontmatter——借助它可以让用户通过 frontmatter 控制每页布局,例如标记某页使用特殊首页布局:
---
layout: home
---主题据此分支渲染:
import { Content, useData } from '@10coding/vitepress-react'
export default function Layout() {
const { page, frontmatter } = useData()
if (page.isNotFound) {
return (
<div className="vp-layout">
<h1>Custom Layout!</h1>
<p>Custom 404 page!</p>
</div>
)
}
if (frontmatter.layout === 'home') {
return (
<div className="vp-layout">
<h1>Custom Layout!</h1>
<p>Custom home page!</p>
</div>
)
}
return (
<div className="vp-layout">
<h1>Custom Layout!</h1>
<Content />
</div>
)
}当然你可以把布局拆成多个组件:
import { useData } from '@10coding/vitepress-react'
import NotFound from './NotFound.tsx'
import Home from './Home.tsx'
import Page from './Page.tsx'
export default function Layout() {
const { page, frontmatter } = useData()
return (
<div className="vp-layout">
<h1>Custom Layout!</h1>
{page.isNotFound ? (
<NotFound />
) : frontmatter.layout === 'home' ? (
<Home />
) : (
<Page />
)}
</div>
)
}import { Content } from '@10coding/vitepress-react'
export default function Page() {
return <Content />
}请查看运行时 API 参考获取主题组件中所有可用的内容。此外,可以利用构建时数据加载生成数据驱动布局——例如,一个列出当前项目中所有文章入口的页面。
基于默认主题布局组合
不必从零自绘:默认主题的 Layout 是自包含的普通 React 组件(内部自己读取数据并渲染 SkipLink/导航/侧栏/内容/页脚),可以直接从 @10coding/vitepress-react/theme 导入,在自己的主题 Layout 里按条件整页复用,只对特定页面走自定义分支。
import Theme from '@10coding/vitepress-react/theme'
import { Content, useData } from '@10coding/vitepress-react'
export default function CustomLayout() {
const { frontmatter } = useData()
// 未打标的页面 → 整套复用默认主题布局(home / doc / layout:false 等
// 分流默认主题已内部处理,不需要重复实现)
if (frontmatter.layout !== 'custom') {
return <Theme.Layout />
}
// frontmatter 打上 layout: custom 的页面 → 自绘
return (
<div className="vp-layout">
<h1>Custom Layout!</h1>
<Content />
</div>
)
}对应页面在 frontmatter 里标记:
---
layout: custom
---接线时用 extends 继承默认主题的其余能力,再覆盖 Layout:
import { defineTheme } from '@10coding/vitepress-react'
import Theme from '@10coding/vitepress-react/theme'
import CustomLayout from './Layout.tsx'
export default defineTheme({
extends: Theme, // 继承默认主题其余字段;enhanceApp 会 base-first 链式执行
Layout: CustomLayout
})几个注意点:
- 导入来源:
Layout不在@10coding/vitepress-react根导出里(那里只有useData/Content等);默认主题要写@10coding/vitepress-react/theme。 - 组合粒度:默认
Layout是自包含组件、不接受children,但它提供与 Vue 上游对齐的具名插槽 props(camelCase,如asideOutlineBefore/layoutTop),可不动默认结构在指定位置注入内容;默认主题内部组合树还可用Theme.components注册表按名覆盖(含叶子组件)。两者详见扩展默认主题。若连插槽与注册表都不够(要改变整体骨架),再整页自绘或复制一份布局组件自己拼(上面的构建布局列了全部可拆分部件思路)。 - 在默认布局外面再包一层(如全站顶部横幅)是允许的:把
<Theme.Layout />放进自己的容器即可。 - 404:本项目 已废弃
Theme.NotFound,按page.isNotFound分支(参考构建布局里的 404 处理);不特殊处理时让它走<Theme.Layout />也可以。
分发自定义主题
分发自定义主题最简单的方式是将其作为 GitHub 模版仓库。
如果希望将主题作为 npm 包分发,请按下面的步骤:
在包入口把主题对象作为默认导出(文件为
.ts/.tsx)。如果合适,把主题配置类型作为
ThemeConfig导出。如果主题需要调整 VitePress 配置,请在包的子路径下(例如
my-theme/config)导出该配置,以便用户扩展。记录主题配置选项(配置文件与 frontmatter 两处)。
提供清晰的使用说明(见下节)。
使用自定义主题
要使用外部主题,请导入它并重新导出:
import Theme from 'awesome-vitepress-theme'
export default Theme如果主题需要扩展:
import Theme from 'awesome-vitepress-theme'
export default {
...Theme,
enhanceApp(ctx) {
// ...
}
}注意:本项目的主题对象用对象展开/覆盖组合(
extends主题字段也可用,语义见主题接口),不是 Vue 的“extends 组件再包装”那套写法。
如果主题需要特殊的 VitePress 配置,在站点配置中扩展它:
import baseConfig from 'awesome-vitepress-theme/config'
export default {
extends: baseConfig
}如果主题提供了 ThemeConfig 类型:
import baseConfig from 'awesome-vitepress-theme/config'
import { defineConfig } from '@10coding/vitepress-react'
import type { ThemeConfig } from 'awesome-vitepress-theme'
export default defineConfig({
extends: baseConfig,
themeConfig: {
// 类型为 ThemeConfig
} as ThemeConfig
})