docs: revise custom theme and default theme extending

pull/2044/head
Evan You 3 years ago
parent f39b6a98d6
commit de84c01b72

@ -100,11 +100,11 @@ function sidebarGuide() {
text: 'Customization',
collapsed: false,
items: [
{ text: 'Using a Custom Theme', link: '/guide/custom-theme' },
{
text: 'Extending the Default Theme',
link: '/guide/extending-default-theme'
},
{ text: 'Building a Custom Theme', link: '/guide/custom-theme' },
{ text: 'Build-Time Data Loading', link: '/guide/data-loading' },
{ text: 'Connecting to a CMS', link: '/guide/cms' }
]

@ -1 +1,222 @@
# Building a Custom Theme
# Using a Custom Theme
## Theme Resolving
You can enable a custom theme by creating a `.vitepress/theme/index.js` or `.vitepress/theme/index.ts` file (the "theme entry file"):
```
.
├─ docs # project root
│ ├─ .vitepress
│ │ ├─ theme
│ │ │ └─ index.js # theme entry
│ │ └─ config.js # config file
│ └─ index.md
└─ package.json
```
VitePress will always use the custom theme instead of the default theme when it detects presence of a theme entry file. You can, however, [extend the default theme](./extending-default-theme) to perform advanced customizations on top of it.
## Theme Interface
A VitePress custom theme is defined as an object with the following interface:
```ts
interface Theme {
/**
* Root layout component for every page
* @required
*/
Layout: Component
/**
* Enhance Vue app instance
* @optional
*/
enhanceApp?: (ctx: EnhanceAppContext) => Awaitable<void>
/**
* Extend another theme, calling its `enhanceApp` before ours
* @optional
*/
extends?: Theme
}
interface EnhanceAppContext {
app: App // Vue app instance
router: Router // VitePress router instance
siteData: Ref<SiteData> // Site-level metadata
}
```
The theme entry file should export the theme as its default export:
```js
// .vitepress/theme/index.js
// You can directly import Vue files in the theme entry
// VitePress is pre-configured with @vitejs/plugin-vue.
import Layout from './Layout.vue'
export default {
Layout,
enhanceApp({ app, router, siteData }) {
// ...
}
}
```
The default export is the only contract for a custom theme, and only the `Layout` property is required. So technically, a VitePress theme can be as simple as a single Vue component.
Inside your layout component, it works just like a normal Vite + Vue 3 application. Do note the theme also needs to be [SSR-compatible](./using-vue#browser-api-access-restrictions).
## Building a Layout
The most basic layout component needs to contain a [`<Content />`](/reference/runtime-api#content) component:
```vue
<!-- .vitepress/theme/Layout.vue -->
<template>
<h1>Custom Layout!</h1>
<!-- this is where markdown content will be rendered -->
<Content />
</template>
```
The above layout simply renders every page's markdown as HTML. The first improvement we can add is to handle 404 errors:
```vue{1-4,9-12}
<script setup>
import { useData } from 'vitepress'
const { page } = useData()
</script>
<template>
<h1>Custom Layout!</h1>
<div v-if="page.isNotFound">
Custom 404 page!
</div>
<Content v-else />
</template>
```
The [`useData()`](/reference/runtime-api#usedata) helper provides us with all the runtime data we need to conditionally render different layouts. One of the other data we can access is the current page's frontmatter. We can leverage this to allow the end user to control the layout in each page. For example, the user can indicate the page should use a special home page layout with:
```md
---
layout: home
---
```
And we can adjust our theme to handle this:
```vue{3,12-14}
<script setup>
import { useData } from 'vitepress'
const { page, frontmatter } = useData()
</script>
<template>
<h1>Custom Layout!</h1>
<div v-if="page.isNotFound">
Custom 404 page!
</div>
<div v-if="frontmatter.layout === 'home'">
Custom home page!
</div>
<Content v-else />
</template>
```
You can, of course, split the layout into more components:
```vue{3-5,12-15}
<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>
```
Consult the [Runtime API Reference](/reference/runtime-api) for everything available in theme components. In addition, you can leverage [Build-Time Data Loading](./data-loading) to generate data-driven layout - for example, a page that lists all blog posts in the current project.
## Distributing a Custom Theme
The easiest way to distribute a custom theme is by providing it as a [template repository on GitHub](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-template-repository).
If you wish to distribute the theme as an npm package, follow these steps:
1. Export the theme object as the default export in your package entry.
2. If applicable, export your theme config type definition as `ThemeConfig`.
3. If your theme requires adjusting the VitePress config, export that config under a package sub-path (e.g. `my-theme/config`) so the user can extend it.
4. Document the theme config options (both via config file and frontmatter).
5. Provide clear instructions on how to consume your theme (see below).
## Consuming a Custom Theme
To consume an external theme, import and re-export it from the custom theme entry:
```js
// .vitepress/theme/index.js
import Theme from 'awesome-vitepress-theme'
export default Theme
```
If the theme needs to be extended:
```js
// .vitepress/theme/index.js
import Theme from 'awesome-vitepress-theme'
export default {
extends: Theme,
enhanceApp(ctx) {
// ...
}
}
```
If the theme requires special VitePress config, you will need to also extend it in your own config:
```ts
// .vitepress/theme/config.ts
import baseConfig from 'awesome-vitepress-theme/config'
export default {
// extend theme base config (if needed)
extends: baseConfig
}
```
Finally, if the theme provides types for its theme config:
```ts
// .vitepress/theme/config.ts
import baseConfig from 'awesome-vitepress-theme/config'
import { defineConfigWithTheme } from 'vitepress'
import type { ThemeConfig } from 'awesome-vitepress-theme'
export default defineConfigWithTheme<ThemeConfig>({
extends: baseConfig,
themeConfig: {
// Type is `ThemeConfig`
}
})
```

@ -1,97 +1,40 @@
# Extending the Default Theme
VitePress comes with its default theme providing many features out of the box. You can check out the full features in the [Default Theme Config Overview](/reference/default-theme-config).
VitePress' default theme is optimized for documentation, and can be customized. Consult the [Default Theme Config Overview](/reference/default-theme-config) for a comprehensive list of options.
There are several cases where you may want to extend the default theme:
However, there are a number of cases where configuration alone won't be enough. For example:
1. You want to modify the Vue app, for example register global components;
2. You want to tweak the CSS styling;
3. You want to inject custom content into the theme via layout slots.
1. You need to tweak the CSS styling;
2. You need to modify the Vue app instance, for example to register global components;
3. You need to inject custom content into the theme via layout slots.
## Using a Custom Theme
These advanced customizations will require using a custom theme that "extends" the default theme.
You can enable a custom theme by adding the `.vitepress/theme/index.js` or `.vitepress/theme/index.ts` file (the "theme entry file").
:::tip
Before proceeding, make sure to first read [Using a Custom Theme](./custom-theme) to understand how custom themes work.
:::
```
.
├─ docs
│ ├─ .vitepress
│ │ ├─ theme
│ │ │ └─ index.js
│ │ └─ config.js
│ └─ index.md
└─ package.json
```
A VitePress custom theme is simply an object containing four properties and is defined as follows:
```ts
interface Theme {
Layout: Component // Vue 3 component
NotFound?: Component
enhanceApp?: (ctx: EnhanceAppContext) => Awaitable<void>
setup?: () => void
}
interface EnhanceAppContext {
app: App // Vue 3 app instance
router: Router // VitePress router instance
siteData: Ref<SiteData>
}
```
## Customizing CSS
The theme entry file should export the theme as its default export:
The default theme CSS is customizable by overriding root level CSS variables:
```js
// .vitepress/theme/index.js
import Layout from './Layout.vue'
export default {
// root component to wrap each page
Layout,
// this is a Vue 3 functional component
NotFound: () => 'custom 404',
enhanceApp({ app, router, siteData }) {
// app is the Vue 3 app instance from `createApp()`.
// router is VitePress' custom router. `siteData` is
// a `ref` of current site-level metadata.
},
setup() {
// this function will be executed inside VitePressApp's
// setup hook. all composition APIs are available here.
}
}
```
...where the `Layout` component could look like this:
```vue
<!-- .vitepress/theme/Layout.vue -->
<template>
<h1>Custom Layout!</h1>
import DefaultTheme from 'vitepress/theme'
import './custom.css'
<!-- this is where markdown content will be rendered -->
<Content />
</template>
export default DefaultTheme
```
The default export is the only contract for a custom theme. Inside your custom theme, it works just like a normal Vite + Vue 3 application. Do note the theme also needs to be [SSR-compatible](./using-vue#browser-api-access-restrictions).
To distribute a theme, simply export the object in your package entry. To consume an external theme, import and re-export it from the custom theme entry:
```js
// .vitepress/theme/index.js
import Theme from 'awesome-vitepress-theme'
export default Theme
```css
/* .vitepress/theme/custom.css */
:root {
--vp-c-brand: #646cff;
--vp-c-brand-light: #747bff;
}
```
## Extending the Default Theme
If you want to extend and customize the default theme, you can import it from `vitepress/theme` and augment it in a custom theme entry. Here are some examples of common customizations:
See [default theme CSS variables](https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css) that can be overridden.
## Registering Global Components
@ -100,11 +43,8 @@ If you want to extend and customize the default theme, you can import it from `v
import DefaultTheme from 'vitepress/theme'
export default {
...DefaultTheme,
extends: DefaultTheme,
enhanceApp(ctx) {
// extend default theme custom behaviour.
DefaultTheme.enhanceApp(ctx)
// register your custom global components
ctx.app.component('MyGlobalComponent' /* ... */)
}
@ -113,28 +53,6 @@ export default {
Since we are using Vite, you can also leverage Vite's [glob import feature](https://vitejs.dev/guide/features.html#glob-import) to auto register a directory of components.
## Customizing CSS
The default theme CSS is customizable by overriding root level CSS variables:
```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: #646cff;
--vp-c-brand-light: #747bff;
}
```
See [default theme CSS variables](https://github.com/vuejs/vitepress/blob/main/src/clien/reference/default-theme-default/styles/vars.css) that can be overridden.
## Layout Slots
The default theme's `<Layout/>` component has a few slots that can be used to inject content at certain locations of the page. Here's an example of injecting a component into the before outline:

@ -4,7 +4,7 @@ VitePress offers several built-in APIs to let you access app data. VitePress als
The helper methods are globally importable from `vitepress` and are typically used in custom theme Vue components. However, they are also usable inside `.md` pages because markdown files are compiled into Vue [Single-File Components](https://vuejs.org/guide/scaling-up/sfc.html).
Methods that start with `use*` indicates that it is a [Vue 3 Composition API](https://vuejs.org/guide/introduction.html#composition-api) function that can only be used inside `setup()` or `<script setup>`.
Methods that start with `use*` indicates that it is a [Vue 3 Composition API](https://vuejs.org/guide/introduction.html#composition-api) function ("Composable") that can only be used inside `setup()` or `<script setup>`.
## `useData` <Badge type="info" text="composable" />
@ -13,7 +13,7 @@ Returns page-specific data. The returned object has the following type:
```ts
interface VitePressData<T = any> {
/**
* Site-level data
* Site-level metadata
*/
site: Ref<SiteData<T>>
/**
@ -21,7 +21,7 @@ interface VitePressData<T = any> {
*/
theme: Ref<T>
/**
* Page-level data
* Page-level metadata
*/
page: Ref<PageData>
/**
@ -121,7 +121,7 @@ If you are using or demoing components that are not SSR-friendly (for example, c
</ClientOnly>
```
## `$frontmatter` <Badge type="info" text="global" />
## `$frontmatter` <Badge type="info" text="template global" />
Directly access current page's [frontmatter](/guide/frontmatter) data in Vue expressions.
@ -133,7 +133,7 @@ title: Hello
# {{ $frontmatter.title }}
```
## `$params` <Badge type="info" text="global" />
## `$params` <Badge type="info" text="template global" />
Directly access current page's [dynamic route params](/guide/routing#dynamic-routes) in Vue expressions.

@ -20,7 +20,7 @@ export const dataSymbol: InjectionKey<VitePressData> = Symbol()
export interface VitePressData<T = any> {
/**
* Site-level info
* Site-level metadata
*/
site: Ref<SiteData<T>>
/**
@ -28,7 +28,7 @@ export interface VitePressData<T = any> {
*/
theme: Ref<T>
/**
* Page-level info
* Page-level metadata
*/
page: Ref<PageData>
/**

Loading…
Cancel
Save