format: apply repo prettier (3.8.1) to the folded skills tree

963 markdown files reformatted with the repository's pinned prettier so
pnpm format:check covers the folded tree like every other repo file.

The formatter's embedded-language pass also normalized code fences
(TS semicolons, closed HTML tags in examples, lowercased CSS hex colors,
one renumbered list that skipped an index). Alphanumeric token deltas vs
the fold commit were audited file-by-file; all are formatter-equivalent
markup normalizations plus the four sanitized skills.
This commit is contained in:
fargo
2026-08-19 14:37:17 -05:00
parent d2eeb64433
commit 1a822493ba
962 changed files with 29594 additions and 27188 deletions
@@ -3,13 +3,14 @@ name: vitepress
description: VitePress static site generator powered by Vite and Vue. Use when building documentation sites, configuring themes, or writing Markdown with Vue components.
metadata:
author: Anthony Fu
version: "2026.1.28"
version: '2026.1.28'
source: Generated from https://github.com/vuejs/vitepress, scripts located at https://github.com/antfu/skills
---
VitePress is a Static Site Generator (SSG) built on Vite and Vue 3. It takes Markdown content, applies a theme, and generates static HTML that becomes an SPA for fast navigation. Perfect for documentation, blogs, and marketing sites.
**Key Characteristics:**
- File-based routing with `.md` files
- Vue components work directly in Markdown
- Fast HMR with instant updates (<100ms)
@@ -17,6 +18,7 @@ VitePress is a Static Site Generator (SSG) built on Vite and Vue 3. It takes Mar
- Built-in search (local or Algolia)
**Before working with VitePress projects:**
- Check `.vitepress/config.ts` for site configuration
- Look at `.vitepress/theme/` for custom theme extensions
- The `public/` directory contains static assets served as-is
@@ -25,41 +27,41 @@ VitePress is a Static Site Generator (SSG) built on Vite and Vue 3. It takes Mar
## Core
| Topic | Description | Reference |
|-------|-------------|-----------|
| Configuration | Config file setup, defineConfig, site metadata | [core-config](references/core-config.md) |
| CLI | Command-line interface: dev, build, preview, init | [core-cli](references/core-cli.md) |
| Routing | File-based routing, source directory, rewrites | [core-routing](references/core-routing.md) |
| Markdown | Frontmatter, containers, tables, anchors, includes | [core-markdown](references/core-markdown.md) |
| Topic | Description | Reference |
| ------------- | -------------------------------------------------- | -------------------------------------------- |
| Configuration | Config file setup, defineConfig, site metadata | [core-config](references/core-config.md) |
| CLI | Command-line interface: dev, build, preview, init | [core-cli](references/core-cli.md) |
| Routing | File-based routing, source directory, rewrites | [core-routing](references/core-routing.md) |
| Markdown | Frontmatter, containers, tables, anchors, includes | [core-markdown](references/core-markdown.md) |
## Features
### Code & Content
| Topic | Description | Reference |
|-------|-------------|-----------|
| Code Blocks | Syntax highlighting, line highlighting, diffs, focus | [features-code-blocks](references/features-code-blocks.md) |
| Vue in Markdown | Components, script setup, directives, templating | [features-vue](references/features-vue.md) |
| Data Loading | Build-time data loaders, createContentLoader | [features-data-loading](references/features-data-loading.md) |
| Dynamic Routes | Generate pages from data, paths loader files | [features-dynamic-routes](references/features-dynamic-routes.md) |
| Topic | Description | Reference |
| --------------- | ---------------------------------------------------- | ---------------------------------------------------------------- |
| Code Blocks | Syntax highlighting, line highlighting, diffs, focus | [features-code-blocks](references/features-code-blocks.md) |
| Vue in Markdown | Components, script setup, directives, templating | [features-vue](references/features-vue.md) |
| Data Loading | Build-time data loaders, createContentLoader | [features-data-loading](references/features-data-loading.md) |
| Dynamic Routes | Generate pages from data, paths loader files | [features-dynamic-routes](references/features-dynamic-routes.md) |
## Theme
| Topic | Description | Reference |
|-------|-------------|-----------|
| Theme Config | Nav, sidebar, search, social links, footer | [theme-config](references/theme-config.md) |
| Topic | Description | Reference |
| ------------- | ---------------------------------------------- | -------------------------------------------------------- |
| Theme Config | Nav, sidebar, search, social links, footer | [theme-config](references/theme-config.md) |
| Customization | CSS variables, slots, fonts, global components | [theme-customization](references/theme-customization.md) |
| Custom Theme | Building themes from scratch, theme interface | [theme-custom](references/theme-custom.md) |
| Custom Theme | Building themes from scratch, theme interface | [theme-custom](references/theme-custom.md) |
## Advanced
| Topic | Description | Reference |
|-------|-------------|-----------|
| Internationalization | Multi-language sites, locale configuration | [advanced-i18n](references/advanced-i18n.md) |
| SSR Compatibility | Server-side rendering, ClientOnly, dynamic imports | [advanced-ssr](references/advanced-ssr.md) |
| Topic | Description | Reference |
| -------------------- | -------------------------------------------------- | -------------------------------------------- |
| Internationalization | Multi-language sites, locale configuration | [advanced-i18n](references/advanced-i18n.md) |
| SSR Compatibility | Server-side rendering, ClientOnly, dynamic imports | [advanced-ssr](references/advanced-ssr.md) |
## Recipes
| Topic | Description | Reference |
|-------|-------------|-----------|
| Topic | Description | Reference |
| ---------- | ------------------------------------------------ | ---------------------------------------------- |
| Deployment | GitHub Pages, Netlify, Vercel, Cloudflare, Nginx | [recipes-deploy](references/recipes-deploy.md) |
@@ -42,26 +42,26 @@ docs/
```ts
// .vitepress/config.ts
import { defineConfig } from 'vitepress'
import { defineConfig } from 'vitepress';
export default defineConfig({
locales: {
root: {
label: 'English',
lang: 'en'
lang: 'en',
},
zh: {
label: '简体中文',
lang: 'zh-CN',
link: '/zh/'
link: '/zh/',
},
fr: {
label: 'Français',
lang: 'fr',
link: '/fr/'
}
}
})
link: '/fr/',
},
},
});
```
## Locale-Specific Config
@@ -112,13 +112,13 @@ Each locale can override:
```ts
interface LocaleSpecificConfig {
lang?: string
dir?: string // 'ltr' or 'rtl'
title?: string
titleTemplate?: string | boolean
description?: string
head?: HeadConfig[] // Merged with existing
themeConfig?: ThemeConfig // Shallow merged
lang?: string;
dir?: string; // 'ltr' or 'rtl'
title?: string;
titleTemplate?: string | boolean;
description?: string;
head?: HeadConfig[]; // Merged with existing
themeConfig?: ThemeConfig; // Shallow merged
}
```
@@ -207,17 +207,17 @@ Set cookie on language change:
```vue
<!-- .vitepress/theme/Layout.vue -->
<script setup>
import DefaultTheme from 'vitepress/theme'
import { useData, inBrowser } from 'vitepress'
import { watchEffect } from 'vue'
import DefaultTheme from 'vitepress/theme';
import { useData, inBrowser } from 'vitepress';
import { watchEffect } from 'vue';
const { lang } = useData()
const { lang } = useData();
watchEffect(() => {
if (inBrowser) {
document.cookie = `nf_lang=${lang.value}; expires=Mon, 1 Jan 2030 00:00:00 UTC; path=/`
document.cookie = `nf_lang=${lang.value}; expires=Mon, 1 Jan 2030 00:00:00 UTC; path=/`;
}
})
});
</script>
<template>
@@ -243,16 +243,16 @@ Requires PostCSS plugin like `postcss-rtlcss`:
```ts
// postcss.config.js
import rtlcss from 'postcss-rtlcss'
import rtlcss from 'postcss-rtlcss';
export default {
plugins: [
rtlcss({
ltrPrefix: ':where([dir="ltr"])',
rtlPrefix: ':where([dir="rtl"])'
})
]
}
rtlPrefix: ':where([dir="rtl"])',
}),
],
};
```
## Organizing Config
@@ -270,18 +270,18 @@ Split config into separate files:
```ts
// .vitepress/config/index.ts
import { defineConfig } from 'vitepress'
import { shared } from './shared'
import { en } from './en'
import { zh } from './zh'
import { defineConfig } from 'vitepress';
import { shared } from './shared';
import { en } from './en';
import { zh } from './zh';
export default defineConfig({
...shared,
locales: {
root: { label: 'English', ...en },
zh: { label: '简体中文', ...zh }
}
})
zh: { label: '简体中文', ...zh },
},
});
```
## Key Points
@@ -10,19 +10,20 @@ VitePress pre-renders pages on the server during build. All Vue code must be SSR
## The Rule
Only access browser/DOM APIs in Vue lifecycle hooks:
- `onMounted()`
- `onBeforeMount()`
```vue
<script setup>
import { onMounted, ref } from 'vue'
import { onMounted, ref } from 'vue';
const windowWidth = ref(0)
const windowWidth = ref(0);
onMounted(() => {
// Safe - runs only in browser
windowWidth.value = window.innerWidth
})
windowWidth.value = window.innerWidth;
});
</script>
```
@@ -31,7 +32,7 @@ onMounted(() => {
```vue
<script setup>
// WRONG - runs during SSR where window doesn't exist
const width = window.innerWidth
const width = window.innerWidth;
</script>
```
@@ -55,12 +56,12 @@ Some libraries access `window` or `document` when imported:
```vue
<script setup>
import { onMounted } from 'vue'
import { onMounted } from 'vue';
onMounted(async () => {
const lib = await import('browser-only-library')
lib.doSomething()
})
const lib = await import('browser-only-library');
lib.doSomething();
});
</script>
```
@@ -68,8 +69,8 @@ onMounted(async () => {
```ts
if (!import.meta.env.SSR) {
const lib = await import('browser-only-library')
lib.doSomething()
const lib = await import('browser-only-library');
lib.doSomething();
}
```
@@ -80,11 +81,11 @@ if (!import.meta.env.SSR) {
export default {
async enhanceApp({ app }) {
if (!import.meta.env.SSR) {
const plugin = await import('browser-plugin')
app.use(plugin.default)
const plugin = await import('browser-plugin');
app.use(plugin.default);
}
}
}
},
};
```
## defineClientComponent
@@ -93,11 +94,11 @@ Helper for components that access browser on import:
```vue
<script setup>
import { defineClientComponent } from 'vitepress'
import { defineClientComponent } from 'vitepress';
const BrowserComponent = defineClientComponent(() => {
return import('browser-only-component')
})
return import('browser-only-component');
});
</script>
<template>
@@ -109,10 +110,10 @@ With props and slots:
```vue
<script setup>
import { ref, h } from 'vue'
import { defineClientComponent } from 'vitepress'
import { ref, h } from 'vue';
import { defineClientComponent } from 'vitepress';
const componentRef = ref(null)
const componentRef = ref(null);
const BrowserComponent = defineClientComponent(
() => import('browser-only-component'),
@@ -121,14 +122,14 @@ const BrowserComponent = defineClientComponent(
{ ref: componentRef, someProp: 'value' },
{
default: () => 'Default slot content',
header: () => h('div', 'Header slot')
}
header: () => h('div', 'Header slot'),
},
],
// Callback after component loads
() => {
console.log('Component loaded', componentRef.value)
}
)
console.log('Component loaded', componentRef.value);
},
);
</script>
```
@@ -151,8 +152,8 @@ For other targets, use `postRender` hook:
export default {
async postRender(context) {
// Inject teleport content into final HTML
}
}
},
};
```
## Common SSR Errors
@@ -163,13 +164,13 @@ Code accesses `window` at module level:
```ts
// BAD
const width = window.innerWidth
const width = window.innerWidth;
// GOOD
let width: number
let width: number;
onMounted(() => {
width = window.innerWidth
})
width = window.innerWidth;
});
```
### "document is not defined"
@@ -178,12 +179,12 @@ Same issue with `document`:
```ts
// BAD
const el = document.querySelector('#app')
const el = document.querySelector('#app');
// GOOD
onMounted(() => {
const el = document.querySelector('#app')
})
const el = document.querySelector('#app');
});
```
### Hydration Mismatch
@@ -204,10 +205,10 @@ Server and client render different content:
```ts
// In Vue component
import.meta.env.SSR // true on server, false on client
import.meta.env.SSR; // true on server, false on client
// In VitePress
import { inBrowser } from 'vitepress'
import { inBrowser } from 'vitepress';
if (inBrowser) {
// Client-only code
}
@@ -24,14 +24,14 @@ vitepress dev docs
**Options:**
| Option | Description |
|--------|-------------|
| `--open [path]` | Open browser on startup |
| `--port <port>` | Specify port number |
| `--base <path>` | Override base URL |
| `--cors` | Enable CORS |
| `--strictPort` | Exit if port is in use |
| `--force` | Ignore cache and re-bundle |
| Option | Description |
| --------------- | -------------------------- |
| `--open [path]` | Open browser on startup |
| `--port <port>` | Specify port number |
| `--base <path>` | Override base URL |
| `--cors` | Enable CORS |
| `--strictPort` | Exit if port is in use |
| `--force` | Ignore cache and re-bundle |
```bash
vitepress dev docs --port 3000 --open
@@ -47,13 +47,13 @@ vitepress build docs
**Options:**
| Option | Description |
|--------|-------------|
| `--base <path>` | Override base URL |
| `--target <target>` | Transpile target (default: `modules`) |
| `--outDir <dir>` | Output directory (relative to cwd) |
| `--assetsInlineLimit <n>` | Asset inline threshold in bytes |
| `--mpa` | Build in MPA mode (no client hydration) |
| Option | Description |
| ------------------------- | --------------------------------------- |
| `--base <path>` | Override base URL |
| `--target <target>` | Transpile target (default: `modules`) |
| `--outDir <dir>` | Output directory (relative to cwd) |
| `--assetsInlineLimit <n>` | Asset inline threshold in bytes |
| `--mpa` | Build in MPA mode (no client hydration) |
```bash
vitepress build docs --outDir dist
@@ -69,10 +69,10 @@ vitepress preview docs
**Options:**
| Option | Description |
|--------|-------------|
| Option | Description |
| --------------- | ------------------- |
| `--port <port>` | Specify port number |
| `--base <path>` | Override base URL |
| `--base <path>` | Override base URL |
```bash
vitepress preview docs --port 4173
@@ -87,6 +87,7 @@ vitepress init
```
This creates the basic file structure:
- `.vitepress/config.js` - Configuration
- `index.md` - Home page
- Optional example pages
@@ -11,39 +11,39 @@ VitePress configuration is defined in `.vitepress/config.[js|ts|mjs|mts]`. Use `
```ts
// .vitepress/config.ts
import { defineConfig } from 'vitepress'
import { defineConfig } from 'vitepress';
export default defineConfig({
// Site metadata
title: 'My Docs',
description: 'Documentation site',
lang: 'en-US',
// URL base path (for GitHub Pages: '/repo-name/')
base: '/',
// Theme configuration
themeConfig: {
// See theme-config.md
}
})
},
});
```
## Site Metadata
```ts
export default defineConfig({
title: 'VitePress', // Displayed in nav, used in page titles
title: 'VitePress', // Displayed in nav, used in page titles
titleTemplate: ':title - Docs', // Page title format (:title = h1)
description: 'Site description', // Meta description
lang: 'en-US', // HTML lang attribute
lang: 'en-US', // HTML lang attribute
head: [
['link', { rel: 'icon', href: '/favicon.ico' }],
['meta', { name: 'theme-color', content: '#5f67ee' }],
['script', { async: '', src: 'https://analytics.example.com/script.js' }]
]
})
['script', { async: '', src: 'https://analytics.example.com/script.js' }],
],
});
```
## Build Options
@@ -52,27 +52,27 @@ export default defineConfig({
export default defineConfig({
// Source files directory (relative to project root)
srcDir: './src',
// Exclude patterns from source
srcExclude: ['**/README.md', '**/TODO.md'],
// Output directory
outDir: './.vitepress/dist',
// Cache directory
cacheDir: './.vitepress/cache',
// Clean URLs without .html extension (requires server support)
cleanUrls: true,
// Ignore dead links during build
ignoreDeadLinks: true,
// Or specific patterns:
ignoreDeadLinks: ['/playground', /^https?:\/\/localhost/],
// Get last updated timestamp from git
lastUpdated: true
})
lastUpdated: true,
});
```
## Route Rewrites
@@ -84,23 +84,23 @@ export default defineConfig({
rewrites: {
// Static mapping
'packages/pkg-a/src/index.md': 'pkg-a/index.md',
// Dynamic parameters
'packages/:pkg/src/:slug*': ':pkg/:slug*'
}
})
'packages/:pkg/src/:slug*': ':pkg/:slug*',
},
});
```
## Appearance (Dark Mode)
```ts
export default defineConfig({
appearance: true, // Enable toggle (default)
appearance: 'dark', // Dark by default
appearance: 'force-dark', // Always dark, no toggle
appearance: 'force-auto', // Always follow system preference
appearance: false // Disable dark mode
})
appearance: true, // Enable toggle (default)
appearance: 'dark', // Dark by default
appearance: 'force-dark', // Always dark, no toggle
appearance: 'force-auto', // Always follow system preference
appearance: false, // Disable dark mode
});
```
## Vite & Vue Configuration
@@ -111,14 +111,14 @@ export default defineConfig({
vite: {
plugins: [],
resolve: { alias: {} },
css: { preprocessorOptions: {} }
css: { preprocessorOptions: {} },
},
// Pass options to @vitejs/plugin-vue
vue: {
template: { compilerOptions: {} }
template: { compilerOptions: {} },
},
// Configure markdown-it
markdown: {
lineNumbers: true,
@@ -127,10 +127,10 @@ export default defineConfig({
container: {
tipLabel: 'TIP',
warningLabel: 'WARNING',
dangerLabel: 'DANGER'
}
}
})
dangerLabel: 'DANGER',
},
},
});
```
## Build Hooks
@@ -139,22 +139,20 @@ export default defineConfig({
export default defineConfig({
// Transform page data
transformPageData(pageData, { siteConfig }) {
pageData.frontmatter.head ??= []
pageData.frontmatter.head.push([
'meta', { name: 'og:title', content: pageData.title }
])
pageData.frontmatter.head ??= [];
pageData.frontmatter.head.push(['meta', { name: 'og:title', content: pageData.title }]);
},
// Transform head before generating each page
async transformHead(context) {
return [['meta', { name: 'custom', content: context.page }]]
return [['meta', { name: 'custom', content: context.page }]];
},
// After build completes
async buildEnd(siteConfig) {
// Generate sitemap, RSS, etc.
}
})
},
});
```
## Dynamic Config
@@ -163,15 +161,15 @@ For async configuration:
```ts
export default async () => {
const data = await fetch('https://api.example.com/data').then(r => r.json())
const data = await fetch('https://api.example.com/data').then((r) => r.json());
return defineConfig({
title: data.title,
themeConfig: {
sidebar: data.sidebar
}
})
}
sidebar: data.sidebar,
},
});
};
```
## Key Points
@@ -32,8 +32,8 @@ Or in script:
```vue
<script setup>
import { useData } from 'vitepress'
const { frontmatter } = useData()
import { useData } from 'vitepress';
const { frontmatter } = useData();
</script>
```
@@ -115,7 +115,7 @@ Generate a TOC with:
```md
| Feature | Status |
|---------|--------|
| ------- | ------ |
| SSR | ✅ |
| HMR | ✅ |
```
@@ -149,7 +149,9 @@ With regions:
```md
<!-- In parts/basics.md -->
<!-- #region usage -->
Usage content here
<!-- #endregion usage -->
<!-- Include just that region -->
@@ -190,11 +192,15 @@ Tab groups for code variants:
::: code-group
```js [config.js]
export default { /* ... */ }
export default {
/* ... */
};
```
```ts [config.ts]
export default defineConfig({ /* ... */ })
export default defineConfig({
/* ... */
});
```
:::
@@ -223,9 +229,9 @@ npm add -D markdown-it-mathjax3@^4
// .vitepress/config.ts
export default {
markdown: {
math: true
}
}
math: true,
},
};
```
Then use LaTeX:
@@ -234,6 +240,7 @@ Then use LaTeX:
Inline: $E = mc^2$
Block:
$$
\frac{-b \pm \sqrt{b^2-4ac}}{2a}
$$
@@ -245,10 +252,10 @@ $$
export default {
markdown: {
image: {
lazyLoading: true
}
}
}
lazyLoading: true,
},
},
};
```
## Raw Container
@@ -41,8 +41,8 @@ Separate source files from project root:
```ts
// .vitepress/config.ts
export default {
srcDir: './src' // Markdown files live in ./src/
}
srcDir: './src', // Markdown files live in ./src/
};
```
With `srcDir: 'src'`:
@@ -61,10 +61,12 @@ Use relative or absolute paths. Omit file extensions:
```md
<!-- Recommended -->
[Getting Started](./getting-started)
[Guide](/guide/)
<!-- Works but not recommended -->
[Getting Started](./getting-started.md)
[Getting Started](./getting-started.html)
```
@@ -75,11 +77,12 @@ Remove `.html` extension from URLs (requires server support):
```ts
export default {
cleanUrls: true
}
cleanUrls: true,
};
```
**Server requirements:**
- Netlify, GitHub Pages: Supported by default
- Vercel: Enable `cleanUrls` in `vercel.json`
- Nginx: Configure `try_files $uri $uri.html $uri/ =404`
@@ -94,11 +97,11 @@ export default {
// Static mapping
'packages/pkg-a/src/index.md': 'pkg-a/index.md',
'packages/pkg-a/src/foo.md': 'pkg-a/foo.md',
// Dynamic parameters
'packages/:pkg/src/:slug*': ':pkg/:slug*'
}
}
'packages/:pkg/src/:slug*': ':pkg/:slug*',
},
};
```
This maps `packages/pkg-a/src/intro.md``/pkg-a/intro.html`.
@@ -110,9 +113,9 @@ Rewrites can also be a function:
```ts
export default {
rewrites(id) {
return id.replace(/^packages\/([^/]+)\/src\//, '$1/')
}
}
return id.replace(/^packages\/([^/]+)\/src\//, '$1/');
},
};
```
## Public Directory
@@ -138,15 +141,15 @@ For sub-path deployment (e.g., GitHub Pages):
```ts
export default {
base: '/repo-name/'
}
base: '/repo-name/',
};
```
All absolute paths are automatically prefixed with base. For dynamic paths in components, use `withBase`:
```vue
<script setup>
import { withBase } from 'vitepress'
import { withBase } from 'vitepress';
</script>
<template>
@@ -14,8 +14,8 @@ Specify language after opening backticks:
````md
```js
export default {
name: 'MyComponent'
}
name: 'MyComponent',
};
```
````
@@ -61,10 +61,10 @@ Inline highlighting with comment:
export default {
data() {
return {
msg: 'Highlighted!' // [!code highlight]
}
}
}
msg: 'Highlighted!', // [!code highlight]
};
},
};
```
````
@@ -77,10 +77,10 @@ Blur other code and focus specific lines:
export default {
data() {
return {
msg: 'Focused!' // [!code focus]
}
}
}
msg: 'Focused!', // [!code focus]
};
},
};
```
````
@@ -116,11 +116,11 @@ Color lines as errors or warnings:
export default {
data() {
return {
msg: 'Error', // [!code error]
msg: 'Warning' // [!code warning]
}
}
}
msg: 'Error', // [!code error]
msg: 'Warning', // [!code warning]
};
},
};
```
````
@@ -132,9 +132,9 @@ Enable globally:
// .vitepress/config.ts
export default {
markdown: {
lineNumbers: true
}
}
lineNumbers: true,
},
};
```
Per-block override:
@@ -169,11 +169,15 @@ Tabbed code blocks:
::: code-group
```js [JavaScript]
export default { /* ... */ }
export default {
/* ... */
};
```
```ts [TypeScript]
export default defineConfig({ /* ... */ })
export default defineConfig({
/* ... */
});
```
:::
@@ -222,7 +226,7 @@ Add filename labels to code blocks:
````md
```js [vite.config.js]
export default defineConfig({})
export default defineConfig({});
```
````
@@ -17,17 +17,17 @@ export default {
load() {
return {
hello: 'world',
timestamp: Date.now()
}
}
}
timestamp: Date.now(),
};
},
};
```
Import the `data` named export:
```vue
<script setup>
import { data } from './example.data.ts'
import { data } from './example.data.ts';
</script>
<template>
@@ -43,10 +43,10 @@ Fetch remote data:
// api.data.ts
export default {
async load() {
const response = await fetch('https://api.example.com/data')
return response.json()
}
}
const response = await fetch('https://api.example.com/data');
return response.json();
},
};
```
## Local Files with Watch
@@ -55,21 +55,21 @@ Process local files with hot reload:
```ts
// posts.data.ts
import fs from 'node:fs'
import { parse } from 'csv-parse/sync'
import fs from 'node:fs';
import { parse } from 'csv-parse/sync';
export default {
watch: ['./data/*.csv'],
load(watchedFiles) {
// watchedFiles = array of absolute paths
return watchedFiles.map(file => {
return watchedFiles.map((file) => {
return parse(fs.readFileSync(file, 'utf-8'), {
columns: true,
skip_empty_lines: true
})
})
}
}
skip_empty_lines: true,
});
});
},
};
```
## createContentLoader
@@ -78,20 +78,20 @@ Helper for loading markdown content (common for blogs/archives):
```ts
// posts.data.ts
import { createContentLoader } from 'vitepress'
import { createContentLoader } from 'vitepress';
export default createContentLoader('posts/*.md')
export default createContentLoader('posts/*.md');
```
Returns array of `ContentData`:
```ts
interface ContentData {
url: string // e.g. /posts/hello.html
frontmatter: Record<string, any>
src?: string // raw markdown (opt-in)
html?: string // rendered HTML (opt-in)
excerpt?: string // excerpt HTML (opt-in)
url: string; // e.g. /posts/hello.html
frontmatter: Record<string, any>;
src?: string; // raw markdown (opt-in)
html?: string; // rendered HTML (opt-in)
excerpt?: string; // excerpt HTML (opt-in)
}
```
@@ -99,47 +99,47 @@ With options:
```ts
// posts.data.ts
import { createContentLoader } from 'vitepress'
import { createContentLoader } from 'vitepress';
export default createContentLoader('posts/*.md', {
includeSrc: true, // Include raw markdown
render: true, // Include rendered HTML
excerpt: true, // Include excerpt (content before first ---)
includeSrc: true, // Include raw markdown
render: true, // Include rendered HTML
excerpt: true, // Include excerpt (content before first ---)
transform(rawData) {
// Sort by date, newest first
return rawData
.sort((a, b) => +new Date(b.frontmatter.date) - +new Date(a.frontmatter.date))
.map(page => ({
.map((page) => ({
title: page.frontmatter.title,
url: page.url,
date: page.frontmatter.date,
excerpt: page.excerpt
}))
}
})
excerpt: page.excerpt,
}));
},
});
```
## Usage Example: Blog Index
```ts
// posts.data.ts
import { createContentLoader } from 'vitepress'
import { createContentLoader } from 'vitepress';
export default createContentLoader('posts/*.md', {
excerpt: true,
transform(data) {
return data
.filter(post => !post.frontmatter.draft)
.sort((a, b) => +new Date(b.frontmatter.date) - +new Date(a.frontmatter.date))
}
})
.filter((post) => !post.frontmatter.draft)
.sort((a, b) => +new Date(b.frontmatter.date) - +new Date(a.frontmatter.date));
},
});
```
```vue
<!-- posts/index.md -->
<script setup>
import { data as posts } from './posts.data.ts'
import { data as posts } from './posts.data.ts';
</script>
<template>
@@ -156,22 +156,22 @@ import { data as posts } from './posts.data.ts'
```ts
// example.data.ts
import { defineLoader } from 'vitepress'
import { defineLoader } from 'vitepress';
export interface Data {
posts: Array<{ title: string; url: string }>
posts: Array<{ title: string; url: string }>;
}
declare const data: Data
export { data }
declare const data: Data;
export { data };
export default defineLoader({
watch: ['./posts/*.md'],
async load(): Promise<Data> {
// ...
return { posts: [] }
}
})
return { posts: [] };
},
});
```
## In Build Hooks
@@ -180,28 +180,28 @@ Use in config for generating additional files:
```ts
// .vitepress/config.ts
import { createContentLoader } from 'vitepress'
import { createContentLoader } from 'vitepress';
export default {
async buildEnd() {
const posts = await createContentLoader('posts/*.md').load()
const posts = await createContentLoader('posts/*.md').load();
// Generate RSS feed, sitemap, etc.
}
}
},
};
```
## Accessing Config
```ts
// example.data.ts
import type { SiteConfig } from 'vitepress'
import type { SiteConfig } from 'vitepress';
export default {
load() {
const config: SiteConfig = (globalThis as any).VITEPRESS_CONFIG
return { base: config.site.base }
}
}
const config: SiteConfig = (globalThis as any).VITEPRESS_CONFIG;
return { base: config.site.base };
},
};
```
## Key Points
@@ -24,16 +24,13 @@ The paths loader exports a `paths` method returning route parameters:
// packages/[pkg].paths.js
export default {
paths() {
return [
{ params: { pkg: 'foo' }},
{ params: { pkg: 'bar' }},
{ params: { pkg: 'baz' }}
]
}
}
return [{ params: { pkg: 'foo' } }, { params: { pkg: 'bar' } }, { params: { pkg: 'baz' } }];
},
};
```
Generated pages:
- `/packages/foo.html`
- `/packages/bar.html`
- `/packages/baz.html`
@@ -52,12 +49,12 @@ Generated pages:
export default {
paths() {
return [
{ params: { pkg: 'foo', version: '1.0.0' }},
{ params: { pkg: 'foo', version: '2.0.0' }},
{ params: { pkg: 'bar', version: '1.0.0' }}
]
}
}
{ params: { pkg: 'foo', version: '1.0.0' } },
{ params: { pkg: 'foo', version: '2.0.0' } },
{ params: { pkg: 'bar', version: '1.0.0' } },
];
},
};
```
## Dynamic Path Generation
@@ -66,15 +63,15 @@ From local files:
```js
// packages/[pkg].paths.js
import fs from 'node:fs'
import fs from 'node:fs';
export default {
paths() {
return fs.readdirSync('packages').map(pkg => ({
params: { pkg }
}))
}
}
return fs.readdirSync('packages').map((pkg) => ({
params: { pkg },
}));
},
};
```
From remote API:
@@ -83,16 +80,16 @@ From remote API:
// packages/[pkg].paths.js
export default {
async paths() {
const packages = await fetch('https://api.example.com/packages').then(r => r.json())
return packages.map(pkg => ({
const packages = await fetch('https://api.example.com/packages').then((r) => r.json());
return packages.map((pkg) => ({
params: {
pkg: pkg.name,
version: pkg.version
}
}))
}
}
version: pkg.version,
},
}));
},
};
```
## Accessing Params in Page
@@ -101,6 +98,7 @@ Template globals:
```md
<!-- packages/[pkg].md -->
# Package: {{ $params.pkg }}
Version: {{ $params.version }}
@@ -110,8 +108,8 @@ In script:
```vue
<script setup>
import { useData } from 'vitepress'
const { params } = useData()
import { useData } from 'vitepress';
const { params } = useData();
</script>
<template>
@@ -127,23 +125,22 @@ For heavy content (raw markdown/HTML from CMS), use `content` instead of params
// posts/[slug].paths.js
export default {
async paths() {
const posts = await fetch('https://cms.example.com/posts').then(r => r.json())
return posts.map(post => ({
const posts = await fetch('https://cms.example.com/posts').then((r) => r.json());
return posts.map((post) => ({
params: { slug: post.slug },
content: post.content // Raw markdown or HTML
}))
}
}
content: post.content, // Raw markdown or HTML
}));
},
};
```
Render content in template:
```md
<!-- posts/[slug].md -->
---
title: {{ $params.title }}
---
## <!-- posts/[slug].md -->
## title: {{ $params.title }}
<!-- @content -->
```
@@ -157,61 +154,57 @@ Auto-rebuild when template or data files change:
```js
// posts/[slug].paths.js
export default {
watch: [
'./templates/**/*.njk',
'../data/**/*.json'
],
watch: ['./templates/**/*.njk', '../data/**/*.json'],
paths(watchedFiles) {
const dataFiles = watchedFiles.filter(f => f.endsWith('.json'))
return dataFiles.map(file => {
const data = JSON.parse(fs.readFileSync(file, 'utf-8'))
const dataFiles = watchedFiles.filter((f) => f.endsWith('.json'));
return dataFiles.map((file) => {
const data = JSON.parse(fs.readFileSync(file, 'utf-8'));
return {
params: { slug: data.slug },
content: renderTemplate(data)
}
})
}
}
content: renderTemplate(data),
};
});
},
};
```
## Complete Example: Blog
```js
// posts/[slug].paths.js
import fs from 'node:fs'
import matter from 'gray-matter'
import fs from 'node:fs';
import matter from 'gray-matter';
export default {
watch: ['./posts/*.md'],
paths(files) {
return files
.filter(f => !f.includes('[slug]'))
.map(file => {
const content = fs.readFileSync(file, 'utf-8')
const { data, content: body } = matter(content)
const slug = file.match(/([^/]+)\.md$/)[1]
.filter((f) => !f.includes('[slug]'))
.map((file) => {
const content = fs.readFileSync(file, 'utf-8');
const { data, content: body } = matter(content);
const slug = file.match(/([^/]+)\.md$/)[1];
return {
params: {
params: {
slug,
title: data.title,
date: data.date
date: data.date,
},
content: body
}
})
}
}
content: body,
};
});
},
};
```
```md
<!-- posts/[slug].md -->
---
layout: doc
---
## <!-- posts/[slug].md -->
## layout: doc
# {{ $params.title }}
@@ -80,15 +80,15 @@ Register in theme for use everywhere:
```ts
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import MyGlobalComponent from './MyGlobalComponent.vue'
import DefaultTheme from 'vitepress/theme';
import MyGlobalComponent from './MyGlobalComponent.vue';
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component('MyGlobalComponent', MyGlobalComponent)
}
}
app.component('MyGlobalComponent', MyGlobalComponent);
},
};
```
Then use in any markdown:
@@ -26,8 +26,8 @@ For sub-path deployment (e.g., `https://user.github.io/repo/`):
```ts
// .vitepress/config.ts
export default {
base: '/repo/'
}
base: '/repo/',
};
```
## GitHub Pages
@@ -93,11 +93,11 @@ For pnpm, add before setup-node:
Configure in dashboard:
| Setting | Value |
|---------|-------|
| Build Command | `npm run docs:build` |
| Setting | Value |
| ---------------- | ---------------------- |
| Build Command | `npm run docs:build` |
| Output Directory | `docs/.vitepress/dist` |
| Node Version | `20` (or above) |
| Node Version | `20` (or above) |
**Warning:** Don't enable "Auto Minify" for HTML - it removes Vue hydration comments.
@@ -217,13 +217,13 @@ Place in `docs/public/_headers`:
## Other Platforms
| Platform | Guide |
|----------|-------|
| Azure Static Web Apps | Set `app_location: /`, `output_location: docs/.vitepress/dist` |
| Surge | `npx surge docs/.vitepress/dist` |
| Heroku | Use `heroku-buildpack-static` |
| Render | Build: `npm run docs:build`, Publish: `docs/.vitepress/dist` |
| Kinsta | Follow [Kinsta docs](https://kinsta.com/docs/vitepress-static-site-example/) |
| Platform | Guide |
| --------------------- | ---------------------------------------------------------------------------- |
| Azure Static Web Apps | Set `app_location: /`, `output_location: docs/.vitepress/dist` |
| Surge | `npx surge docs/.vitepress/dist` |
| Heroku | Use `heroku-buildpack-static` |
| Render | Build: `npm run docs:build`, Publish: `docs/.vitepress/dist` |
| Kinsta | Follow [Kinsta docs](https://kinsta.com/docs/vitepress-static-site-example/) |
## Key Points
@@ -14,20 +14,20 @@ export default {
themeConfig: {
// Site title in nav (overrides config.title)
siteTitle: 'My Docs',
siteTitle: false, // Hide title
siteTitle: false, // Hide title
// Logo
logo: '/logo.svg',
logo: { light: '/light-logo.svg', dark: '/dark-logo.svg', alt: 'Logo' },
// Nav links
nav: [
{ text: 'Guide', link: '/guide/' },
{ text: 'API', link: '/api/' },
{ text: 'GitHub', link: 'https://github.com/...' }
]
}
}
{ text: 'GitHub', link: 'https://github.com/...' },
],
},
};
```
### Dropdown Menu
@@ -38,8 +38,8 @@ nav: [
text: 'Dropdown',
items: [
{ text: 'Item A', link: '/item-a' },
{ text: 'Item B', link: '/item-b' }
]
{ text: 'Item B', link: '/item-b' },
],
},
// With sections
{
@@ -49,12 +49,12 @@ nav: [
text: 'v2.x',
items: [
{ text: 'v2.0', link: '/v2/' },
{ text: 'v2.1', link: '/v2.1/' }
]
}
]
}
]
{ text: 'v2.1', link: '/v2.1/' },
],
},
],
},
];
```
### Active Match
@@ -66,9 +66,9 @@ nav: [
{
text: 'Guide',
link: '/guide/',
activeMatch: '/guide/' // Regex pattern
}
]
activeMatch: '/guide/', // Regex pattern
},
];
```
## Sidebar
@@ -81,10 +81,10 @@ sidebar: [
text: 'Guide',
items: [
{ text: 'Introduction', link: '/guide/' },
{ text: 'Getting Started', link: '/guide/getting-started' }
]
}
]
{ text: 'Getting Started', link: '/guide/getting-started' },
],
},
];
```
### Multiple Sidebars
@@ -154,7 +154,7 @@ sidebar: {
```ts
themeConfig: {
search: {
provider: 'local'
provider: 'local';
}
}
```
@@ -199,9 +199,9 @@ socialLinks: [
{
icon: { svg: '<svg>...</svg>' },
link: 'https://...',
ariaLabel: 'Custom Link'
}
]
ariaLabel: 'Custom Link',
},
];
```
## Footer
@@ -232,8 +232,8 @@ Enable in site config:
```ts
export default {
lastUpdated: true // Get timestamp from git
}
lastUpdated: true, // Get timestamp from git
};
```
Customize display:
@@ -262,9 +262,9 @@ outline: {
Or just the level:
```ts
outline: 'deep' // Same as [2, 6]
outline: 2 // Only h2
outline: [2, 4] // h2 through h4
outline: 'deep'; // Same as [2, 6]
outline: 2; // Only h2
outline: [2, 4]; // h2 through h4
```
## Doc Footer Navigation
@@ -284,7 +284,7 @@ docFooter: {
## External Link Icon
```ts
externalLinkIcon: true // Show icon on external links
externalLinkIcon: true; // Show icon on external links
```
## Appearance Toggle Labels
@@ -13,14 +13,14 @@ Create `.vitepress/theme/index.ts`:
```ts
// .vitepress/theme/index.ts
import Layout from './Layout.vue'
import Layout from './Layout.vue';
export default {
Layout,
enhanceApp({ app, router, siteData }) {
// Register global components, plugins, etc.
}
}
},
};
```
## Theme Interface
@@ -28,19 +28,19 @@ export default {
```ts
interface Theme {
// Required: Root layout component
Layout: Component
Layout: Component;
// Optional: Enhance Vue app instance
enhanceApp?: (ctx: EnhanceAppContext) => Awaitable<void>
enhanceApp?: (ctx: EnhanceAppContext) => Awaitable<void>;
// Optional: Extend another theme
extends?: Theme
extends?: Theme;
}
interface EnhanceAppContext {
app: App // Vue app instance
router: Router // VitePress router
siteData: Ref<SiteData> // Site-level metadata
app: App; // Vue app instance
router: Router; // VitePress router
siteData: Ref<SiteData>; // Site-level metadata
}
```
@@ -51,8 +51,8 @@ The Layout component must render `<Content />` for markdown:
```vue
<!-- .vitepress/theme/Layout.vue -->
<script setup>
import { useData } from 'vitepress'
const { page, frontmatter } = useData()
import { useData } from 'vitepress';
const { page, frontmatter } = useData();
</script>
<template>
@@ -60,21 +60,21 @@ const { page, frontmatter } = useData()
<header>
<nav>My Site</nav>
</header>
<main>
<div v-if="page.isNotFound">
<h1>404 - Page Not Found</h1>
</div>
<div v-else-if="frontmatter.layout === 'home'">
<h1>Welcome!</h1>
</div>
<article v-else>
<Content />
</article>
</main>
<footer>
<p>© 2024 My Site</p>
</footer>
@@ -88,27 +88,27 @@ Access VitePress data in your theme:
```vue
<script setup>
import { useData, useRoute, useRouter } from 'vitepress'
import { useData, useRoute, useRouter } from 'vitepress';
// Page and site data
const {
site, // Site config (title, description, etc.)
theme, // Theme config
page, // Current page data
const {
site, // Site config (title, description, etc.)
theme, // Theme config
page, // Current page data
frontmatter, // Current page frontmatter
title, // Page title
title, // Page title
description, // Page description
lang, // Current language
isDark, // Dark mode state
params // Dynamic route params
} = useData()
lang, // Current language
isDark, // Dark mode state
params, // Dynamic route params
} = useData();
// Routing
const route = useRoute()
const router = useRouter()
const route = useRoute();
const router = useRouter();
// Navigate programmatically
const goToGuide = () => router.go('/guide/')
const goToGuide = () => router.go('/guide/');
</script>
```
@@ -116,13 +116,13 @@ const goToGuide = () => router.go('/guide/')
```vue
<script setup>
import { Content } from 'vitepress'
import { Content } from 'vitepress';
</script>
<template>
<!-- Renders markdown content -->
<Content />
<!-- Renders slot only on client (SSR-safe) -->
<ClientOnly>
<NonSSRComponent />
@@ -136,36 +136,36 @@ Build on top of default theme or any other:
```ts
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import DefaultTheme from 'vitepress/theme';
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
// Your customizations
}
}
},
};
```
## Register Plugins and Components
```ts
// .vitepress/theme/index.ts
import Layout from './Layout.vue'
import GlobalComponent from './GlobalComponent.vue'
import Layout from './Layout.vue';
import GlobalComponent from './GlobalComponent.vue';
export default {
Layout,
enhanceApp({ app }) {
// Register global component
app.component('GlobalComponent', GlobalComponent)
app.component('GlobalComponent', GlobalComponent);
// Register plugin
app.use(MyPlugin)
app.use(MyPlugin);
// Provide/inject
app.provide('key', value)
}
}
app.provide('key', value);
},
};
```
## Async enhanceApp
@@ -178,11 +178,11 @@ export default {
async enhanceApp({ app }) {
if (!import.meta.env.SSR) {
// Client-only plugin
const plugin = await import('browser-only-plugin')
app.use(plugin.default)
const plugin = await import('browser-only-plugin');
app.use(plugin.default);
}
}
}
},
};
```
## Theme-Aware Layout
@@ -191,13 +191,13 @@ Handle different page layouts:
```vue
<script setup>
import { useData } from 'vitepress'
import Home from './Home.vue'
import Doc from './Doc.vue'
import Page from './Page.vue'
import NotFound from './NotFound.vue'
import { useData } from 'vitepress';
import Home from './Home.vue';
import Doc from './Doc.vue';
import Page from './Page.vue';
import NotFound from './NotFound.vue';
const { page, frontmatter } = useData()
const { page, frontmatter } = useData();
</script>
<template>
@@ -214,28 +214,28 @@ As npm package:
```ts
// my-theme/index.ts
import Layout from './Layout.vue'
export default { Layout }
import Layout from './Layout.vue';
export default { Layout };
// Export types for config
export type { ThemeConfig } from './types'
export type { ThemeConfig } from './types';
```
Consumer usage:
```ts
// .vitepress/theme/index.ts
import Theme from 'my-vitepress-theme'
import Theme from 'my-vitepress-theme';
export default Theme
export default Theme;
// Or extend it
export default {
extends: Theme,
enhanceApp({ app }) {
// Additional customization
}
}
},
};
```
## Theme Config Types
@@ -244,14 +244,14 @@ For custom theme config types:
```ts
// .vitepress/config.ts
import { defineConfigWithTheme } from 'vitepress'
import type { ThemeConfig } from 'my-theme'
import { defineConfigWithTheme } from 'vitepress';
import type { ThemeConfig } from 'my-theme';
export default defineConfigWithTheme<ThemeConfig>({
themeConfig: {
// Type-checked theme config
}
})
},
});
```
## Key Points
@@ -13,10 +13,10 @@ Create `.vitepress/theme/index.ts` to extend the default theme:
```ts
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import './custom.css'
import DefaultTheme from 'vitepress/theme';
import './custom.css';
export default DefaultTheme
export default DefaultTheme;
```
## CSS Variables
@@ -30,11 +30,11 @@ Override root CSS variables:
--vp-c-brand-1: #646cff;
--vp-c-brand-2: #747bff;
--vp-c-brand-3: #9499ff;
/* Backgrounds */
--vp-c-bg: #ffffff;
--vp-c-bg-soft: #f6f6f7;
/* Text */
--vp-c-text-1: #213547;
--vp-c-text-2: #476582;
@@ -55,7 +55,7 @@ See [all CSS variables](https://github.com/vuejs/vitepress/blob/main/src/client/
/* Gradient name color */
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: linear-gradient(120deg, #bd34fe, #41d1ff);
/* Hero image glow */
--vp-home-hero-image-background-image: linear-gradient(-45deg, #bd34fe 50%, #47caff 50%);
--vp-home-hero-image-filter: blur(44px);
@@ -68,10 +68,10 @@ Remove Inter font and use your own:
```ts
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme-without-fonts'
import './fonts.css'
import DefaultTheme from 'vitepress/theme-without-fonts';
import './fonts.css';
export default DefaultTheme
export default DefaultTheme;
```
```css
@@ -93,14 +93,17 @@ Preload fonts in config:
// .vitepress/config.ts
export default {
transformHead({ assets }) {
const fontFile = assets.find(file => /myfont\.[\w-]+\.woff2/.test(file))
const fontFile = assets.find((file) => /myfont\.[\w-]+\.woff2/.test(file));
if (fontFile) {
return [
['link', { rel: 'preload', href: fontFile, as: 'font', type: 'font/woff2', crossorigin: '' }]
]
[
'link',
{ rel: 'preload', href: fontFile, as: 'font', type: 'font/woff2', crossorigin: '' },
],
];
}
}
}
},
};
```
## Global Components
@@ -109,15 +112,15 @@ Register components available in all markdown:
```ts
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import MyComponent from './components/MyComponent.vue'
import DefaultTheme from 'vitepress/theme';
import MyComponent from './components/MyComponent.vue';
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component('MyComponent', MyComponent)
}
}
app.component('MyComponent', MyComponent);
},
};
```
Use in markdown:
@@ -132,20 +135,20 @@ Inject content into specific locations:
```ts
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import MyLayout from './MyLayout.vue'
import DefaultTheme from 'vitepress/theme';
import MyLayout from './MyLayout.vue';
export default {
extends: DefaultTheme,
Layout: MyLayout
}
Layout: MyLayout,
};
```
```vue
<!-- .vitepress/theme/MyLayout.vue -->
<script setup>
import DefaultTheme from 'vitepress/theme'
const { Layout } = DefaultTheme
import DefaultTheme from 'vitepress/theme';
const { Layout } = DefaultTheme;
</script>
<template>
@@ -153,11 +156,11 @@ const { Layout } = DefaultTheme
<template #aside-outline-before>
<div>Above outline</div>
</template>
<template #doc-before>
<div>Before doc content</div>
</template>
<template #doc-after>
<div>After doc content</div>
</template>
@@ -168,6 +171,7 @@ const { Layout } = DefaultTheme
### Available Slots
**Doc layout (`layout: doc`):**
- `doc-top`, `doc-bottom`
- `doc-before`, `doc-after`
- `doc-footer-before`
@@ -177,15 +181,18 @@ const { Layout } = DefaultTheme
- `aside-ads-before`, `aside-ads-after`
**Home layout (`layout: home`):**
- `home-hero-before`, `home-hero-after`
- `home-hero-info-before`, `home-hero-info`, `home-hero-info-after`
- `home-hero-actions-after`, `home-hero-image`
- `home-features-before`, `home-features-after`
**Page layout (`layout: page`):**
- `page-top`, `page-bottom`
**Always available:**
- `layout-top`, `layout-bottom`
- `nav-bar-title-before`, `nav-bar-title-after`
- `nav-bar-content-before`, `nav-bar-content-after`
@@ -197,18 +204,18 @@ Alternative to template slots:
```ts
// .vitepress/theme/index.ts
import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import MyComponent from './MyComponent.vue'
import { h } from 'vue';
import DefaultTheme from 'vitepress/theme';
import MyComponent from './MyComponent.vue';
export default {
extends: DefaultTheme,
Layout() {
return h(DefaultTheme.Layout, null, {
'aside-outline-before': () => h(MyComponent)
})
}
}
'aside-outline-before': () => h(MyComponent),
});
},
};
```
## Override Internal Components
@@ -217,7 +224,7 @@ Replace default theme components with Vite aliases:
```ts
// .vitepress/config.ts
import { fileURLToPath, URL } from 'node:url'
import { fileURLToPath, URL } from 'node:url';
export default {
vite: {
@@ -226,13 +233,13 @@ export default {
{
find: /^.*\/VPNavBar\.vue$/,
replacement: fileURLToPath(
new URL('./theme/components/CustomNavBar.vue', import.meta.url)
)
}
]
}
}
}
new URL('./theme/components/CustomNavBar.vue', import.meta.url),
),
},
],
},
},
};
```
## View Transitions
@@ -242,33 +249,37 @@ Custom dark mode toggle animation:
```vue
<!-- .vitepress/theme/Layout.vue -->
<script setup>
import { useData } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import { nextTick, provide } from 'vue'
import { useData } from 'vitepress';
import DefaultTheme from 'vitepress/theme';
import { nextTick, provide } from 'vue';
const { isDark } = useData()
const { isDark } = useData();
provide('toggle-appearance', async ({ clientX: x, clientY: y }) => {
if (!document.startViewTransition) {
isDark.value = !isDark.value
return
isDark.value = !isDark.value;
return;
}
const clipPath = [
`circle(0px at ${x}px ${y}px)`,
`circle(${Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y))}px at ${x}px ${y}px)`
]
`circle(${Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y))}px at ${x}px ${y}px)`,
];
await document.startViewTransition(async () => {
isDark.value = !isDark.value
await nextTick()
}).ready
isDark.value = !isDark.value;
await nextTick();
}).ready;
document.documentElement.animate(
{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
{ duration: 300, easing: 'ease-in', pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)` }
)
})
{
duration: 300,
easing: 'ease-in',
pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`,
},
);
});
</script>
<template>