Merge branch 'main' into zh

pull/1593/head
Xavi Lee 4 years ago committed by GitHub
commit 3dbbd80ec3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -23,7 +23,7 @@ jobs:
strategy:
matrix:
node_version: [14, 16, 18]
node_version: [16, 18]
steps:
- name: Checkout

@ -1,3 +1,32 @@
# [1.0.0-alpha.50](https://github.com/vuejs/vitepress/compare/v1.0.0-alpha.49...v1.0.0-alpha.50) (2023-03-07)
### Bug Fixes
* avoid deprecation warning when using --force ([0c0b6cc](https://github.com/vuejs/vitepress/commit/0c0b6cc5a3a06bb0bee14dc854c7c1102a1b6657))
* ensure HMR works properly for page outline ([1457681](https://github.com/vuejs/vitepress/commit/1457681484c873a7801729f9a9e11872b60b4868)), closes [#1281](https://github.com/vuejs/vitepress/issues/1281)
* extract all headers by default ([580a8e1](https://github.com/vuejs/vitepress/commit/580a8e1a551089e973745fd224d97aec9d3fa702))
* respect command line minify and outDir options ([22047f3](https://github.com/vuejs/vitepress/commit/22047f3363af290687cb3077ff617ae550af6e8a))
* **theme:** make tip box text color darker ([3158115](https://github.com/vuejs/vitepress/commit/3158115afc8f15bee3e35644a33328b02dee6d6d))
* **theme:** prevent text wrapping in nav dropdown menu ([2a1abbe](https://github.com/vuejs/vitepress/commit/2a1abbe45e10b38f03795357cd52dc4f6cea5dfc))
### Features
* **data-loader:** defineLoader() type helper ([4673bb1](https://github.com/vuejs/vitepress/commit/4673bb187905374896b7a1a3b1a1e5ad3777bdc4))
* **data-loader:** pass watched files into load() ([e29b6a0](https://github.com/vuejs/vitepress/commit/e29b6a051e89e23945e2acfdfca7057978929715))
* deprecate Theme.setup ([868a586](https://github.com/vuejs/vitepress/commit/868a58670e747310bba1f0f900a76243c6473da3))
* export loadEnv from vite ([7609704](https://github.com/vuejs/vitepress/commit/76097048f3570b3f2417ac76ef177ce16afb9116))
* expose isNotFound on PageData, deperecate Theme.NotFound ([74caccd](https://github.com/vuejs/vitepress/commit/74caccda4342feee3ab980b1a446ef7ec4819e0f))
* expose params at top level in useData() ([66f94fd](https://github.com/vuejs/vitepress/commit/66f94fd7a0f43882386d32769b6b98014154ffa6))
* support $params in page components ([a4ac055](https://github.com/vuejs/vitepress/commit/a4ac055dbf42848206683611a8d15e09572441ac))
* support Theme.extends ([f39b6a9](https://github.com/vuejs/vitepress/commit/f39b6a98d6d2cc9ba405204a4d7a91eadce64a0d))
* **theme:** add `as` prop to `Content` ([#2011](https://github.com/vuejs/vitepress/issues/2011)) ([254e15b](https://github.com/vuejs/vitepress/commit/254e15beb9b895c081e301eb379cbc2551b3e53c))
* **theme:** add `home-hero-info` slot ([#1807](https://github.com/vuejs/vitepress/issues/1807)) ([996a5f4](https://github.com/vuejs/vitepress/commit/996a5f47e9064da839aef9e81db22db70fa8d76d))
* vitepress init command ([#2020](https://github.com/vuejs/vitepress/issues/2020)) ([38bbdad](https://github.com/vuejs/vitepress/commit/38bbdaddb72ec426865d731c2f443e545e5bbbd7)), closes [#1252](https://github.com/vuejs/vitepress/issues/1252)
# [1.0.0-alpha.49](https://github.com/vuejs/vitepress/compare/v1.0.0-alpha.48...v1.0.0-alpha.49) (2023-02-28)

@ -51,6 +51,19 @@ const sidebar: DefaultTheme.Config['sidebar'] = {
link: '/dynamic-routes/bar'
}
]
},
{
text: 'Markdown Extensions',
items: [
{
text: 'Test Page',
link: '/markdown-extensions/'
},
{
text: 'Foo',
link: '/markdown-extensions/foo'
}
]
}
],
'/multi-sidebar/': [

@ -1,8 +1,3 @@
<script setup>
import { useData } from 'vitepress'
const { page } = useData()
</script>
<!-- @content -->
<pre class="params">{{ page.params }}</pre>
<pre class="params">{{ $params }}</pre>

@ -0,0 +1,7 @@
# Foo
<!-- #region snippet -->
## Region
this is region
<!-- #endregion snippet -->

@ -0,0 +1,178 @@
# Markdown Extensions
## Links
### Internal Links
- [home](/)
- [markdown-extensions](/markdown-extensions/)
- [heading](./#internal-links)
- [omit extension](./foo)
- [.md extension](./foo.md)
- [.html extension](./foo.html)
### External Links
[VitePress on GitHub](https://github.com/vuejs/vitepress)
## GitHub-Style Tables
| Tables | Are | Cool |
| ------------- | :-----------: | -----: |
| col 3 is | right-aligned | \$1600 |
| col 2 is | centered | \$12 |
| zebra stripes | are neat | \$1 |
## Emoji
- :tada:
- :100:
## Table of Contents
[[toc]]
## Custom Containers
### Default Title
::: info
This is an info box.
:::
::: tip
This is a tip.
:::
::: warning
This is a warning.
:::
::: danger
This is a dangerous warning.
:::
::: details
This is a details block.
:::
### Custom Title
::: danger STOP
Danger zone, do not proceed
:::
::: details Click me to view the code
```js
console.log('Hello, VitePress!')
```
:::
## Line Highlighting in Code Blocks
### Single Line
```js{4}
export default {
data () {
return {
msg: 'Highlighted!'
}
}
}
```
### Multiple single lines, ranges
```js{1,4,6-8}
export default {
data () {
return {
msg: `Highlighted!
This line isn't highlighted,
but this and the next 2 are.`,
motd: 'VitePress is awesome',
lorem: 'ipsum',
}
}
}
```
### Comment Highlight
```js
export default { // [!code focus]
data() { // [!code hl]
return {
msg: 'Removed' // [!code --]
msg: 'Added' // [!code ++]
msg: 'Error', // [!code error]
msg: 'Warning' // [!code warning]
}
}
}
```
## Line Numbers
```ts:line-numbers
const line1 = 'This is line 1'
const line2 = 'This is line 2'
```
## Import Code Snippets
### Basic Code Snippet
<<< @/markdown-extensions/foo.md
### Specify Region
<<< @/markdown-extensions/foo.md#snippet
### With Other Features
<<< @/markdown-extensions/foo.md#snippet{1 ts:line-numbers} [snippet with region]
## Code Groups
### Basic Code Group
::: code-group
```js [config.js]
/**
* @type {import('vitepress').UserConfig}
*/
const config = {
// ...
}
export default config
```
```ts [config.ts]
import type { UserConfig } from 'vitepress'
const config: UserConfig = {
// ...
}
export default config
```
:::
### With Other Features
::: code-group
<<< @/markdown-extensions/foo.md
<<< @/markdown-extensions/foo.md#snippet{1 ts:line-numbers} [snippet with region]
:::
## Markdown File Inclusion
<!--@include: ./foo.md-->

@ -0,0 +1,232 @@
import type { Locator } from 'playwright-chromium'
const getClassList = async (locator: Locator) => {
const className = await locator.getAttribute('class')
return className?.split(' ').filter(Boolean) ?? []
}
beforeEach(async () => {
await goto('/markdown-extensions/')
})
describe('Links', () => {
test('render internal link', async () => {
const targetMap = Object.entries({
home: '/',
'markdown-extensions': '/markdown-extensions/',
heading: './#internal-links',
'omit extension': './foo.html',
'.md extension': './foo.html',
'.html extension': './foo.html'
})
const items = page.locator('#internal-links +ul a')
const count = await items.count()
expect(count).toBe(6)
for (let i = 0; i < count; i++) {
const [text, href] = targetMap[i]
expect(await items.nth(i).textContent()).toBe(text)
expect(await items.nth(i).getAttribute('href')).toBe(href)
}
})
test('external link get target="_blank" and rel="noreferrer"', async () => {
const link = page.locator('#external-links + p a')
expect(await link.getAttribute('target')).toBe('_blank')
expect(await link.getAttribute('rel')).toBe('noreferrer')
})
})
describe('GitHub-Style Tables', () => {
test('render table', async () => {
const table = page.locator('#github-style-tables + table')
expect(table).toBeTruthy()
})
})
describe('Emoji', () => {
test('render emoji', async () => {
const emojis = ['🎉', '💯']
const items = page.locator('#emoji + ul li')
const count = await items.count()
expect(count).toBe(2)
for (let i = 0; i < count; i++) {
expect(await items.nth(i).textContent()).toBe(emojis[i])
}
})
})
describe('Table of Contents', () => {
test('render toc', async () => {
const items = page.locator('#table-of-contents + nav ul li')
const count = await items.count()
expect(count).toBe(23)
})
})
describe('Custom Containers', () => {
enum CustomBlocks {
Info = 'INFO',
Tip = 'TIP',
Warning = 'WARNING',
Danger = 'DANGER',
Details = 'Details'
}
const classnameMap = {
[CustomBlocks.Info]: 'info',
[CustomBlocks.Tip]: 'tip',
[CustomBlocks.Warning]: 'warning',
[CustomBlocks.Danger]: 'danger',
[CustomBlocks.Details]: 'details'
}
const getTitleText = (locator: Locator, type: CustomBlocks) => {
if (type === CustomBlocks.Details) {
return locator.locator('summary').textContent()
} else {
return locator.locator('.custom-block-title').textContent()
}
}
test('default title', async () => {
const blocks = page.locator('#default-title ~ .custom-block')
for (const [index, type] of Object.values(CustomBlocks).entries()) {
const block = blocks.nth(index)
const classList = await getClassList(block)
expect(classList).contain(classnameMap[type as CustomBlocks])
expect(await getTitleText(block, type)).toBe(type)
}
})
test('custom Title', async () => {
const blocks = page.locator('#custom-title ~ .custom-block')
expect(await getTitleText(blocks.nth(0), CustomBlocks.Danger)).toBe('STOP')
expect(await getTitleText(blocks.nth(1), CustomBlocks.Details)).toBe(
'Click me to view the code'
)
})
})
describe('Line Highlighting in Code Blocks', () => {
test('single line', async () => {
const classList = await getClassList(
page.locator('#single-line + div code > span').nth(3)
)
expect(classList).toContain('highlighted')
})
test('multiple single lines, ranges', async () => {
const lines = page.locator(
'#multiple-single-lines-ranges + div code > span'
)
for (const num of [1, 4, 6, 7, 8]) {
expect(await getClassList(lines.nth(num - 1))).toContain('highlighted')
}
})
test('comment highlight', async () => {
const lines = page.locator('#comment-highlight + div code > span')
expect(await getClassList(lines.nth(0))).toContain('has-focus')
expect(await getClassList(lines.nth(1))).toContain('highlighted')
expect(await getClassList(lines.nth(3))).toContain('diff')
expect(await getClassList(lines.nth(3))).toContain('remove')
expect(await getClassList(lines.nth(4))).toContain('diff')
expect(await getClassList(lines.nth(4))).toContain('add')
expect(await getClassList(lines.nth(5))).toContain('highlighted')
expect(await getClassList(lines.nth(5))).toContain('error')
expect(await getClassList(lines.nth(6))).toContain('highlighted')
expect(await getClassList(lines.nth(6))).toContain('warning')
})
})
describe('Line Numbers', () => {
test('render line numbers', async () => {
const div = page.locator('#line-numbers + div')
expect(await getClassList(div)).toContain('line-numbers-mode')
const lines = div.locator('.line-numbers-wrapper > span')
expect(await lines.count()).toBe(2)
})
})
describe('Import Code Snippets', () => {
test('basic', async () => {
const lines = page.locator('#basic-code-snippet + div code > span')
expect(await lines.count()).toBe(7)
})
test('specify region', async () => {
const lines = page.locator('#specify-region + div code > span')
expect(await lines.count()).toBe(3)
})
test('with other features', async () => {
const div = page.locator('#with-other-features + div')
expect(await getClassList(div)).toContain('line-numbers-mode')
const lines = div.locator('code > span')
expect(await lines.count()).toBe(3)
expect(await getClassList(lines.nth(0))).toContain('highlighted')
})
})
describe('Code Groups', () => {
test('basic', async () => {
const div = page.locator('#basic-code-group + div')
// tabs
const labels = div.locator('.tabs > label')
const labelNames = ['config.js', 'config.ts']
const count = await labels.count()
expect(count).toBe(2)
for (let i = 0; i < count; i++) {
const text = await labels.nth(i).textContent()
expect(text).toBe(labelNames[i])
}
// blocks
const blocks = div.locator('.blocks > div')
expect(await getClassList(blocks.nth(0))).toContain('active')
await labels.nth(1).click()
expect(await getClassList(blocks.nth(1))).toContain('active')
})
test('with other features', async () => {
const div = page.locator('#with-other-features-1 + div')
// tabs
const labels = div.locator('.tabs > label')
const labelNames = ['foo.md', 'snippet with region']
const count = await labels.count()
expect(count).toBe(2)
for (let i = 0; i < count; i++) {
const text = await labels.nth(i).textContent()
expect(text).toBe(labelNames[i])
}
// blocks
const blocks = div.locator('.blocks > div')
expect(await blocks.nth(0).locator('code > span').count()).toBe(7)
expect(await getClassList(blocks.nth(1))).toContain('line-numbers-mode')
expect(await getClassList(blocks.nth(1))).toContain('language-ts')
expect(await blocks.nth(1).locator('code > span').count()).toBe(3)
expect(
await getClassList(blocks.nth(1).locator('code > span').nth(0))
).toContain('highlighted')
})
})
describe('Markdown File Inclusion', () => {
test('render markdown', async () => {
const h1 = page.locator('#markdown-file-inclusion + h1')
expect(await h1.getAttribute('id')).toBe('foo')
})
})

@ -14,7 +14,8 @@ describe('test multi sidebar sort root', () => {
'& <Text Literals &> code',
'Static Data',
'Multi Sidebar Test',
'Dynamic Routes'
'Dynamic Routes',
'Markdown Extensions'
])
})
})

@ -1,23 +1,20 @@
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
const dirname = path.dirname(fileURLToPath(import.meta.url))
import { defineLoader } from 'vitepress'
type Data = Record<string, boolean>[]
export declare const data: Data
export default {
export default defineLoader({
watch: ['./data/*'],
async load(): Promise<Data> {
async load(files: string[]): Promise<Data> {
const foo = fs.readFileSync(
path.resolve(dirname, './data/foo.json'),
files.find((f) => f.endsWith('foo.json'))!,
'utf-8'
)
const bar = fs.readFileSync(
path.resolve(dirname, './data/bar.json'),
files.find((f) => f.endsWith('bar.json'))!,
'utf-8'
)
return [JSON.parse(foo), JSON.parse(bar)]
}
}
})

@ -7,14 +7,23 @@ describe('client/theme-default/composables/outline', () => {
resolveHeaders(
[
{
level: 2,
title: 'h2 - 1',
link: '#h2-1'
},
{
level: 3,
title: 'h3 - 1',
link: '#h3-1'
level: 1,
title: 'h1 - 1',
link: '#h1-1',
children: [
{
level: 2,
title: 'h2 - 1',
link: '#h2-1',
children: [
{
level: 3,
title: 'h3 - 1',
link: '#h3-1'
}
]
}
]
}
],
[2, 3]
@ -42,12 +51,14 @@ describe('client/theme-default/composables/outline', () => {
{
level: 2,
title: 'h2 - 1',
link: '#h2-1'
},
{
level: 3,
title: 'h3 - 1',
link: '#h3-1'
link: '#h2-1',
children: [
{
level: 3,
title: 'h3 - 1',
link: '#h3-1'
}
]
}
],
2
@ -68,42 +79,52 @@ describe('client/theme-default/composables/outline', () => {
{
level: 2,
title: 'h2 - 1',
link: '#h2-1'
},
{
level: 3,
title: 'h3 - 1',
link: '#h3-1'
},
{
level: 4,
title: 'h4 - 1',
link: '#h4-1'
},
{
level: 3,
title: 'h3 - 2',
link: '#h3-2'
},
{
level: 4,
title: 'h4 - 2',
link: '#h4-2'
link: '#h2-1',
children: [
{
level: 3,
title: 'h3 - 1',
link: '#h3-1',
children: [
{
level: 4,
title: 'h4 - 1',
link: '#h4-1'
}
]
},
{
level: 3,
title: 'h3 - 2',
link: '#h3-2',
children: [
{
level: 4,
title: 'h4 - 2',
link: '#h4-2'
}
]
}
]
},
{
level: 2,
title: 'h2 - 2',
link: '#h2-2'
},
{
level: 3,
title: 'h3 - 3',
link: '#h3-3'
},
{
level: 4,
title: 'h4 - 3',
link: '#h4-3'
link: '#h2-2',
children: [
{
level: 3,
title: 'h3 - 3',
link: '#h3-3',
children: [
{
level: 4,
title: 'h4 - 3',
link: '#h4-3'
}
]
}
]
}
],
'deep'

@ -12,7 +12,7 @@ export default defineConfig({
cleanUrls: true,
head: [['meta', { name: 'theme-color', content: '#3c8772' }]],
markdown: {
headers: {
level: [0, 0]

@ -1,192 +0,0 @@
# Frontmatter Config
Frontmatter enables page based configuration. In every markdown file, you can use frontmatter config to override app-level or theme config options. Also, there are config options which you can only define in frontmatter.
```yaml
---
title: Docs with VitePress
editLink: true
---
```
You can access frontmatter by `$frontmatter` helper inside any markdown file.
```md
{{ $frontmatter.title }}
```
## title
- Type: `string`
Title for the page. It's same as [config.title](../config/app-config#title), and it overrides the app config.
```yaml
---
title: VitePress
---
```
## titleTemplate
- Type: `string | boolean`
The suffix for the title. It's same as [config.titleTemplate](../config/app-config#titletemplate), and it overrides the app config.
```yaml
---
title: VitePress
titleTemplate: Vite & Vue powered static site generator
---
```
## description
- Type: `string`
Description for the page. It's same as [config.description](../config/app-config#description), and it overrides the app config.
```yaml
---
description: VitePress
---
```
## head
- Type: `HeadConfig[]`
Specify extra head tags to be injected:
```yaml
---
head:
- - meta
- name: description
content: hello
- - meta
- name: keywords
content: super duper SEO
---
```
```ts
type HeadConfig =
| [string, Record<string, string>]
| [string, Record<string, string>, string]
```
## lastUpdated
- Type: `boolean`
- Default: `true`
Whether to display [Last Updated](../guide/theme-last-updated) text in the current page.
```yaml
---
lastUpdated: false
---
```
## layout
- Type: `doc | home | page`
- Default: `doc`
Determines the layout of the page.
- `doc` - It applies default documentation styles to the markdown content.
- `home` - Special layout for "Home Page". You may add extra options such as `hero` and `features` to rapidly create beautiful landing page.
- `page` - Behave similar to `doc` but it applies no styles to the content. Useful when you want to create a fully custom page.
```yaml
---
layout: doc
---
```
## hero
- Type: `Hero`
This option only takes effect when `layout` is set to `home`.
It defines contents of home hero section.
```yaml
---
layout: home
hero:
name: VitePress
text: Vite & Vue powered static site generator.
tagline: Lorem ipsum...
actions:
- theme: brand
text: Get Started
link: /guide/what-is-vitepress
- theme: alt
text: View on GitHub
link: https://github.com/vuejs/vitepress
---
```
```ts
interface Hero {
// The string shown top of `text`. Comes with brand color
// and expected to be short, such as product name.
name?: string
// The main text for the hero section. This will be defined
// as `h1` tag.
text: string
// Tagline displayed below `text`.
tagline?: string
// Action buttons to display in home hero section.
actions?: HeroAction[]
}
interface HeroAction {
// Color theme of the button. Defaults to `brand`.
theme?: 'brand' | 'alt'
// Label of the button.
text: string
// Destination link of the button.
link: string
}
```
## features
- Type: `Feature[]`
This option only takes effect when `layout` is set to `home`.
It defines items to display in features section.
You may learn more about it in [Theme: Home Page](../guide/theme-home-page).
## aside
- Type: `boolean`
- Default: `true`
If you want the right aside component in `doc` layout not to be shown, set this option to `false`.
```yaml
---
aside: false
---
```
## outline
- Type: `number | [number, number] | 'deep' | false`
- Default: `2`
The levels of header in the outline to display for the page. It's same as [config.themeConfig.outline](../config/theme-config#outline), and it overrides the theme config.

@ -1,76 +0,0 @@
# Introduction
Place your configuration file at `.vitepress/config.js`. This is where all VitePress-specific files will be placed.
```
.
├─ docs
│ ├─ .vitepress
│ │ └─ config.js
│ └─ index.md
└─ package.json
```
::: tip
You can also use any of `.ts`, `.cjs`, `.mjs`, `.cts`, `.mts` as the config file extension.
:::
VitePress comes with 2 types of configuration. One is the [App Config](./app-config) which configures the site's fundamental features such as setting title of the site, or customize how markdown parser works. Second is the [Theme Config](./theme-config) which configures the theme of the site, for example, adding a sidebar, or add features such as "Edit this page on GitHub" link.
There's also another configuration you may do in [Frontmatter](./frontmatter-config). Frontmatter config can override global config defined in App Config or Theme Config for that specific page. However, there're several options that are only available at frontmatter as well.
Please refer to the corresponding config page to learn more.
## Config Intellisense
Since VitePress ships with TypeScript typings, you can leverage your IDE's intellisense with jsdoc type hints:
```js
/**
* @type {import('vitepress').UserConfig}
*/
const config = {
// ...
}
export default config
```
Alternatively, you can use the `defineConfig` helper at which should provide intellisense without the need for jsdoc annotations:
```js
import { defineConfig } from 'vitepress'
export default defineConfig({
// ...
})
```
VitePress also directly supports TS config files. You can use `.vitepress/config.ts` with the `defineConfig` helper as well.
## Typed Theme Config
By default, `defineConfig` helper leverages the theme config type from default theme:
```ts
import { defineConfig } from 'vitepress'
export default defineConfig({
themeConfig: {
// Type is `DefaultTheme.Config`
}
})
```
If you use a custom theme and want type checks for the theme config, you'll need to use `defineConfigWithTheme` instead, and pass the config type for your custom theme via a generic argument:
```ts
import { defineConfigWithTheme } from 'vitepress'
import { ThemeConfig } from 'your-theme'
export default defineConfigWithTheme<ThemeConfig>({
themeConfig: {
// Type is `ThemeConfig`
}
})
```

@ -1,16 +1,16 @@
# Asset Handling
All Markdown files are compiled into Vue components and processed by [Vite](https://github.com/vitejs/vite). You can, **and should**, reference any assets using relative URLs:
All Markdown files are compiled into Vue components and processed by [Vite](https://vitejs.dev/guide/assets.html). You can, **and should**, reference any assets using relative URLs:
```md
![An image](./image.png)
```
You can reference static assets in your markdown files, your `*.vue` components in the theme, styles and plain `.css` files either using absolute public paths (based on project root) or relative paths (based on your file system). The latter is similar to the behavior you are used to if you have used `vue-cli` or webpack's `file-loader`.
You can reference static assets in your markdown files, your `*.vue` components in the theme, styles and plain `.css` files either using absolute public paths (based on project root) or relative paths (based on your file system). The latter is similar to the behavior you are used to if you have used Vite, Vue CLI, or webpack's `file-loader`.
Common image, media, and font filetypes are detected and included as assets automatically.
All referenced assets, including those using absolute paths, will be copied to the dist folder with a hashed file name in the production build. Never-referenced assets will not be copied. Similar to `vue-cli`, image assets smaller than 4kb will be base64 inlined.
All referenced assets, including those using absolute paths, will be copied to the dist folder with a hashed file name in the production build. Never-referenced assets will not be copied. Image assets smaller than 4kb will be base64 inlined - this can be configured via the [`vite`](/reference/site-config#vite) config option.
All **static** path references, including absolute paths, should be based on your working directory structure.
@ -45,7 +45,7 @@ However, if you are authoring a theme component that links to assets dynamically
<img :src="theme.logoPath" />
```
In this case it is recommended to wrap the path with the [`withBase` helper](/api/#withbase) provided by VitePress:
In this case it is recommended to wrap the path with the [`withBase` helper](/reference/runtime-api#withbase) provided by VitePress:
```vue
<script setup>

@ -0,0 +1,56 @@
---
outline: deep
---
# Connecting to a CMS
## General Workflow
Connecting VitePress to a CMS will largely revolve around [Dynamic Routes](/guide/routing#dynamic-routes). Make sure to understand how it works before proceeding.
Since each CMS will work differently, here we can only provide a generic workflow that you will need to adapt to your specific scenario.
1. If your CMS requires authentication, create an `.env` file to store your API tokens and load it so:
```js
// posts/[id].paths.js
import { loadEnv } from 'vitepress'
const env = loadEnv('', process.cwd())
```
2. Fetch the necessary data from the CMS and format it into proper paths data:
```js
export default {
async paths() {
// use respective CMS client library if needed
const data = await (await fetch('https://my-cms-api', {
headers: {
// token if necessary
}
})).json()
return data.map(entry => {
return {
params: { id: entry.id, /* title, authors, date etc. */ },
content: entry.content
}
})
}
}
```
3. Render the content in the page:
```md
# {{ $params.title }}
- by {{ $params.author }} on {{ $params.date }}
<!-- @content -->
```
## Integration Guides
If you have written a guide on integrating VitePress with a specific CMS, please use the "Edit this page" link below to submit it here!

@ -1,27 +0,0 @@
# Configuration
Without any configuration, the page is pretty minimal, and the user has no way to navigate around the site. To customize your site, let's first create a `.vitepress` directory inside your docs directory. This is where all VitePress-specific files will be placed. Your project structure is probably like this:
```
.
├─ docs
│ ├─ .vitepress
│ │ └─ config.js
│ └─ index.md
└─ package.json
```
The essential file for configuring a VitePress site is `.vitepress/config.js`, which should export a JavaScript object:
```js
export default {
title: 'VitePress',
description: 'Just playing around.'
}
```
In the above example, the site will have the title of `VitePress`, and `Just playing around.` as the description meta tag.
Learn everything about VitePress features at [Theme: Introduction](./customization-intro) to find how to configure specific features within this config file.
You may also find all configuration references at [Config Reference](../config/introduction).

@ -0,0 +1,222 @@
# 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,228 +0,0 @@
# Theme Introduction
VitePress comes with its default theme providing many features out of the box. Learn more about each feature on its dedicated page listed below.
- [Nav](./theme-nav)
- [Sidebar](./theme-sidebar)
- [Prev Next Link](./theme-prev-next-link)
- [Edit Link](./theme-edit-link)
- [Last Updated](./theme-last-updated)
- [Layout](./theme-layout)
- [Home Page](./theme-home-page)
- [Team Page](./theme-team-page)
- [Badge](./theme-badge)
- [Footer](./theme-footer)
- [Search](./theme-search)
- [Carbon Ads](./theme-carbon-ads)
If you don't find the features you're looking for, or you would rather create your own theme, you may customize VitePress to fit your requirements. In the following sections, we'll go through each way of customizing the VitePress theme.
## Using a Custom Theme
You can enable a custom theme by adding the `.vitepress/theme/index.js` or `.vitepress/theme/index.ts` file (the "theme entry file").
```
.
├─ 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>
}
```
The theme entry file should export the theme as its default export:
```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>
<!-- this is where markdown content will be rendered -->
<Content />
</template>
```
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
```
## 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:
### Registering Global Components
```js
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
export default {
...DefaultTheme,
enhanceApp(ctx) {
// extend default theme custom behaviour.
DefaultTheme.enhanceApp(ctx)
// register your custom global components
ctx.app.component('MyGlobalComponent' /* ... */)
}
}
```
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/client/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:
```js
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
import MyLayout from './MyLayout.vue'
export default {
...DefaultTheme,
// override the Layout with a wrapper component that
// injects the slots
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>
```
Or you could use render function as well.
```js
// .vitepress/theme/index.js
import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import MyComponent from './MyComponent.vue'
export default {
...DefaultTheme,
Layout() {
return h(DefaultTheme.Layout, null, {
'aside-outline-before': () => h(MyComponent)
})
}
}
```
Full list of slots available in the default theme layout:
- When `layout: 'doc'` (default) is enabled via frontmatter:
- `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`
- When `layout: 'home'` is enabled via frontmatter:
- `home-hero-before`
- `home-hero-info`
- `home-hero-image`
- `home-hero-after`
- `home-features-before`
- `home-features-after`
- Always:
- `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`

@ -1,6 +1,15 @@
# Build-Time Data Loading
VitePress provides a feature called **data loaders** that allows you to load arbitrary data and import it from pages or components. The data loading is executed **only at build time**: the resulting data will be serialized as JSON in the final JavaScript bundle.
Data loaders can be used to fetch remote data, or generate metadata based on local files. For example, you can use data loaders to parse all your local API pages and automatically generate an index of all API entries.
## Basic Usage
A data loader file must end with either `.data.js` or `.data.ts`. The file should provide a default export of an object with the `load()` method:
```js
// example.data.js
export default {
load() {
return {
@ -10,30 +19,76 @@ export default {
}
```
The loader module is evaluated only in Node.js, so you can import Node APIs and npm dependencies as needed.
You can then import data from this file in `.md` pages and `.vue` components using the `data` named export:
```html
<script setup>
import { data } from './example.data.js'
</script>
<pre>{{ data }}</pre>
```
Output:
```json
{
"data": "hello"
}
```
You'll notice the data loader itself does not export the `data`. It is VitePress calling the `load()` method behind the scenes and implicitly exposing the result via the `data` named export.
This works even if the loader is async:
```js
export default {
async load() {
// fetch remote data
return (await fetch('...')).json()
}
}
```
## Generating Data Based On Local Files
## Data from Local Files
When you need to generate data based on local files, you should use the `watch` option in the data loader so that changes made to these files can trigger hot updates.
The `watch` option is also convenient in that you can use [glob patterns](https://github.com/mrmlnc/fast-glob#pattern-syntax) to match multiple files. The patterns can be relative to the loader file itself, and the `load()` function will receive the matched files as absolute paths:
```js
import { readDirSync } from 'node:fs'
import fs from 'node:fs'
import parseFrontmatter from 'gray-matter'
export default {
watch: ['*.md'],
async load() {
//
// watch all blog posts
watch: ['./posts/*.md'],
load(watchedFiles) {
// watchedFiles will be an array of absolute paths of the matched files.
// generate an array of blog post metadata that can be used to render
// a list in the theme layout
return watchedFiles.map(file => {
const content = fs.readFileSync(file, 'utf-8')
const { data, excerpt } = parseFrontmatter(content)
return {
file,
data,
excerpt
}
})
}
}
```
## Typed Data
## Typed Data Loaders
When using TypeScript, you can type your loader and `data` export like so:
```ts
import { defineLoader } from 'vitepress'
export interface Data {
// data type
}
@ -41,9 +96,11 @@ export interface Data {
declare const data: Data
export { data }
export default {
export default defineLoader({
// type checked loader options
glob: ['...'],
async load(): Promise<Data> {
// ...
}
}
})
```

@ -1,4 +1,4 @@
# Deploying
# Deploy Your VitePress Site
The following guides are based on some shared assumptions:
@ -17,7 +17,7 @@ The following guides are based on some shared assumptions:
::: tip
If your site is to be served at a subdirectory (`https://example.com/subdir/`), then you have to set `'/subdir/'` as the [`base`](../config/app-config#base) in your `docs/.vitepress/config.js`.
If your site is to be served at a subdirectory (`https://example.com/subdir/`), then you have to set `'/subdir/'` as the [`base`](/reference/site-config#base) in your `docs/.vitepress/config.js`.
**Example:** If you're using Github (or GitLab) Pages and deploying to `user.github.io/repo/`, then set your `base` to `/repo/`.

@ -0,0 +1,138 @@
# Extending the Default Theme
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.
However, there are a number of cases where configuration alone won't be enough. For example:
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.
These advanced customizations will require using a custom theme that "extends" the default theme.
:::tip
Before proceeding, make sure to first read [Using a Custom Theme](./custom-theme) to understand how custom themes work.
:::
## 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/client/theme-default/styles/vars.css) that can be overridden.
## Registering Global Components
```js
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
export default {
extends: DefaultTheme,
enhanceApp(ctx) {
// register your custom global components
ctx.app.component('MyGlobalComponent' /* ... */)
}
}
```
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.
## 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:
```js
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
import MyLayout from './MyLayout.vue'
export default {
...DefaultTheme,
// override the Layout with a wrapper component that
// injects the slots
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>
```
Or you could use render function as well.
```js
// .vitepress/theme/index.js
import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import MyComponent from './MyComponent.vue'
export default {
...DefaultTheme,
Layout() {
return h(DefaultTheme.Layout, null, {
'aside-outline-before': () => h(MyComponent)
})
}
}
```
Full list of slots available in the default theme layout:
- When `layout: 'doc'` (default) is enabled via frontmatter:
- `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`
- When `layout: 'home'` is enabled via frontmatter:
- `home-hero-before`
- `home-hero-info`
- `home-hero-image`
- `home-hero-after`
- `home-features-before`
- `home-features-after`
- Always:
- `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`

@ -1,6 +1,8 @@
# Frontmatter
Any Markdown file that contains a YAML frontmatter block will be processed by [gray-matter](https://github.com/jonschlinkert/gray-matter). The frontmatter must be at the top of the Markdown file, and must take the form of valid YAML set between triple-dashed lines. Example:
## Usage
VitePress supports YAML frontmatter in all Markdown files, parsing them with [gray-matter](https://github.com/jonschlinkert/gray-matter). The frontmatter must be at the top of the Markdown file (before any elements including `<script>` tags), and must take the form of valid YAML set between triple-dashed lines. Example:
```md
---
@ -9,7 +11,13 @@ editLink: true
---
```
Between the triple-dashed lines, you can set [predefined variables](../config/frontmatter-config), or even create custom ones of your own. These variables can be used via the special <code>$frontmatter</code> variable.
Many site or default theme config options have corresponding options in frontmatter. You can use frontmatter to override specific behavior for the current page only. For details, see [Frontmatter Config Reference](/reference/frontmatter-config).
You can also define custom frontmatter data of your own, to be used in dynamic Vue expressions on the page.
## Accessing Frontmatter Data
Frontmatter data can be accessed via the special `$frontmatter` global variable:
Here's an example of how you could use it in your Markdown file:
@ -24,6 +32,8 @@ editLink: true
Guide content
```
You can also access current page's frontmatter data in `<script setup>` with the [`useData()`](/reference/runtime-api#usedata) helper.
## Alternative Frontmatter Formats
VitePress also supports JSON frontmatter syntax, starting and ending in curly braces:

@ -1,85 +1,130 @@
# Getting Started
This section will help you build a basic VitePress documentation site from ground up. If you already have an existing project and would like to keep documentation inside the project, start from Step 2.
## Try It Online
You can also try VitePress online on [StackBlitz](https://vitepress.new/). It runs the VitePress-based site directly in the browser, so it is almost identical to the local setup but doesn't require installing anything on your machine.
You can try VitePress directly in your browser on [StackBlitz](https://vitepress.new).
::: warning
VitePress is currently in `alpha` status. It is already suitable for out-of-the-box documentation use, but the config and theming API may still change between minor releases.
:::
## Step 1: Create a new project
## Installation
Create and change into a new directory.
### Prerequisites
```sh
$ mkdir vitepress-starter && cd vitepress-starter
```
- [Node.js](https://nodejs.org/) version 16 or higher.
- Terminal for accessing VitePress via its command line interface (CLI).
- Text Editor with [Markdown](https://en.wikipedia.org/wiki/Markdown) syntax support.
- [VSCode](https://code.visualstudio.com/) is recommended, along with the [official Vue extension](https://marketplace.visualstudio.com/items?itemName=Vue.volar).
Then, initialize with your preferred package manager.
VitePress can be used on its own, or be installed into an existing project. In both cases, you can install it with:
::: code-group
```sh [npm]
$ npm init
$ npm install -D vitepress
```
```sh [pnpm]
$ pnpm add -D vitepress
```
```sh [yarn]
$ yarn init
$ yarn add -D vitepress
```
```sh [pnpm]
$ pnpm init
:::
::: details Getting missing peer deps warnings?
If using PNPM, you will notice a missing peer warning for `@docsearch/js`. This does not prevent VitePress from working. If you wish to suppress this warning, add the following to your `package.json`:
```json
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"@algolia/client-search"
]
}
}
```
:::
## Step 2: Install VitePress
### Setup Wizard
Add VitePress and Vue as dev dependencies for the project.
VitePress ships with a command line setup wizard that will help you scaffold a basic project. After installation, start the wizard by running:
::: code-group
```sh [npm]
$ npm install -D vitepress vue
$ npx vitepress init
```
```sh [yarn]
$ yarn add -D vitepress vue
```sh [pnpm]
$ pnpm exec vitepress init
```
```sh [pnpm]
$ pnpm add -D vitepress vue
:::
You will be greeted with a few simple questions:
<p>
<img src="./vitepress-init.png" alt="vitepress init screenshot" style="border-radius:8px">
</p>
:::tip Vue as Peer Dependency
If you intend to perform customization that uses Vue components or APIs, you should also explicitly install `vue` as a peer dependency.
:::
## File Structure
If you are building a standalone VitePress site, you can scaffold the site in your current directory (`./`). However, if you are installing VitePress in an existing project alongside other source code, it is recommended to scaffold the site in a nested directory (e.g. `./docs`) so that it is separate from the rest of the project.
Assuming you chose to scaffold the VitePress project in `./docs`, the generated file structure should look like this:
```
.
├─ docs
│ ├─ .vitepress
│ │ └─ config.js
│ ├─ api-examples.md
│ ├─ markdown-examples.md
│ └─ index.md
└─ package.json
```
The `docs` directory is considered the **project root** of the VitePress site. The `.vitepress` directory is a reserved location for VitePress' config file, dev server cache, build output, and optional theme customization code.
:::tip
By default, VitePress stores its dev server cache in `.vitepress/cache`, and the production build output in `.vitepress/dist`. If using Git, you should add them to your `.gitignore` file. These locations can also be [configured](/reference/site-config#outdir).
:::
::: details Getting missing peer deps warnings?
`@docsearch/js` has certain issues with its peer dependencies. If you see some commands failing due to them, you can try this workaround for now:
### The Config File
If using PNPM, add this in your `package.json`:
The config file (`.vitepress/config.js`) allows you to customize various aspects of your VitePress site, with the most basic options being the title and description of the site:
```json
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"@algolia/client-search"
]
```js
// .vitepress/config.js
export default {
// site-level options
title: 'VitePress',
description: 'Just playing around.',
themeConfig: {
// theme-level options
}
}
```
:::
You can also configure the behavior of the theme via the `themeConfig` option. Consult the [Config Reference](/reference/site-config) for full details on all config options.
Create your first document.
### Source Files
```sh
$ mkdir docs && echo '# Hello VitePress' > docs/index.md
```
Markdown files outside the `.vitepress` directory are considered **source files**.
## Step 3: Boot up dev environment
VitePress uses **file-based routing**: each `.md` file is compiled into a corresponding `.html` file with the same path. For example, `index.md` will be compiled into `index.html`, and can be visited at the root path `/` of the resulting VitePress site.
Add some scripts to `package.json`.
VitePress also provides the ability to generate clean URLs, rewrite paths, and dynamically generate pages. These will be covered in the [Routing Guide](./routing).
## Up and Running
The tool should have also injected the following npm scripts to your `package.json` if you allowed it to do so during the setup process:
```json
{
@ -93,7 +138,7 @@ Add some scripts to `package.json`.
}
```
Serve the documentation site in the local server.
The `docs:dev` script will start a local dev server with instant hot updates. Run it with the following command:
::: code-group
@ -101,41 +146,42 @@ Serve the documentation site in the local server.
$ npm run docs:dev
```
```sh [yarn]
$ yarn docs:dev
```
```sh [pnpm]
$ pnpm run docs:dev
```
```sh [yarn]
$ yarn docs:dev
```
:::
VitePress will start a hot-reloading development server at `http://localhost:5173`.
## Step 4: Add more pages
Instead of npm scripts, you can also invoke VitePress directly with:
Let's add another page to the site. Create a file name `getting-started.md` along with `index.md` you've created in Step 2. Now your directory structure should look like this.
::: code-group
```sh [npm]
$ npx vitepress dev docs
```
.
├─ docs
│ ├─ getting-started.md
│ └─ index.md
└─ package.json
```sh [pnpm]
$ pnpm exec vitepress dev docs
```
Then, try to access `http://localhost:5173/getting-started.html` and you should see the content of `getting-started.md` is shown.
:::
More command line usage is documented in the [CLI Reference](/reference/cli).
This is how VitePress works basically. The directory structure corresponds with the URL path. You add files, and just try to access it.
The dev server should be running at `http://localhost:5173`. Visit the URL in your browser to see your new site in action!
## What's next?
## What's Next?
By now, you should have a basic but functional VitePress documentation site. But currently, the user has no way to navigate around the site because it's missing for example sidebar menu we have on this site.
- To better understand how markdown files are mapped to generated HTML, proceed to the [Routing Guide](./routing.md).
To enable those navigations, we must add some configurations to the site. Head to [configuration guide](./configuration) to learn how to configure VitePress.
- To discover more about what you can do on the page, such as writing markdown content or using Vue Component, refer to the "Writing" section of the guide. A great place to start would be to learn about [Markdown Extensions](/guide/markdown).
If you would like to know more about what you can do within the page, for example, writing markdown contents, or using Vue Component, check out the "Writing" section of the docs. [Markdown guide](./markdown) would be a great starting point.
- To explore the features provided by the default documentation theme, check out the [Default Theme Config Reference](/reference/default-theme-config).
If you want to know how to customize how the site looks (Theme), and find out the features VitePress's default theme provides, visit [Theme: Introduction](./customization-intro).
- If you want to further customize the appearance of your site, explore how to either [Extend the Default Theme](./extending-default-theme) or [Build a Custom Theme](./custom-theme).
When your documentation site starts to take shape, be sure to read the [deployment guide](./deploying).
- Once your documentation site takes shape, make sure to read the [Deployment Guide](./deploy).

@ -49,7 +49,7 @@ interface LocaleSpecificConfig<ThemeConfig = any> {
}
```
Refer [`DefaultTheme.Config`](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) interface for details on customizing the placeholder texts of the default theme. Don't override `themeConfig.algolia` or `themeConfig.carbonAds` at locale-level. Refer [Algolia docs](./theme-search#i18n) for using multilingual search.
Refer [`DefaultTheme.Config`](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) interface for details on customizing the placeholder texts of the default theme. Don't override `themeConfig.algolia` or `themeConfig.carbonAds` at locale-level. Refer [Algolia docs](/reference/default-theme-search#i18n) for using multilingual search.
**Pro tip:** Config file can be stored at `docs/.vitepress/config/index.ts` too. It might help you organize stuff by creating a configuration file per locale and then merge and export them from `index.ts`.
@ -75,7 +75,7 @@ However, VitePress won't redirect `/` to `/en/` by default. You'll need to confi
/* /en/:splat 302
```
**Pro tip:** If using the above approach, you can use `nf_lang` cookie to persist user's language choice. A very basic way to do this is register a watcher inside the [setup](./customization-intro#using-a-custom-theme) function of custom theme:
**Pro tip:** If using the above approach, you can use `nf_lang` cookie to persist user's language choice. A very basic way to do this is register a watcher inside the [setup](./custom-theme#using-a-custom-theme) function of custom theme:
```ts
// docs/.vitepress/theme/index.ts

@ -64,7 +64,7 @@ lang: en-US
This data will be available to the rest of the page, along with all custom and theming components.
For more details, see [Frontmatter](./frontmatter).
For more details, see [Frontmatter](/reference/frontmatter-config).
## GitHub-Style Tables
@ -281,7 +281,7 @@ export default {
A [list of valid languages](https://github.com/shikijs/shiki/blob/main/docs/languages.md) is available on Shiki's repository.
You may also customize syntax highlight theme in app config. Please see [`markdown` options](../config/app-config#markdown) for more details.
You may also customize syntax highlight theme in app config. Please see [`markdown` options](/reference/site-config#markdown) for more details.
## Line Highlighting in Code Blocks
@ -493,7 +493,7 @@ export default {
}
```
Please see [`markdown` options](../config/app-config#markdown) for more details.
Please see [`markdown` options](/reference/site-config#markdown) for more details.
You can add `:line-numbers` / `:no-line-numbers` mark in your fenced code blocks to override the value set in config.
@ -748,4 +748,4 @@ module.exports = {
}
```
See full list of configurable properties in [Config Reference: App Config](../config/app-config#markdown).
See full list of configurable properties in [Config Reference: App Config](/reference/site-config#markdown).

@ -12,12 +12,12 @@ If you're coming from VitePress 0.x version, there're several breaking changes d
- `children` key is now named `items`.
- Top level item may not contain `link` at the moment. We're planning to bring it back.
- `repo`, `repoLabel`, `docsDir`, `docsBranch`, `editLinks`, `editLinkText` are removed in favor of more flexible api.
- For adding GitHub link with icon to the nav, use [Social Links](./theme-nav#navigation-links) feature.
- For adding "Edit this page" feature, use [Edit Link](./theme-edit-link) feature.
- For adding GitHub link with icon to the nav, use [Social Links](/reference/default-theme-nav#navigation-links) feature.
- For adding "Edit this page" feature, use [Edit Link](/reference/default-theme-edit-link) feature.
- `lastUpdated` option is now split into `config.lastUpdated` and `themeConfig.lastUpdatedText`.
- `carbonAds.carbon` is changed to `carbonAds.code`.
## Frontmatter Config
- `home: true` option has changed to `layout: home`. Also, many Homepage related settings have been modified to provide additional features. See [Home Page guide](./theme-home-page) for details.
- `footer` option is moved to [`themeConfig.footer`](../config/theme-config#footer).
- `home: true` option has changed to `layout: home`. Also, many Homepage related settings have been modified to provide additional features. See [Home Page guide](/reference/default-theme-home-page) for details.
- `footer` option is moved to [`themeConfig.footer`](/reference/default-theme-config#footer).

@ -0,0 +1,23 @@
# MPA Mode <Badge type="warning" text="experimental" />
MPA (Multi-Page Application) mode can be enabled via the command line via `vitepress build --mpa`, or via config through the `mpa: true` option.
In MPA mode, all pages are rendered without any JavaScript included by default. As a result, the production site will likely have a better initial visit performance score from audit tools.
However, due to the absence of SPA navigation, cross-page links will lead to full page reloads. Post-load navigations in MPA mode will not feel as instant as in SPA mode.
Also note that no-JS-by-default also means you are essentially using Vue purely as a server-side templating language - no event handlers will be attached in the browser, so there will be no interactivity. To load client-side JavaScript, you can do so by using the special `<script client>` tag (works in both `.md` and `.vue` files, but only in MPA mode):
```html
<script client>
document.querySelector('h1').addEventListener('click', () => {
console.log('client side JavaScript!')
})
</script>
# Hello
```
Client scripts in all theme components will be bundled together, while client script for a specific page will be split for that page only.
Notice that `<script client>` is **not evaluated as Vue component code**: it's processed as a plain JavaScript module. For this reason, MPA mode should only be used if your site requires absolutely minimal client-side interactivity.

@ -1,10 +1,12 @@
# Routing
---
outline: deep
---
VitePress is built with file system based routing, which means the directory structure of the source file corresponds to the final URL. You may customize the mapping of the directory structure and URL too. Read through this page to learn everything about the VitePress routing system.
# Routing
## Basic Routing
## File-Based Routing
By default, VitePress assumes your page files are stored in project root. Here you may add markdown files with the name being the URL path. For example, when you have following directory structure:
VitePress uses file-based routing, which means the generated HTML pages are mapped from the directory structure of the source Markdown files. For example, given the following directory structure:
```
.
@ -15,51 +17,71 @@ By default, VitePress assumes your page files are stored in project root. Here y
└─ prologue.md
```
Then you can access the pages by the below URL.
The generated HTML pages will be:
```
index.md -> /
prologue.md -> /prologue.html
guide/index.md -> /guide/
guide/getting-started.md -> /guide/getting-started.html
index.md --> /index.html (accessible as /)
prologue.md --> /prologue.html
guide/index.md --> /guide/index.html (accessible as /guide/)
guide/getting-started.md --> /guide/getting-started.html
```
As you can see, the directory structure corresponds to the final URL, as same as hosting plain HTML from a typical web server.
The resulting HTML can be hosted on any web server that can serve static files.
## Root and Source Directory
## Changing the Root Directory
There are two important concepts in the file structure of a VitePress project: the **project root** and the **source directory**.
To change the root directory for your page files, you may pass the directory name to the `vitepress` command. For example, if you want to store your page files under `docs` directory, then you should run `vitepress dev docs` command.
### Project Root
Project root is where VitePress will try to look for the `.vitepress` special directory. The `.vitepress` directory is a reserved location for VitePress' config file, dev server cache, build output, and optional theme customization code.
When you run `vitepress dev` or `vitepress build` from the command line, VitePress will use the current working directory as project root. To specify a sub-directory as root, you will need to pass the relative path to the command. For example, if your VitePress project is located in `./docs`, you should run `vitepress dev docs`:
```
.
├─ docs
├─ docs # project root
│ ├─ .vitepress # config dir
│ ├─ getting-started.md
│ └─ index.md
└─ ...
```
```
```sh
vitepress dev docs
```
This is going to map the URL as follows.
This is going to result in the following source-to-HTML mapping:
```
docs/index.md -> /
docs/getting-started.md -> /getting-started.html
docs/index.md --> /index.html (accessible as /)
docs/getting-started.md --> /getting-started.html
```
You may also customize the root directory in config file via [`srcDir`](/config/app-config#srcdir) option too. Running `vitepress dev` with the following setting acts same as running `vitepress dev docs` command.
### Source Directory
```ts
export default {
srcDir: './docs'
}
Source directory is where your Markdown source files live. By default, it is the same as the project root. However, you can configure it via the [`srcDir`](/reference/site-config#srcdir) config option.
The `srcDir` option is resolved relative to project root. For example, with `srcDir: 'src'`, your file structure will look like this:
```
. # project root
├─ .vitepress # config dir
└─ src # source dir
├─ getting-started.md
└─ index.md
```
The resulting source-to-HTML mapping:
```
src/index.md --> /index.html (accessible as /)
src/getting-started.md --> /getting-started.html
```
## Linking Between Pages
When adding links in pages, omit extension from the path and use either absolute path from the root, or relative path from the page. VitePress will handle the extension according to your configuration setup.
You can use both absolute and relative paths when linking between pages. Note that although both `.md` and `.html` extensions will work, the best practice is to omit file extensions so that VitePress can generate the final URLs based on your config.
```md
<!-- Do -->
@ -71,13 +93,13 @@ When adding links in pages, omit extension from the path and use either absolute
[Getting Started](/guide/getting-started.html)
```
Learn more about page links and links to assets, such as link to images, at [Asset Handling](asset-handling).
Learn more about linking to assets such images in [Asset Handling](asset-handling).
## Generate Clean URL
## Generating Clean URL
A "Clean URL" is commonly known as URL without `.html` extension, for example, `example.com/path` instead of `example.com/path.html`.
By default, VitePress resolves inbound links to URLs ending with `.html`. However, some users may prefer "Clean URLs" without the `.html` extension - for example, `example.com/path` instead of `example.com/path.html`.
By default, VitePress generates the final static page files by adding `.html` extension to each file. If you would like to have clean URL, you may structure your directory by only using `index.html` file.
One way to achieve clean URLs is to structure your files using only `index.md` inside directories:
```
.
@ -88,17 +110,11 @@ By default, VitePress generates the final static page files by adding `.html` ex
└─ index.md
```
However, you may also generate a clean URL by setting up [`cleanUrls`](/config/app-config#cleanurls) option.
Some servers or hosting platforms (for example Netlify or Vercel) provide the ability to map a URL like `/foo` to `/foo.html` if it exists. If this feature is available to you, you can use the [`cleanUrls`](/reference/site-config#cleanurls) config option so that inbound links are always generated without the `.html` extension. When this option is enabled, VitePress' client-side router will also redirect to the clean URL when a visited URL ends with `.html`.
```ts
export default {
cleanUrls: true
}
```
## Customize the Mappings
## Route Rewrites
You may customize the mapping between directory structure and URL. It's useful when you have complex document structure. For example, let's say you have several packages and would like to place documentations along with the source files like this.
You can customize the mapping between the source directory structure and the generated pages. It's useful when you have a complex project structure. For example, let's say you have a monorepo with multiple packages, and would like to place documentations along with the source files like this:
```
.
@ -106,68 +122,216 @@ You may customize the mapping between directory structure and URL. It's useful w
│ ├─ pkg-a
│ │ └─ src
│ │ ├─ pkg-a-code.ts
│ │ └─ pkg-a-code.md
│ │ └─ pkg-a-docs.md
│ └─ pkg-b
│ └─ src
│ ├─ pkg-b-code.ts
│ └─ pkg-b-code.md
│ └─ pkg-b-docs.md
```
And you want the VitePress pages to be generated as follows.
And you want the VitePress pages to be generated like this:
```
packages/pkg-a/src/pkg-a-code.md -> /pkg-a/pkg-a-code.md
packages/pkg-b/src/pkg-b-code.md -> /pkg-b/pkg-b-code.md
packages/pkg-a/src/pkg-a-docs.md --> /pkg-a/index.html
packages/pkg-b/src/pkg-b-docs.md --> /pkg-b/index.html
```
You may configure the mapping via [`rewrites`](/config/app-config#rewrites) option like this.
You can achieve this by configuring the [`rewrites`](/reference/site-config#rewrites) option like this:
```ts
// .vitepress/config.js
export default {
rewrites: {
'packages/pkg-a/src/pkg-a-code.md': 'pkg-a/pkg-a-code.md',
'packages/pkg-b/src/pkg-b-code.md': 'pkg-b/pkg-b-code.md'
'packages/pkg-a/src/pkg-a-docs.md': 'pkg-a/index.md',
'packages/pkg-b/src/pkg-b-docs.md': 'pkg-b/index.md'
}
}
```
The `rewrites` option can also have dynamic route parameters. In this example, we have fixed path `packages` and `src` which stays the same on all pages, and it might be verbose to have to list all pages in your config as you add pages. You may configure the above mapping as below and get the same result.
The `rewrites` option also supports dynamic route parameters. In the above example, it would be verbose to list all the paths if you have many packages. Given that they all have the same file structure, you can simplify the config like this:
```ts
export default {
rewrites: {
'packages/:pkg/src/:page': ':pkg/:page'
'packages/:pkg/src/(.*)': ':pkg/index.md'
}
}
```
Route parameters are prefixed by `:` (e.g. `:pkg`). The name of the parameter is just a placeholder and can be anything.
The rewrite paths are compiled using the `path-to-regexp` package - consult [its documentation](https://github.com/pillarjs/path-to-regexp#parameters) for more advanced syntax.
In addition, you may add `*` at the end of the parameter to map all sub directories from there on.
:::warning Relative Links with Rewrites
```ts
When rewrites are enabled, **relative links should be based on the rewritten paths**. For example, in order to create a relative link from `packages/pkg-a/src/pkg-a-code.md` to `packages/pkg-b/src/pkg-b-code.md`, you should use:
```md
[Link to PKG B](../pkg-b/pkg-b-code)
```
:::
## Dynamic Routes
You can generate many pages using a single Markdown file and dynamic data. For example, you can create a `packages/[pkg].md` file that generates a corresponding page for every package in a project. Here, the `[pkg]` segment is a route **parameter** that differentiates each page from the others.
### Paths Loader File
Since VitePress is a static site generator, the possible page paths must be determined at build time. Therefore, a dynamic route page **must** be accompanied by a **paths loader file**. For `packages/[pkg].md`, we will need `packages/[pkg].paths.js` (`.ts` is also supported):
```
.
└─ packages
├─ [pkg].md # route template
└─ [pkg].paths.js # route paths loader
```
The paths loader should provide an object with a `paths` method as its default export. The `paths` method should return an array of objects with a `params` property. Each of these objects will generate a corresponding page.
Given the following `paths` array:
```js
// packages/[pkg].paths.js
export default {
rewrites: {
'packages/:pkg/src/:page*': ':pkg/:page*'
paths() {
return [
{ params: { pkg: 'foo' }},
{ params: { pkg: 'bar' }}
]
}
}
```
The above will create mapping as below.
The generated HTML pages will be:
```
packages/pkg-a/src/pkg-a-code.md -> /pkg-a/pkg-a-code.md
packages/pkg-b/src/folder/file.md -> /pkg-b/folder/file.md
.
└─ packages
├─ foo.html
└─ bar.html
```
::: warning You need server restart on page addition
At the moment, VitePress doesn't detect page additions to the mapped directory. You need to restart your server when adding or removing files from the directory during the dev mode. Updating the already existing files gets updated as usual.
:::
### Multiple Params
A dynamic route can contain multiple params:
**File Structure**
```
.
└─ packages
├─ [pkg]-[version].md
└─ [pkg]-[version].paths.js
```
**Paths Loader**
```js
export default {
paths: () => [
{ params: { pkg: 'foo', version: '1.0.0' }},
{ params: { pkg: 'foo', version: '2.0.0' }},
{ params: { pkg: 'bar', version: '1.0.0' }},
{ params: { pkg: 'bar', version: '2.0.0' }}
]
}
```
**Output**
```
.
└─ packages
├─ foo-1.0.0.html
├─ foo-2.0.0.html
├─ bar-1.0.0.html
└─ bar-2.0.0.html
```
### Relative Link Handling in Page
### Dynamically Generating Paths
Note that when enabling rewrites, **relative links in the markdown are resolved relative to the final path**. For example, in order to create relative link from `packages/pkg-a/src/pkg-a-code.md` to `packages/pkg-b/src/pkg-b-code.md`, you should define link as below.
The paths loader module is run in Node.js and only executed during build time. You can dynamically generate the paths array using any data, either local or remote.
Generating paths from local files:
```js
import fs from 'fs'
export default {
paths() {
return fs
.readdirSync('packages')
.map((pkg) => {
return { params: { pkg }}
})
}
}
```
Generating paths from remote data:
```js
export default {
async paths() {
const pkgs = await (await fetch('https://my-api.com/packages')).json()
return pkgs.map((pkg) => {
return {
params: {
pkg: pkg.name,
version: pkg.version
}
}
})
}
}
```
### Accessing Params in Page
You can use the params to pass additional data to each page. The Markdown route file can access the current page params in Vue expressions via the `$params` global property:
```md
[Link to PKG B](../pkg-b/pkg-b-code)
- package name: {{ $params.pkg }}
- version: {{ $params.version }}
```
You can also access the current page's params via the `[useData](/reference/runtime-api#usedata)` runtime API. This is available in both Markdown files and Vue components:
```vue
<script setup>
import { useData } from 'vitepress'
// params is a Vue ref
const { params } = useData()
console.log(params.value)
</script>
```
### Rendering Raw Content
Params passed to the page will be serialized in the client JavaScript payload, so you should avoid passing heavy data in params, for example raw Markdown or HTML content fetched from a remote CMS.
Instead, you can pass such content to each page using the `content` property on each path object:
```js
export default {
paths() {
async paths() {
const posts = await (await fetch('https://my-cms.com/blog-posts')).json()
return posts.map((post) => {
return {
params: { id: post.id },
content: post.content // raw Markdown or HTML
}
})
}
}
}
```
Then, use the following special syntax to render the content as part of the Markdown file itself:
```md
<!-- @content -->
```

@ -1,8 +1,8 @@
# Using Vue in Markdown
In VitePress, each markdown file is compiled into HTML and then processed as a Vue Single-File Component. This means you can use any Vue features inside the markdown, including dynamic templating, using Vue components, or arbitrary in-page Vue component logic by adding a `<script>` tag.
In VitePress, each Markdown file is compiled into HTML and then processed as a [Vue Single-File Component](https://vuejs.org/guide/scaling-up/sfc.html). This means you can use any Vue features inside the Markdown, including dynamic templating, using Vue components, or arbitrary in-page Vue component logic by adding a `<script>` tag.
It is also important to know that VitePress leverages Vue 3's compiler to automatically detect and optimize the purely static parts of the markdown. Static contents are optimized into single placeholder nodes and eliminated from the page's JavaScript payload. They are also skipped during client-side hydration. In short, you only pay for the dynamic parts on any given page.
It's worth noting that VitePress leverages Vue's compiler to automatically detect and optimize the purely static parts of the Markdown content. Static contents are optimized into single placeholder nodes and eliminated from the page's JavaScript payload for initial visits. They are also skipped during client-side hydration. In short, you only pay for the dynamic parts on any given page.
## Templating
@ -22,7 +22,7 @@ Each Markdown file is first compiled into HTML and then passed on as a Vue compo
### Directives
Directives also work:
Directives also work (note that by design, raw HTML is also valid in Markdown):
**Input**
@ -34,9 +34,40 @@ Directives also work:
<div class="language-text"><pre><code><span v-for="i in 3">{{ i }} </span></code></pre></div>
### Access to Site & Page Data
## `<script>` and `<style>`
You can use the [`useData` helper](/api/#usedata) in a `<script>` block and expose the data to the page.
Root-level `<script>` and `<style>` tags in Markdown files work just like they do in Vue SFCs, including `<script setup>`, `<style module>`, etc. The main difference here is that there is no `<template>` tag: all other root-level content is Markdown. Also note that all tags should be placed **after** the frontmatter:
```html
---
hello: world
---
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
## Markdown Content
The count is: {{ count }}
<button :class="$style.module" @click="count++">Increment</button>
<style module>
.button {
color: red;
font-weight: bold;
}
</style>
```
:::warning Avoid `<style scoped>` in Markdown
When used in Markdown, `<style scoped>` requires adding special attributes to every element on the current page, which will significantly bloat the page size. `<style module>` is preferred when locally-scoped styling is needed in a page.
:::
You also have access to VitePress' runtime APIs such as the [`useData` helper](/reference/runtime-api#usedata), which provides access to current page's metadata:
**Input**
@ -56,35 +87,18 @@ const { page } = useData()
{
"path": "/using-vue.html",
"title": "Using Vue in Markdown",
"frontmatter": {}
"frontmatter": {},
...
}
```
## Escaping
By default, fenced code blocks are automatically wrapped with `v-pre`, unless you have set some language with `-vue` suffix like `js-vue` (in that case you can use Vue-style interpolation inside fences). To display raw mustaches or Vue-specific syntax inside inline code snippets or plain text, you need to wrap a paragraph with the `v-pre` custom container:
**Input**
```md
::: v-pre
`{{ This will be displayed as-is }}`
:::
```
**Output**
::: v-pre
`{{ This will be displayed as-is }}`
:::
## Using Components
When you need to have more flexibility, VitePress allows you to extend your authoring toolbox with your own Vue Components.
You can import and use Vue components directly in Markdown files.
### Importing components in markdown
### Importing in Markdown
If your components are going to be used in only a few places, the recommended way to use them is to importing the components in the file where it is used.
If a component is only used by a few pages, it's recommended to explicitly import them where they are used. This allows them to be properly code-split and only loaded when the relevant pages are shown:
```md
<script setup>
@ -102,31 +116,9 @@ This is a .md using a custom component
...
```
### Registering global components in the theme
If the components are going to be used across several pages in the docs, they can be registered globally in the theme (or as part of extending the default VitePress theme). Check out the [Customization Guide](./customization-intro) for more information.
### Registering Components Globally
In `.vitepress/theme/index.js`, the `enhanceApp` function receives the Vue `app` instance so you can [register components](https://vuejs.org/guide/components/registration.html) as you would do in a regular Vue application.
```js
import DefaultTheme from 'vitepress/theme'
export default {
...DefaultTheme,
enhanceApp(ctx) {
DefaultTheme.enhanceApp(ctx)
ctx.app.component('VueClickAwayExample', VueClickAwayExample)
}
}
```
Later in your markdown files, the component can be interleaved between the content
```md
# Vue Click Away
<VueClickAwayExample />
```
If a component is going to be used on most of the pages, they can be registered globally by customizing the Vue app instance. See relevant section in [Extending Default Theme](/guide/extending-default-theme#registering-global-components) for an example.
::: warning IMPORTANT
Make sure a custom component's name either contains a hyphen or is in PascalCase. Otherwise, it will be treated as an inline element and wrapped inside a `<p>` tag, which will lead to hydration mismatch because `<p>` does not allow block elements to be placed inside it.
@ -144,9 +136,62 @@ You can use Vue components in the headers, but note the difference between the f
The HTML wrapped by `<code>` will be displayed as-is; only the HTML that is **not** wrapped will be parsed by Vue.
::: tip
The output HTML is accomplished by [markdown-it](https://github.com/markdown-it/markdown-it), while the parsed headers are handled by VitePress (and used for both the sidebar and document title).
The output HTML is accomplished by [Markdown-it](https://github.com/Markdown-it/Markdown-it), while the parsed headers are handled by VitePress (and used for both the sidebar and document title).
:::
## Escaping
You can escape Vue interpolations by wrapping them in a `<span>` or other elements with the `v-pre` directive:
**Input**
```md
This <span v-pre>{{ will be displayed as-is }}</span>
```
**Output**
<div class="escape-demo">
<p>This <span v-pre>{{ will be displayed as-is }}</span></p>
</div>
Alternatively, you can wrap the entire paragraph in a `v-pre` custom container:
```md
::: v-pre
{{ This will be displayed as-is }}`
:::
```
**Output**
<div class="escape-demo">
::: v-pre
{{ This will be displayed as-is }}
:::
</div>
## Unescape in Code Blocks
By default, all fenced code blocks are automatically wrapped with `v-pre`, so no Vue syntax will be processd inside. To enable Vue-style interpolation inside fences, you can append the language with the `-vue` suffix, e.g. `js-vue`:
**Input**
````md
```js-vue
Hello {{ 1 + 1 }}
```
````
**Output**
```js-vue
Hello {{ 1 + 1 }}
```
## Using CSS Pre-processors
VitePress has [built-in support](https://vitejs.dev/guide/features.html#css-pre-processors) for CSS pre-processors: `.scss`, `.sass`, `.less`, `.styl` and `.stylus` files. There is no need to install Vite-specific plugins for them, but the corresponding pre-processor itself must be installed:
@ -171,39 +216,6 @@ Then you can use the following in Markdown and theme components:
</style>
```
## Script & Style Hoisting
Sometimes you may need to apply some JavaScript or CSS only to the current page. In those cases, you can directly write root-level `<script>` or `<style>` blocks in the Markdown file. These will be hoisted out of the compiled HTML and used as the `<script>` and `<style>` blocks for the resulting Vue single-file component:
<p class="demo" :class="$style.example"></p>
<style module>
.example {
color: #41b883;
}
</style>
<script>
import ComponentInHeader from '../components/ComponentInHeader.vue'
export default {
props: ['slot-key'],
components: { ComponentInHeader },
mounted () {
document.querySelector(`.${this.$style.example}`)
.textContent = 'This is rendered by inline script and styled by inline CSS'
}
}
</script>
## Built-In Components
VitePress provides Built-In Vue Components like `ClientOnly`, check out the [Global Component Guide](/api/) for more information.
**Also see:**
- [Using Components In Headers](#using-components-in-headers)
## Browser API Access Restrictions
Because VitePress applications are server-rendered in Node.js when generating static builds, any Vue usage must conform to the [universal code requirements](https://vuejs.org/guide/scaling-up/ssr.html). In short, make sure to only access Browser / DOM APIs in `beforeMount` or `mounted` hooks.
@ -263,7 +275,7 @@ export default {
## Using Teleports
Vitepress currently has SSG support for teleports to body only. For other targets, you can wrap them inside the built-in `<ClientOnly>` component or inject the teleport markup into the correct location in your final page HTML through [`postRender` hook](../config/app-config#postrender).
Vitepress currently has SSG support for teleports to body only. For other targets, you can wrap them inside the built-in `<ClientOnly>` component or inject the teleport markup into the correct location in your final page HTML through [`postRender` hook](/reference/site-config#postrender).
<ModalDemo />
@ -284,3 +296,11 @@ Vitepress currently has SSG support for teleports to body only. For other target
<script setup>
import ModalDemo from '../components/ModalDemo.vue'
</script>
<style>
.escape-demo {
border: 1px solid var(--vp-c-border);
border-radius: 8px;
padding: 0 20px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@ -1,6 +1,26 @@
# What is VitePress?
VitePress is a [Static Site Generator](https://en.wikipedia.org/wiki/Static_site_generator) (SSG). It is designed for building performant content-centric websites, such as this documentation you are reading right now. It also powers the documentation for [Vue.js](https://vuejs.org/), [Vite](https://vitejs.dev/), and many more<!-- TODO: showcase page? -->. In a nutshell, VitePress takes your source content written in [Markdown](https://en.wikipedia.org/wiki/Markdown), applies a theme to it, and generates a directory of static HTML pages (and necessary asset files) that can be easily deployed anywhere.
VitePress is a [Static Site Generator](https://en.wikipedia.org/wiki/Static_site_generator) (SSG) designed for building fast, content-centric websites. In a nutshell, VitePress takes your source content written in [Markdown](https://en.wikipedia.org/wiki/Markdown), applies a theme to it, and generates static HTML pages that can be easily deployed anywhere.
<div class="tip custom-block" style="padding-top: 8px">
Just want to try it out? Skip to the [Quickstart](./getting-started).
</div>
## Use Cases
- **Documentation**
VitePress ships with a default theme designed for technical documentation, especially those that need to embed interactive demos. It powers this page you are reading right now, along with the documentation for [Vite](https://vitejs.dev/), [Pinia](https://pinia.vuejs.org/), [VueUse](https://vueuse.org/), [Mermaid](https://mermaid.js.org/), [Wikimedia Codex](https://doc.wikimedia.org/codex/latest/), and many more.
The [official Vue.js documentation](https://vuejs.org/) is also based on VitePress, but uses a custom theme shared between multiple translations.
- **Blogs, Portfolios, and Marketing Sites**
VitePress supports [fully customized themes](/guide/custom-theme), with the developer experience of a standard Vite + Vue application. Being built on Vite also means you can directly leverage Vite plugins from its rich ecosystem. In addition, VitePress provides flexible APIs to [load data](/guide/data-loading) (local or remote) and [dynamically generate routes](/guide/routing#dynamic-routes). You can use it to build almost anything as long as the data can be determined at build time.
The official [Vue.js blog](https://blog.vuejs.org/) is a simple blog that generates its index page based on local content.
## Developer Experience
@ -18,7 +38,7 @@ Unlike many traditional SSGs, a website generated by VitePress is in fact a [Sin
- **Fast Initial Load**
The initial visit to any page will be served the static, pre-rendered HTML for maximum loading speed, together with a JavaScript bundle that turns the page into a Vue SPA ("hydration"). The hydration process is extremely fast: on [PageSpeed Insights](https://pagespeed.web.dev/), typical VitePress sites achieve near-perfect performance scores even on low-end mobile devices with a slow network.
The initial visit to any page will be served the static, pre-rendered HTML for blazing fast loading speed and optimal SEO. The page then loads a JavaScript bundle that turns the page into a Vue SPA ("hydration"). The hydration process is extremely fast: on [PageSpeed Insights](https://pagespeed.web.dev/report?url=https%3A%2F%2Fvitepress.vuejs.org%2F), typical VitePress sites achieve near-perfect performance scores even on low-end mobile devices with a slow network.
- **Fast Post-load Navigation**
@ -28,14 +48,6 @@ Unlike many traditional SSGs, a website generated by VitePress is in fact a [Sin
To be able to hydrate the dynamic Vue parts embedded inside static Markdown, each Markdown page is processed as a Vue component and compiled into JavaScript. This may sound inefficient, but the Vue compiler is smart enough to separate the static and dynamic parts, minimizing both the hydration cost and payload size. For the initial page load, the static parts are automatically eliminated from the JavaScript payload and skipped during hydration.
## Theming & Extensibility
VitePress ships with a feature-rich default theme designed for documentation purposes. It allows you to spin up a beautiful documentation site like this one with minimal effort, and doesn't require any Vue-specific knowledge.
VitePress also supports fully customized themes with the developer experience of a standard Vite + Vue application. Being built on Vite also means you can directly leverage Vite plugins from its rich ecosystem. This makes VitePress an ideal choice for building sites that is content-centric but also requires non-trivial interactivity. The [Vue.js documentation](https://github.com/vuejs/docs) is a good example of such customization.
And of course, you can use it to build a blog! The [official Vue.js blog](https://github.com/vuejs/blog) is also built with VitePress.
## What About VuePress?
VitePress is the spiritual successor of VuePress. The original VuePress was based on Vue 2 and webpack. With Vue 3 and Vite under the hood, VitePress provides significantly better DX, better production performance, a more polished default theme, and a more flexible customization API.

@ -7,7 +7,7 @@ titleTemplate: Vite & Vue Powered Static Site Generator
hero:
name: VitePress
text: Vite & Vue Powered Static Site Generator
tagline: Simple, powerful, and performant. Meet the modern SSG framework you've always wanted.
tagline: Simple, powerful, and fast. Meet the modern SSG framework you've always wanted.
actions:
- theme: brand
text: Get Started
@ -17,12 +17,22 @@ hero:
link: https://github.com/vuejs/vitepress
features:
- title: "Vite: The DX that can't be beat"
details: Feel the speed of Vite. Instant server start and lightning fast HMR that stays fast regardless of the app size.
- title: Designed to be simplicity first
details: With Markdown-centered content, it's built to help you focus on writing and deployed with minimum configuration.
- title: Power of Vue meets Markdown
details: Enhance your content with all the features of Vue in Markdown, while being able to customize your site with Vue.
- title: Fully static yet still dynamic
details: Go wild with true SSG + SPA architecture. Static on page load, but engage users with 100% interactivity from there.
- icon: 📝
title: Focus on Your Content
details: Effortlessly create beautiful documentation sites with just markdown.
- icon:
src: vite.svg
width: 10
height: 10
title: Enjoy the Vite DX
details: Instant server start, lightning fast hot updates, and leverage Vite ecosystem plugins.
- icon:
src: vue.svg
width: 10
height: 10
title: Customize with Vue
details: Use Vue syntax and components directly in markdown, or build custom themes with Vue components.
- icon: 🚀
title: Ship Fast Sites
details: Fast initial load with static HTML, fast post-load navigation with client-side routing.
---

@ -0,0 +1,15 @@
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#41D1FF"/>
<stop offset="1" stop-color="#BD34FE"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEA83"/>
<stop offset="0.0833333" stop-color="#FFDD35"/>
<stop offset="1" stop-color="#FFA800"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196.32 170.02">
<path fill="#42b883" d="M120.83 0L98.16 39.26 75.49 0H0l98.16 170.02L196.32 0h-75.49z"/>
<path fill="#35495e" d="M120.83 0L98.16 39.26 75.49 0H39.26l58.9 102.01L157.06 0h-36.23z"/>
</svg>

After

Width:  |  Height:  |  Size: 261 B

@ -0,0 +1,74 @@
# Command Line Interface
## `vitepress dev`
Start VitePress dev server using designated directory as root. Defaults to current directory. The `dev` command can also be omitted when running in current directory.
### Usage
```sh
# start in current directory, omitting `dev`
vitepress
# start in sub directory
vitepress dev [root]
```
### Options
| Option | Description |
| - | - |
| `--open [path]` | Open browser on startup (`boolean \| string`) |
| `--port <port>` | Specify port (`number`) |
| `--base <path>` | Public base path (default: `/`) (`string`) |
| `--cors` | Enable CORS |
| `--strictPort` | Exit if specified port is already in use (`boolean`) |
| `--force` | Force the optimizer to ignore the cache and re-bundle (`boolean`) |
## `vitepress build`
Build the VitePress site for production.
### Usage
```sh
vitepress build [root]
```
### Options
| Option | Description |
| - | - |
| `--mpa` (experimental) | Build in [MPA mode](/guide/mpa-mode) without client-side hydration (`boolean`) |
| `--base <path>` | Public base path (default: `/`) (`string`) |
| `--target <target>` | Transpile target (default: `"modules"`) (`string`) |
| `--outDir <dir>` | Output directory (default: `.vitepress/dist`) (`string`) |
| `--minify [minifier]` | Enable/disable minification, or specify minifier to use (default: `"esbuild"`) (`boolean \| "terser" \| "esbuild"`) |
| `--assetsInlineLimit <number>` | Static asset base64 inline threshold in bytes (default: `4096`) (`number`) |
## `vitepress preview`
Locally preview the production build.
### Usage
```sh
vitepress preview [root]
```
### Options
| Option | Description |
| - | - |
| `--base <path>` | Public base path (default: `/`) (`string`) |
| `--port <port>` | Specify port (`number`) |
## `vitepress init`
Start the [Setup Wizard](/guide/getting-started#setup-wizard) in current directory.
### Usage
```sh
vitepress init
```

@ -64,7 +64,7 @@ export default {
- Type: `NavItem`
The configuration for the nav menu item. You may learn more details at [Theme: Nav](../guide/theme-nav#navigation-links).
The configuration for the nav menu item. More details in [Default Theme: Nav](./default-theme-nav#navigation-links).
```js
export default {
@ -111,7 +111,7 @@ interface NavItemWithChildren {
- Type: `Sidebar`
The configuration for the sidebar menu item. You may learn more details at [Theme: Sidebar](../guide/theme-sidebar).
The configuration for the sidebar menu item. More details in [Default Theme: Sidebar](./default-theme-sidebar).
```js
export default {
@ -271,7 +271,7 @@ export interface Footer {
- Type: `EditLink`
Edit Link lets you display a link to edit the page on Git management services such as GitHub, or GitLab. See [Theme: Edit Link](../guide/theme-edit-link) for more details.
Edit Link lets you display a link to edit the page on Git management services such as GitHub, or GitLab. See [Default Theme: Edit Link](./default-theme-edit-link) for more details.
```js
export default {
@ -310,7 +310,7 @@ export default {
- Type: `AlgoliaSearch`
An option to support searching your docs site using [Algolia DocSearch](https://docsearch.algolia.com/docs/what-is-docsearch). Learn more in [Theme: Search](../guide/theme-search)
An option to support searching your docs site using [Algolia DocSearch](https://docsearch.algolia.com/docs/what-is-docsearch). Learn more in [Default Theme: Search](./default-theme-search)
```ts
export interface AlgoliaSearchOptions extends DocSearchProps {
@ -344,7 +344,7 @@ export interface CarbonAdsOptions {
}
```
Learn more in [Theme: Carbon Ads](../guide/theme-carbon-ads)
Learn more in [Default Theme: Carbon Ads](./default-theme-carbon-ads)
## docFooter
@ -389,4 +389,11 @@ Can be used to customize the sidebar menu label. This label is only displayed in
- Type: `string`
- Default: `Return to top`
Can be used to customize the label of the returnToTop. This label is only displayed in the mobile view.
Can be used to customize the label of the return to top button. This label is only displayed in the mobile view.
## langMenuLabel
- Type: `string`
- Default: `Change language`
Can be used to customize the aria-label of the language toggle button in navbar. This is only used if you're using [i18n](../guide/i18n).

@ -36,4 +36,4 @@ export default {
}
```
Note that footer will not be displayed when the [SideBar](./theme-sidebar) is visible.
Note that footer will not be displayed when the [SideBar](/reference/default-theme-sidebar) is visible.

@ -1,6 +1,6 @@
# Home Page
VitePress default theme provides a homepage layout, which you can also see used on [the homepage of this site](../). You may use it on any of your pages by specifying `layout: home` in the [frontmatter](./frontmatter).
VitePress default theme provides a homepage layout, which you can also see used on [the homepage of this site](../). You may use it on any of your pages by specifying `layout: home` in the [frontmatter](./frontmatter-config).
```yaml
---
@ -131,7 +131,7 @@ interface Feature {
// Link when clicked on feature component. The link can
// be both internal or external.
//
// e.g. `guide/theme-home-page` or `htttps://example.com`
// e.g. `guid/reference/default-theme-home-page` or `htttps://example.com`
link?: string
// Link text to be shown inside feature component. Best

@ -1,6 +1,6 @@
# Layout
You may choose the page layout by setting `layout` option to the page [frontmatter](./frontmatter). There are 3 layout options, `doc`, `page`, and `home`. If nothing is specified, then the page is treated as `doc` page.
You may choose the page layout by setting `layout` option to the page [frontmatter](./frontmatter-config). There are 3 layout options, `doc`, `page`, and `home`. If nothing is specified, then the page is treated as `doc` page.
```yaml
---
@ -19,11 +19,11 @@ It also provides documentation specific features listed below. These features ar
- Edit Link
- Prev Next Link
- Outline
- [Carbon Ads](./theme-carbon-ads)
- [Carbon Ads](./default-theme-carbon-ads)
## Page Layout
Option `page` is treated as "blank page". The Markdown will still be parsed, and all of the [Markdown Extensions](./markdown) work as same as `doc` layout, but it wouldn't get any default stylings.
Option `page` is treated as "blank page". The Markdown will still be parsed, and all of the [Markdown Extensions](/guide/markdown) work as same as `doc` layout, but it wouldn't get any default stylings.
The page layout will let you style everything by you without VitePress theme affecting the markup. This is useful when you want to create your own custom page.
@ -31,7 +31,7 @@ Note that even in this layout, sidebar will still show up if the page has a matc
## Home Layout
Option `home` will generate templated "Homepage". In this layout, you can set extra options such as `hero` and `features` to customize the content further. Please visit [Theme: Home Page](./theme-home-page) for more details.
Option `home` will generate templated "Homepage". In this layout, you can set extra options such as `hero` and `features` to customize the content further. Please visit [Default Theme: Home Page](/reference/default-theme-home-page) for more details.
## No Layout

@ -4,7 +4,7 @@ The Nav is the navigation bar displayed on top of the page. It contains the site
## Site Title and Logo
By default, nav shows the title of the site referencing [`config.title`](../config/app-config#title) value. If you would like to change what's displayed on nav, you may define custom text in `themeConfig.siteTitle` option.
By default, nav shows the title of the site referencing [`config.title`](/reference/site-config#title) value. If you would like to change what's displayed on nav, you may define custom text in `themeConfig.siteTitle` option.
```js
export default {
@ -35,7 +35,7 @@ export default {
}
```
You can also pass an object as logo if you want to add `alt` attribute or customize it based on dark/light mode. Refer [`themeConfig.logo`](../config/theme-config#logo) for details.
You can also pass an object as logo if you want to add `alt` attribute or customize it based on dark/light mode. Refer [`themeConfig.logo`](/reference/default-theme-config#logo) for details.
## Navigation Links
@ -159,4 +159,4 @@ export default {
## Social Links
Refer [`socialLinks`](../config/theme-config#sociallinks).
Refer [`socialLinks`](/reference/default-theme-config#sociallinks).

@ -1,4 +1,4 @@
# Prev Next Link
# Prev Next Links
You can customize the text and link for the previous and next pages (shown at doc footer). This is helpful if you want a different text there than what you have on your sidebar. Additionally, you may find it useful to disable the footer or link to a page that is not included in your sidebar.

@ -1,6 +1,6 @@
# Sidebar
The sidebar is the main navigation block for your documentation. You can configure the sidebar menu in [`themeConfig.sidebar`](/config/theme-config#sidebar).
The sidebar is the main navigation block for your documentation. You can configure the sidebar menu in [`themeConfig.sidebar`](/reference/default-theme-config#sidebar).
```js
export default {

@ -68,7 +68,7 @@ If you have large number of members, or simply would like to have more space to
## Create a full Team Page
Instead of adding team members to doc page, you may also create a full Team Page, similar to how you can create a custom [Home Page](./theme-home-page).
Instead of adding team members to doc page, you may also create a full Team Page, similar to how you can create a custom [Home Page](/reference/default-theme-home-page).
To create a team page, first, create a new md file. The file name doesn't matter, but here lets call it `team.md`. In this file, set frontmatter option `layout: page`, and then you may compose your page structure using `TeamPage` components.
@ -207,7 +207,7 @@ interface TeamMember {
// Social links. e.g. GitHub, Twitter, etc. You may pass in
// the Social Links object here.
// See: https://vitepress.vuejs.org/config/theme-config.html#sociallinks
// See: https://vitepress.vuejs.org/reference/default-theme-config.html#sociallinks
links?: SocialLink[]
// URL for the sponsor page for the member.

@ -0,0 +1,145 @@
---
outline: deep
---
# Frontmatter Config
Frontmatter enables page based configuration. In every markdown file, you can use frontmatter config to override site-level or theme-level config options. Also, there are config options which you can only define in frontmatter.
Example usage:
```md
---
title: Docs with VitePress
editLink: true
---
```
You can access frontmatter data via the `$frontmatter` global in Vue expressions:
```md
{{ $frontmatter.title }}
```
## title
- Type: `string`
Title for the page. It's same as [config.title](/reference/site-config#title), and it overrides the site-level config.
```yaml
---
title: VitePress
---
```
## titleTemplate
- Type: `string | boolean`
The suffix for the title. It's same as [config.titleTemplate](/reference/site-config#titletemplate), and it overrides the site-level config.
```yaml
---
title: VitePress
titleTemplate: Vite & Vue powered static site generator
---
```
## description
- Type: `string`
Description for the page. It's same as [config.description](/reference/site-config#description), and it overrides the site-level config.
```yaml
---
description: VitePress
---
```
## head
- Type: `HeadConfig[]`
Specify extra head tags to be injected for the current page. Will be appended after head tags injected by site-level config.
```yaml
---
head:
- - meta
- name: description
content: hello
- - meta
- name: keywords
content: super duper SEO
---
```
```ts
type HeadConfig =
| [string, Record<string, string>]
| [string, Record<string, string>, string]
```
## Default Theme Only
The following frontmatter options are only applicable when using the default theme.
### layout <Badge type="info" text="default theme only" />
- Type: `doc | home | page`
- Default: `doc`
Determines the layout of the page.
- `doc` - It applies default documentation styles to the markdown content.
- `home` - Special layout for "Home Page". You may add extra options such as `hero` and `features` to rapidly create beautiful landing page.
- `page` - Behave similar to `doc` but it applies no styles to the content. Useful when you want to create a fully custom page.
```yaml
---
layout: doc
---
```
### hero <Badge type="info" text="default theme only" /> <Badge type="info" text="Home page only" />
Defines contents of home hero section when `layout` is set to `home`. More details in [Default Theme: Home Page](/reference/default-theme-home-page).
### features <Badge type="info" text="default theme only" /> <Badge type="info" text="Home page only" />
Defines items to display in features section when `layout` is set to `home`. More details in [Default Theme: Home Page](/reference/default-theme-home-page).
### aside <Badge type="info" text="default theme only" />
- Type: `boolean`
- Default: `true`
If you want the right aside component in `doc` layout not to be shown, set this option to `false`.
```yaml
---
aside: false
---
```
### outline <Badge type="info" text="default theme only" />
- Type: `number | [number, number] | 'deep' | false`
- Default: `2`
The levels of header in the outline to display for the page. It's same as [config.themeConfig.outline](/reference/default-theme-config#outline), and it overrides the theme config.
### lastUpdated <Badge type="info" text="default theme only" />
- Type: `boolean`
- Default: `true`
Whether to display [Last Updated](/reference/default-theme-last-updated) text in the current page.
```yaml
---
lastUpdated: false
---
```

@ -1,21 +1,37 @@
# Runtime API Reference
# Runtime API
VitePress offers several built-in APIs to let you access app data. VitePress also comes with a few built-in components that can be used globally.
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`
## `useData` <Badge type="info" text="composable" />
Returns page-specific data. The returned object has the following type:
```ts
interface VitePressData<T = any> {
/**
* Site-level metadata
*/
site: Ref<SiteData<T>>
/**
* themeConfig from .vitepress/config.js
*/
theme: Ref<T>
/**
* Page-level metadata
*/
page: Ref<PageData>
theme: Ref<T> // themeConfig from .vitepress/config.js
/**
* Page frontmatter
*/
frontmatter: Ref<PageData['frontmatter']>
/**
* Dynamic route params
*/
params: Ref<PageData['params']>
title: Ref<string>
description: Ref<string>
lang: Ref<string>
@ -23,6 +39,18 @@ interface VitePressData<T = any> {
dir: Ref<string>
localeIndex: Ref<string>
}
interface PageData {
title: string
titleTemplate?: string | boolean
description: string
relativePath: string
headers: Header[]
frontmatter: Record<string, any>
params?: Record<string, any>
isNotFound?: boolean
lastUpdated?: number
}
```
**Example:**
@ -39,7 +67,7 @@ const { theme } = useData()
</template>
```
## `useRoute`
## `useRoute` <Badge type="info" text="composable" />
Returns the current route object with the following type:
@ -51,7 +79,7 @@ interface Route {
}
```
## `useRouter`
## `useRouter` <Badge type="info" text="composable" />
Returns the VitePress router instance so you can programmatically navigate to another page.
@ -62,15 +90,15 @@ interface Router {
}
```
## `withBase`
## `withBase` <Badge type="info" text="helper" />
- **Type**: `(path: string) => string`
Appends the configured [`base`](/config/app-config#base) to a given URL path. Also see [Base URL](/guide/asset-handling#base-url).
Appends the configured [`base`](/reference/site-config#base) to a given URL path. Also see [Base URL](/guide/asset-handling#base-url).
## `<Content />`
## `<Content />` <Badge type="info" text="component" />
The `<Content />` component displays the rendered markdown contents. Useful [when creating your own theme](/guide/customization-intro).
The `<Content />` component displays the rendered markdown contents. Useful [when creating your own theme](/guide/custom-theme).
```vue
<template>
@ -79,7 +107,7 @@ The `<Content />` component displays the rendered markdown contents. Useful [whe
</template>
```
## `<ClientOnly />`
## `<ClientOnly />` <Badge type="info" text="component" />
The `<ClientOnly />` component renders its slot only at client side.
@ -92,3 +120,24 @@ If you are using or demoing components that are not SSR-friendly (for example, c
<NonSSRFriendlyComponent />
</ClientOnly>
```
## `$frontmatter` <Badge type="info" text="template global" />
Directly access current page's [frontmatter](/guide/frontmatter) data in Vue expressions.
```md
---
title: Hello
---
# {{ $frontmatter.title }}
```
## `$params` <Badge type="info" text="template global" />
Directly access current page's [dynamic route params](/guide/routing#dynamic-routes) in Vue expressions.
```md
- package name: {{ $params.pkg }}
- version: {{ $params.version }}
```

@ -1,6 +1,32 @@
# App Config
---
outline: deep
---
App config is where you can define the global settings of the site. App config options define settings that apply to every VitePress site, regardless of what theme it is using. For example, the base directory or the title of the site.
# Site Config
Site config is where you can define the global settings of the site. App config options define settings that apply to every VitePress site, regardless of what theme it is using. For example, the base directory or the title of the site.
<div class="site-config-toc">
[[toc]]
</div>
<style>
@media (min-width: 1280px) {
.site-config-toc {
display: none;
}
}
</style>
## Overview
### Config Resolution
The config file is always resolved from `<root>/.vitepress/config.[ext]`, where `<root>` is your VitePress [project root](/guide/routing#root-and-source-directory), and `[ext]` is one of the supported file extensions. TypeScript is supported out of the box. Supported extensions include `.js`, `.ts`, `.cjs`, `.mjs`, `.cts`, and `.mts`.
It is recommended to use ES modules syntax in config files. The config file should default export an object:
```ts
export default {
@ -12,44 +38,104 @@ export default {
}
```
## appearance
### Config Intellisense
- Type: `boolean | 'dark'`
- Default: `true`
Using the `defineConfig` helper will provide TypeScript-powered intellisense for config options. Assuming your IDE supports it, this should work in both JavaScript and TypeScript.
Whether to enable dark mode or not.
```js
import { defineConfig } from 'vitepress'
- If the option is set to `true`, the default theme will be determined by the user's preferred color scheme.
- If the option is set to `dark`, the theme will be dark by default, unless the user manually toggles it.
- If the option is set to `false`, users will not be able to toggle the theme.
export default defineConfig({
// ...
})
```
### Typed Theme Config
It also injects inline script that tries to read users settings from local storage by `vitepress-theme-appearance` key and restores users preferred color mode.
By default, `defineConfig` helper expects the theme config type from default theme:
```ts
import { defineConfig } from 'vitepress'
export default defineConfig({
themeConfig: {
// Type is `DefaultTheme.Config`
}
})
```
If you use a custom theme and want type checks for the theme config, you'll need to use `defineConfigWithTheme` instead, and pass the config type for your custom theme via a generic argument:
```ts
import { defineConfigWithTheme } from 'vitepress'
import type { ThemeConfig } from 'your-theme'
export default defineConfigWithTheme<ThemeConfig>({
themeConfig: {
// Type is `ThemeConfig`
}
})
```
## Site Metadata
### title
- Type: `string`
- Default: `VitePress`
- Can be overridden per page via [frontmatter](./frontmatter-config#title)
Title for the site. When using the default theme, this will be displayed in the nav bar.
It will also be used as the default suffix for all individual page titles, unless [`titleTemplate`](#titletemplate) is defined. An individual page's final title will be the text content of its first `<h1>` header, combined with the global `title` as the suffix. For example with the following config and page content:
```ts
export default {
appearance: true
title: 'My Awesome Site'
}
```
```md
# Hello
```
## base
The title of the page will be `Hello | My Awesome Site`.
- Type: `string`
- Default: `/`
### titleTemplate
The base URL the site will be deployed at. You will need to set this if you plan to deploy your site under a sub path, for example, GitHub pages. If you plan to deploy your site to `https://foo.github.io/bar/`, then you should set base to `'/bar/'`. It should always start and end with a slash.
- Type: `string | boolean`
- Can be overridden per page via [frontmatter](./frontmatter-config#titletemplate)
The base is automatically prepended to all the URLs that start with / in other options, so you only need to specify it once.
Allows customizing each page's title suffix or the entire title. For example:
```ts
export default {
base: '/base/'
title: 'My Awesome Site',
titleTemplate: 'Custom Suffix'
}
```
```md
# Hello
```
## description
The title of the page will be `Hello | Custom Suffix`.
To completely customize how the title should be rendered, you can use the `:title` symbol in `titleTemplate`:
```ts
export default {
titleTemplate: ':title - Custom Suffix'
}
```
Here `:title` will be replaced with the text inferred from the page's first `<h1>` header. The title of the previous example page will be `Hello - Custom Suffix`.
The option can be set to `false` to disable title suffixes.
### description
- Type: `string`
- Default: `A VitePress site`
- Can be overridden per page via [frontmatter](./frontmatter-config#description)
Description for the site. This will render as a `<meta>` tag in the page HTML.
@ -59,10 +145,11 @@ export default {
}
```
## head
### head
- Type: `HeadConfig[]`
- Default: `[]`
- Can be appended per page via [frontmatter](./frontmatter-config#head)
Additional elements to render in the `<head>` tag in the page HTML. The user-added tags are rendered before the closing `head` tag, after VitePress tags.
@ -84,46 +171,162 @@ type HeadConfig =
| [string, Record<string, string>, string]
```
## ignoreDeadLinks
### lang
- Type: `boolean | 'localhostLinks'`
- Default: `false`
- Type: `string`
- Default: `en-US`
When set to `true`, VitePress will not fail builds due to dead links. When set to `localhostLinks`, the build will fail on dead links, but won't check `localhost` links.
The lang attribute for the site. This will render as a `<html lang="en-US">` tag in the page HTML.
```ts
export default {
ignoreDeadLinks: true
lang: 'en-US'
}
```
## lang
### base
- Type: `string`
- Default: `en-US`
- Default: `/`
The lang attribute for the site. This will render as a `<html lang="en-US">` tag in the page HTML.
The base URL the site will be deployed at. You will need to set this if you plan to deploy your site under a sub path, for example, GitHub pages. If you plan to deploy your site to `https://foo.github.io/bar/`, then you should set base to `'/bar/'`. It should always start and end with a slash.
The base is automatically prepended to all the URLs that start with / in other options, so you only need to specify it once.
```ts
export default {
lang: 'en-US'
base: '/base/'
}
```
## lastUpdated
## Routing
### cleanUrls
- Type: `boolean`
- Default: `false`
Use git commit to get the timestamp. This option enables the default theme to display the page's last updated time. You can customize the text via [`themeConfig.lastUpdatedText`](theme-config#lastupdatedtext) option.
When set to `true`, VitePress will remove the trailing `.html` from URLs. Also see [Generating Clean URL](/guide/routing#generating-clean-url).
::: warning Server Support Required
Enabling this may require additional configuration on your hosting platform. For it to work, your server must be able to serve `/foo.html` when visiting `/foo` **without a redirect**.
:::
### rewrites
- Type: `Record<string, string>`
Defines custom directory <-> URL mappings. See [Routing: Route Rewrites](/guide/routing#route-rewrites) for more details.
```ts
export default {
lastUpdated: true
rewrites: {
'source/:page': 'destination/:page'
}
}
```
## markdown
## Build
### srcDir
- Type: `string`
- Default: `.`
The directory where your markdown pages are stored, relative to project root. Also see [Root and Source Directory](/guide/routing#root-and-source-directory).
```ts
export default {
srcDir: './src'
}
```
### srcExclude
- Type: `string`
- Default: `undefined`
A [glob pattern](https://github.com/mrmlnc/fast-glob#pattern-syntax) for matching markdown files that should be excluded as source content.
```ts
export default {
srcExclude: ['**/README.md', '**/TODO.md']
}
```
### outDir
- Type: `string`
- Default: `./.vitepress/dist`
The build output location for the site, relative to [project root](/guide/routing#root-and-source-directory).
```ts
export default {
outDir: '../public'
}
```
### cacheDir
- Type: `string`
- Default: `./.vitepress/cache`
The directory for cache files, relative to [project root](/guide/routing#root-and-source-directory). See also: [cacheDir](https://vitejs.dev/config/shared-options.html#cachedir).
```ts
export default {
cacheDir: './.vitepress/.vite'
}
```
### ignoreDeadLinks
- Type: `boolean | 'localhostLinks'`
- Default: `false`
When set to `true`, VitePress will not fail builds due to dead links. When set to `'localhostLinks'`, the build will fail on dead links, but won't check `localhost` links.
```ts
export default {
ignoreDeadLinks: true
}
```
### mpa <Badge type="warning" text="experimental" />
- Type: `boolean`
- Default: `false`
When set to `true`, the production app will be built in [MAP Mode](/guide/mpa-mode). MPA mode ships 0kb JavaScript by default, at the cost of disabling client-side navigation and requires explicit opt-in for interactivity.
## Theming
### appearance
- Type: `boolean | 'dark'`
- Default: `true`
Whether to enable dark mode (by adding the `.dark` class to the `<html>` element).
- If the option is set to `true`, the default theme will be determined by the user's preferred color scheme.
- If the option is set to `dark`, the theme will be dark by default, unless the user manually toggles it.
- If the option is set to `false`, users will not be able to toggle the theme.
This option injects an inline script that restores users settings from local storage using the `vitepress-theme-appearance` key. This ensures the `.dark` class is applied before the page is rendered to avoid flickering.
### lastUpdated
- Type: `boolean`
- Default: `false`
Whether to get the last updated timestamp for each page using Git. The timestamp will be included in each page's page data, accessible via [`useData`](/reference/runtime-api#usedata).
When using the default theme, enabling this option will display each page's last updated time. You can customize the text via [`themeConfig.lastUpdatedText`](./default-theme-config#lastupdatedtext) option.
## Customization
### markdown
- Type: `MarkdownOption`
@ -195,111 +398,17 @@ interface MarkdownOptions extends MarkdownIt.Options {
}
```
## outDir
### vite
- Type: `string`
- Default: `./.vitepress/dist`
- Type: `import('vite').UserConfig`
The build output location for the site, relative to project root (`docs` folder if you're running `vitepress build docs`).
Pass raw [Vite Config](https://vitejs.dev/config/) to internal Vite dev server / bundler.
```ts
export default {
outDir: '../public'
}
```
### vue
## cacheDir
- Type: `string`
- Default: `./.vitepress/cache`
The directory for cache files, relative to project root (`docs` folder if you're running `vitepress build docs`). See also: [cacheDir](https://vitejs.dev/config/shared-options.html#cachedir).
```ts
export default {
cacheDir: './.vitepress/.vite'
}
```
## srcDir
- Type: `string`
- Default: `.`
- Type: `import('@vitejs/plugin-vue').Options`
The directory where your markdown pages are stored, relative to project root.
```ts
export default {
srcDir: './src'
}
```
## title
- Type: `string`
- Default: `VitePress`
Title for the site. This will be displayed in the nav bar. Also used as the suffix for all page titles unless `titleTemplate` is defined.
```ts
export default {
title: 'VitePress'
}
```
## titleTemplate
- Type: `string | boolean`
The suffix for the title. For example, if you set `title` as `VitePress` and set `titleTemplate` as `My Site`, the html title becomes `VitePress | My Site`.
Set `false` to disable the feature. If the option is `undefined`, then the value of `title` option will be used.
```ts
export default {
title: 'VitePress',
titleTemplate: 'Vite & Vue powered static site generator'
}
```
To configure a title separator other than `|`, you can omit `title` and use the `:title` symbol in `titleTemplate`.
```ts
export default {
titleTemplate: ':title - Vitepress'
}
```
## cleanUrls
- Type: `boolean`
- Default: `false`
Allows removing trailing `.html` from URLs.
```ts
export default {
cleanUrls: true
}
```
::: warning
Enabling this may require additional configuration on your hosting platform. For it to work, your server must serve `/foo.html` on requesting `/foo` **without a redirect**.
:::
## rewrites
- Type: `Record<string, string>`
Defines custom directory <-> URL mappings. See [Routing: Customize the Mappings](/guide/routing#customize-the-mappings) for more details.
```ts
export default {
rewrites: {
'source/:page': 'destination/:page'
}
}
```
Pass raw [`@vitejs/plugin-vue` options](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue#options) to the internal plugin instance.
## Build Hooks

@ -1,6 +1,6 @@
{
"name": "vitepress",
"version": "1.0.0-alpha.49",
"version": "1.0.0-alpha.50",
"description": "Vite & Vue powered static site generator",
"type": "module",
"packageManager": "pnpm@7.28.0",
@ -80,10 +80,7 @@
"docs-debug": "node --inspect-brk ./bin/vitepress dev docs",
"docs-build": "run-s build docs-build-only",
"docs-build-only": "node ./bin/vitepress build docs",
"docs-preview": "node ./bin/vitepress preview docs",
"docs:dev": "vitepress dev /Users/evan/Vue/vitepress/__tests__/init/temp",
"docs:build": "vitepress build /Users/evan/Vue/vitepress/__tests__/init/temp",
"docs:preview": "vitepress preview /Users/evan/Vue/vitepress/__tests__/init/temp"
"docs-preview": "node ./bin/vitepress preview docs"
},
"dependencies": {
"@docsearch/css": "^3.3.3",

@ -1,20 +1,16 @@
import { defineComponent, h, onUpdated } from 'vue'
import { defineComponent, h } from 'vue'
import { useRoute } from '../router.js'
export const Content = defineComponent({
name: 'VitePressContent',
props: {
onContentUpdated: Function,
as: { type: [Object, String], default: 'div' }
},
setup(props) {
const route = useRoute()
onUpdated(() => {
props.onContentUpdated?.()
})
return () =>
h(props.as, { style: { position: 'relative' } }, [
route.component ? h(route.component) : null
route.component ? h(route.component) : '404 Page Not Found'
])
}
})

@ -19,10 +19,26 @@ import {
export const dataSymbol: InjectionKey<VitePressData> = Symbol()
export interface VitePressData<T = any> {
/**
* Site-level metadata
*/
site: Ref<SiteData<T>>
page: Ref<PageData>
/**
* themeConfig from .vitepress/config.js
*/
theme: Ref<T>
/**
* Page-level metadata
*/
page: Ref<PageData>
/**
* page frontmatter data
*/
frontmatter: Ref<PageData['frontmatter']>
/**
* dynamic route params
*/
params: Ref<PageData['params']>
title: Ref<string>
description: Ref<string>
lang: Ref<string>
@ -56,6 +72,7 @@ export function initData(route: Route): VitePressData {
theme: computed(() => site.value.themeConfig),
page: computed(() => route.data),
frontmatter: computed(() => route.data.frontmatter),
params: computed(() => route.data.params),
lang: computed(() => site.value.lang),
dir: computed(() => site.value.dir),
localeIndex: computed(() => site.value.localeIndex || 'root'),

@ -7,7 +7,7 @@ import {
onMounted,
watchEffect
} from 'vue'
import Theme from '@theme/index'
import RawTheme from '@theme/index'
import { inBrowser, pathToFile } from './utils.js'
import { type Router, RouterSymbol, createRouter } from './router.js'
import { siteDataRef, useData } from './data.js'
@ -19,7 +19,22 @@ import { ClientOnly } from './components/ClientOnly.js'
import { useCopyCode } from './composables/copyCode.js'
import { useCodeGroups } from './composables/codeGroups.js'
const NotFound = Theme.NotFound || (() => '404 Not Found')
function resolveThemeExtends(theme: typeof RawTheme): typeof RawTheme {
if (theme.extends) {
const base = resolveThemeExtends(theme.extends)
return {
...base,
...theme,
enhanceApp(ctx) {
if (base.enhanceApp) base.enhanceApp(ctx)
if (theme.enhanceApp) theme.enhanceApp(ctx)
}
}
}
return theme
}
const Theme = resolveThemeExtends(RawTheme)
const VitePressApp = defineComponent({
name: 'VitePressApp',
@ -59,17 +74,21 @@ export async function createApp() {
const data = initData(router.route)
app.provide(dataSymbol, data)
// provide this to avoid circular dependency in VPContent
app.provide('NotFound', NotFound)
// install global components
app.component('Content', Content)
app.component('ClientOnly', ClientOnly)
// expose $frontmatter
Object.defineProperty(app.config.globalProperties, '$frontmatter', {
get() {
return data.frontmatter.value
// expose $frontmatter & $params
Object.defineProperties(app.config.globalProperties, {
$frontmatter: {
get() {
return data.frontmatter.value
}
},
$params: {
get() {
return data.page.value.params
}
}
})
@ -120,7 +139,7 @@ function newRouter(): Router {
}
return import(/*@vite-ignore*/ pageFilePath)
}, NotFound)
}, Theme.NotFound)
}
if (inBrowser) {

@ -10,7 +10,16 @@ export interface EnhanceAppContext {
export interface Theme {
Layout: Component
NotFound?: Component
enhanceApp?: (ctx: EnhanceAppContext) => Awaitable<void>
extends?: Theme
/**
* @deprecated can be replaced by wrapping layout component
*/
setup?: () => void
/**
* @deprecated Render not found page by checking `useData().page.value.isNotFound` in Layout instead.
*/
NotFound?: Component
}

@ -1,17 +1,15 @@
<script setup lang="ts">
import { useRoute } from 'vitepress'
import { useData } from '../composables/data.js'
import { useSidebar } from '../composables/sidebar.js'
import VPPage from './VPPage.vue'
import VPHome from './VPHome.vue'
import VPDoc from './VPDoc.vue'
import { inject } from 'vue'
import NotFound from '../NotFound.vue'
const route = useRoute()
const { frontmatter } = useData()
const { page, frontmatter } = useData()
const { hasSidebar } = useSidebar()
const NotFound = inject('NotFound')
console.log(page.value)
</script>
<template>
@ -23,7 +21,7 @@ const NotFound = inject('NotFound')
'is-home': frontmatter.layout === 'home'
}"
>
<NotFound v-if="route.component === NotFound" />
<NotFound v-if="page.isNotFound" />
<VPPage v-else-if="frontmatter.layout === 'page'" />

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useRoute } from 'vitepress'
import { computed, provide, ref } from 'vue'
import { computed } from 'vue'
import { useSidebar } from '../composables/sidebar.js'
import VPDocAside from './VPDocAside.vue'
import VPDocFooter from './VPDocFooter.vue'
@ -11,9 +11,6 @@ const { hasSidebar, hasAside } = useSidebar()
const pageName = computed(() =>
route.path.replace(/[./]+/g, '_').replace(/_html$/, '')
)
const onContentUpdated = ref()
provide('onContentUpdated', onContentUpdated)
</script>
<template>
@ -42,7 +39,7 @@ provide('onContentUpdated', onContentUpdated)
<div class="content-container">
<slot name="doc-before" />
<main class="main">
<Content class="vp-doc" :class="pageName" :onContentUpdated="onContentUpdated" />
<Content class="vp-doc" :class="pageName" />
</main>
<slot name="doc-footer-before" />
<VPDocFooter />

@ -1,26 +1,20 @@
<script setup lang="ts">
import type { DefaultTheme } from 'vitepress/theme'
import { computed, inject, ref, type Ref } from 'vue'
import { computed, ref } from 'vue'
import { useData } from '../composables/data.js'
import {
getHeaders,
useActiveAnchor,
type MenuItem
resolveHeaders,
useActiveAnchor
} from '../composables/outline.js'
import VPDocAsideOutlineItem from './VPDocAsideOutlineItem.vue'
const { frontmatter, theme } = useData()
const { frontmatter, page, theme } = useData()
const pageOutline = computed<DefaultTheme.Config['outline']>(
() => frontmatter.value.outline ?? theme.value.outline
)
const onContentUpdated = inject('onContentUpdated') as Ref<() => void>
onContentUpdated.value = () => {
headers.value = getHeaders(pageOutline.value, theme.value.outlineBadges)
}
const headers = ref<MenuItem[]>([])
const headers = computed(() => {
return resolveHeaders(
page.value.headers,
frontmatter.value.outline ?? theme.value.outline
)
})
const hasOutline = computed(() => headers.value.length > 0)
const container = ref()

@ -59,7 +59,8 @@ defineProps<{
}
.VPFeature:deep(.VPImage) {
width: fit-content;
width: 48px;
height: 48px;
margin-bottom: 20px;
}

@ -2,8 +2,10 @@
import VPIconLanguages from './icons/VPIconLanguages.vue'
import VPFlyout from './VPFlyout.vue'
import VPMenuLink from './VPMenuLink.vue'
import { useData } from '../composables/data.js'
import { useLangs } from '../composables/langs.js'
const { theme } = useData()
const { localeLinks, currentLang } = useLangs({ correspondingLink: true })
</script>
@ -12,6 +14,7 @@ const { localeLinks, currentLang } = useLangs({ correspondingLink: true })
v-if="localeLinks.length && currentLang.label"
class="VPNavBarTranslations"
:icon="VPIconLanguages"
:label="theme.langMenuLabel || 'Change language'"
>
<div class="items">
<p class="title">{{ currentLang.label }}</p>

@ -11,41 +11,14 @@ export type MenuItem = Omit<Header, 'slug' | 'children'> & {
children?: MenuItem[]
}
export function getHeaders(
pageOutline: DefaultTheme.Config['outline'],
outlineBadges: DefaultTheme.Config['outlineBadges']
) {
if (pageOutline === false) return []
let updatedHeaders: MenuItem[] = []
document
.querySelectorAll<HTMLHeadingElement>('h2, h3, h4, h5, h6')
.forEach((el) => {
if (el.textContent && el.id) {
let title = el.textContent
if (outlineBadges === false) {
const clone = el.cloneNode(true) as HTMLElement
for (const child of clone.querySelectorAll('.VPBadge')) {
child.remove()
}
title = clone.textContent || ''
}
updatedHeaders.push({
level: Number(el.tagName[1]),
title: title.replace(/\s+#\s*$/, ''),
link: `#${el.id}`
})
}
})
return resolveHeaders(updatedHeaders, pageOutline)
}
export function resolveHeaders(
headers: MenuItem[],
range?: Exclude<DefaultTheme.Config['outline'], false>
range?: DefaultTheme.Config['outline']
) {
if (range === false) {
return []
}
const levelsRange =
(typeof range === 'object' && !Array.isArray(range)
? range.level
@ -58,51 +31,38 @@ export function resolveHeaders(
? [2, 6]
: levelsRange
return groupHeaders(headers, levels)
const isInRange = (h: MenuItem): boolean =>
h.level >= levels[0] && h.level <= levels[1]
return filterHeaders(headers, isInRange)
}
function groupHeaders(headers: MenuItem[], levelsRange: [number, number]) {
function filterHeaders(
headers: MenuItem[],
isInRange: (h: MenuItem) => boolean
) {
const result: MenuItem[] = []
headers = headers.map((h) => ({ ...h }))
headers.forEach((h, index) => {
if (h.level >= levelsRange[0] && h.level <= levelsRange[1]) {
if (addToParent(index, headers, levelsRange)) {
result.push(h)
headers.forEach((h) => {
if (isInRange(h)) {
if (h.children) {
const filteredChildren = filterHeaders(h.children, isInRange)
if (filteredChildren.length) {
h.children = filteredChildren
} else {
delete h.children
}
}
result.push(h)
} else if (h.children) {
result.push(...filterHeaders(h.children, isInRange))
}
})
return result
}
function addToParent(
currIndex: number,
headers: MenuItem[],
levelsRange: [number, number]
) {
if (currIndex === 0) {
return true
}
const currentHeader = headers[currIndex]
for (let index = currIndex - 1; index >= 0; index--) {
const header = headers[index]
if (
header.level < currentHeader.level &&
header.level >= levelsRange[0] &&
header.level <= levelsRange[1]
) {
if (header.children == null) header.children = []
header.children.push(currentHeader)
return false
}
}
return true
}
export function useActiveAnchor(
container: Ref<HTMLElement>,
marker: Ref<HTMLElement>

@ -11,7 +11,6 @@ import './styles/components/vp-sponsor.css'
import type { Theme } from 'vitepress'
import VPBadge from './components/VPBadge.vue'
import Layout from './Layout.vue'
import NotFound from './NotFound.vue'
export { default as VPHomeHero } from './components/VPHomeHero.vue'
export { default as VPHomeFeatures } from './components/VPHomeFeatures.vue'
@ -24,7 +23,6 @@ export { default as VPTeamMembers } from './components/VPTeamMembers.vue'
const theme: Theme = {
Layout,
NotFound,
enhanceApp: ({ app }) => {
app.component('Badge', VPBadge)
}

@ -405,7 +405,7 @@
z-index: 3;
/*rtl:ignore*/
border-right: 1px solid var(--vp-code-block-divider-color);
padding-top: 16px;
padding-top: 20px;
width: 32px;
text-align: center;
font-family: var(--vp-font-family-mono);

@ -291,7 +291,7 @@
--vp-custom-block-info-code-bg: var(--vp-c-bg-soft-down);
--vp-custom-block-tip-border: var(--vp-c-green);
--vp-custom-block-tip-text: var(--vp-c-green);
--vp-custom-block-tip-text: var(--vp-c-green-dark);
--vp-custom-block-tip-bg: var(--vp-c-bg-soft);
--vp-custom-block-tip-code-bg: var(--vp-c-bg-soft-down);

@ -62,7 +62,15 @@ export async function bundle(
...options,
emptyOutDir: true,
ssr,
outDir: ssr ? config.tempDir : config.outDir,
// minify with esbuild in MPA mode (for CSS)
minify: ssr
? config.mpa
? 'esbuild'
: false
: typeof options.minify === 'boolean'
? options.minify
: !process.env.DEBUG,
outDir: ssr ? config.tempDir : options.outDir || config.outDir,
cssCodeSplit: false,
rollupOptions: {
...rollupOptions,
@ -107,9 +115,7 @@ export async function bundle(
}
})
}
},
// minify with esbuild in MPA mode (for CSS)
minify: ssr ? (config.mpa ? 'esbuild' : false) : !process.env.DEBUG
}
}
})

@ -20,6 +20,11 @@ if (root) {
}
if (!command || command === 'dev') {
if (argv.force) {
delete argv.force
argv.optimizeDeps = { force: true }
}
const createDevServer = async () => {
const server = await createServer(root, argv, async () => {
await server.close()

@ -4,6 +4,8 @@ export * from './markdown'
export * from './build/build'
export * from './serve/serve'
export * from './init/init'
export { defineLoader, type LoaderModule } from './plugins/staticDataPlugin'
export { loadEnv } from 'vite'
// shared types
export type {

@ -9,7 +9,7 @@ import {
} from '@clack/prompts'
import fs from 'fs-extra'
import path from 'path'
import { black, cyan, bgCyan, bold } from 'picocolors'
import { black, cyan, bgCyan, bold, yellow } from 'picocolors'
import { fileURLToPath } from 'url'
// @ts-ignore
import template from 'lodash.template'
@ -106,7 +106,7 @@ export function scaffold({
theme,
useTs,
injectNpmScripts
}: ScaffoldOptions) {
}: ScaffoldOptions): string {
const resolvedRoot = path.resolve(root)
const templateDir = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
@ -158,25 +158,44 @@ export function scaffold({
}
const dir = root === './' ? `` : ` ${root.replace(/^\.\//, '')}`
const pkgPath = path.resolve('package.json')
const userPkg = fs.existsSync(pkgPath)
? JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
: {}
const tips = []
if (fs.existsSync('.git')) {
tips.push(
`Make sure to add ${cyan(`.vitepress/dist`)} and ` +
`${cyan(`.vitepress/cache`)} to your ${cyan(`.gitignore`)} file.`
)
}
if (
theme !== ScaffoldThemeType.Default &&
!userPkg.dependencies?.['vue'] &&
!userPkg.devDependencies?.['vue']
) {
tips.push(
`Since you've chosen to customize the theme, ` +
`you should also explicitly install ${cyan(`vue`)} as a dev dependency.`
)
}
const tip = tips.length ? yellow([`\n\nTips:`, ...tips].join('\n- ')) : ``
if (injectNpmScripts) {
const scripts = {
'docs:dev': `vitepress dev${dir}`,
'docs:build': `vitepress build${dir}`,
'docs:preview': `vitepress preview${dir}`
}
const pkgPath = path.resolve('package.json')
let pkg
if (!fs.existsSync(pkgPath)) {
pkg = { scripts }
} else {
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
Object.assign(pkg.scripts || (pkg.scripts = {}), scripts)
}
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2))
return `Done! Now run ${cyan(`npm run docs:dev`)} and start writing.`
Object.assign(userPkg.scripts || (userPkg.scripts = {}), scripts)
fs.writeFileSync(pkgPath, JSON.stringify(userPkg, null, 2))
return `Done! Now run ${cyan(`npm run docs:dev`)} and start writing.${tip}`
} else {
return `You're all set! Now run ${cyan(
`npx vitepress dev${dir}`
)} and start writing.`
)} and start writing.${tip}`
}
}

@ -106,6 +106,7 @@ export const createMarkdownRenderer = async (
...options.frontmatter
} as FrontmatterPluginOptions)
.use(headersPlugin, {
level: [2, 3, 4, 5, 6],
...options.headers
} as HeadersPluginOptions)
.use(sfcPlugin, {

@ -6,31 +6,31 @@ export function resolveRewrites(
pages: string[],
userRewrites: UserConfig['rewrites']
) {
const rewriteEntries = Object.entries(userRewrites || {})
const rewrites = rewriteEntries.length
? Object.fromEntries(
pages
.map((src) => {
for (const [from, to] of rewriteEntries) {
const dest = rewrite(src, from, to)
if (dest) return [src, dest]
}
})
.filter((e) => e != null) as [string, string][]
)
: {}
return {
map: rewrites,
inv: Object.fromEntries(Object.entries(rewrites).map((a) => a.reverse()))
const rewriteRules = Object.entries(userRewrites || {}).map(([from, to]) => ({
toPath: compile(to),
matchUrl: match(from)
}))
const pageToRewrite: Record<string, string> = {}
const rewriteToPage: Record<string, string> = {}
if (rewriteRules.length) {
for (const page of pages) {
for (const { matchUrl, toPath } of rewriteRules) {
const res = matchUrl(page)
if (res) {
const dest = toPath(res.params)
pageToRewrite[page] = dest
rewriteToPage[dest] = page
break
}
}
}
}
}
function rewrite(src: string, from: string, to: string) {
const urlMatch = match(from)
const res = urlMatch(src)
if (!res) return false
const toPath = compile(to)
return toPath(res.params)
return {
map: pageToRewrite,
inv: rewriteToPage
}
}
export const rewritesPlugin = (config: SiteConfig): Plugin => {

@ -6,22 +6,25 @@ import {
} from 'vite'
import path, { dirname, resolve } from 'path'
import { isMatch } from 'micromatch'
import glob from 'fast-glob'
const loaderMatch = /\.data\.(j|t)s$/
let server: ViteDevServer
interface LoaderModule {
watch: string[] | string | undefined
load: () => any
export interface LoaderModule {
watch?: string[] | string
load: (watchedFiles: string[]) => any
}
interface CachedLoaderModule {
pattern: string[] | undefined
loader: () => any
/**
* Helper for defining loaders with type inference
*/
export function defineLoader(loader: LoaderModule) {
return loader
}
const idToLoaderModulesMap: Record<string, CachedLoaderModule | undefined> =
const idToLoaderModulesMap: Record<string, LoaderModule | undefined> =
Object.create(null)
const depToLoaderModuleIdMap: Record<string, string> = Object.create(null)
@ -59,12 +62,12 @@ export const staticDataPlugin: Plugin = {
}
const base = dirname(id)
let pattern: string[] | undefined
let loader: () => any
let watch: LoaderModule['watch']
let load: LoaderModule['load']
const existing = idToLoaderModulesMap[id]
if (existing) {
;({ pattern, loader } = existing)
;({ watch, load } = existing)
} else {
// use vite's load config util as a away to load Node.js file with
// TS & native ESM support
@ -78,26 +81,34 @@ export const staticDataPlugin: Plugin = {
}
const loaderModule = res?.config as LoaderModule
pattern =
watch =
typeof loaderModule.watch === 'string'
? [loaderModule.watch]
: loaderModule.watch
if (pattern) {
pattern = pattern.map((p) => {
if (watch) {
watch = watch.map((p) => {
return p.startsWith('.')
? normalizePath(resolve(base, p))
: normalizePath(p)
})
}
loader = loaderModule.load
load = loaderModule.load
}
// load the data
const data = await loader()
let watchedFiles
if (watch) {
watchedFiles = (
await glob(watch, {
ignore: ['**/node_modules/**', '**/dist/**']
})
).sort()
}
const data = await load(watchedFiles || [])
// record loader module for HMR
if (server) {
idToLoaderModulesMap[id] = { pattern, loader }
idToLoaderModulesMap[id] = { watch, load }
}
const result = `export const data = JSON.parse(${JSON.stringify(
@ -112,9 +123,11 @@ export const staticDataPlugin: Plugin = {
transform(_code, id) {
if (server && loaderMatch.test(id)) {
// register this module as a glob importer
const { pattern } = idToLoaderModulesMap[id]!
if (pattern) {
;(server as any)._importGlobMap.set(id, [pattern])
const { watch } = idToLoaderModulesMap[id]!
if (watch) {
;(server as any)._importGlobMap.set(id, [
Array.isArray(watch) ? watch : [watch]
])
}
}
return null
@ -132,8 +145,8 @@ export const staticDataPlugin: Plugin = {
}
for (const id in idToLoaderModulesMap) {
const { pattern } = idToLoaderModulesMap[id]!
if (pattern && isMatch(file, pattern)) {
const { watch } = idToLoaderModulesMap[id]!
if (watch && isMatch(file, watch)) {
ctx.modules.push(server.moduleGraph.getModuleById(id)!)
}
}

@ -27,7 +27,8 @@ export const notFoundPageData: PageData = {
description: 'Not Found',
headers: [],
frontmatter: { sidebar: false, layout: 'page' },
lastUpdated: 0
lastUpdated: 0,
isNotFound: true
}
export function isActive(

@ -1,15 +1,11 @@
<% if (useTs) { %>import { defineConfig } from 'vitepress'
import { defineConfig } from 'vitepress'
// https://vitepress.vuejs.org/config/app-config
export default defineConfig(<% } else { %>/**
* @type {import('vitepress').UserConfig}
* https://vitepress.vuejs.org/config/app-config
*/
const config = <% } %>{
// https://vitepress.vuejs.org/reference/site-config
export default defineConfig({
title: <%= title %>,
description: <%= description %><% if (defaultTheme) { %>,
themeConfig: {
// https://vitepress.vuejs.org/config/default-theme-config
// https://vitepress.vuejs.org/reference/default-theme-config
nav: [
{ text: 'Home', link: '/' },
{ text: 'Examples', link: '/markdown-examples' }
@ -29,6 +25,4 @@ const config = <% } %>{
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
]
}<% } %>
}<% if (useTs) { %>)<% } else { %>
export default config<% } %>
})

@ -1,28 +0,0 @@
import { defineConfig } from 'vitepress'
// https://vitepress.vuejs.org/config/app-config
export default defineConfig({
title: <%= title %>,
description: <%= description %><% if (defaultTheme) { %>,
themeConfig: {
// https://vitepress.vuejs.org/config/default-theme-config
nav: [
{ text: 'Home', link: '/' },
{ text: 'Examples', link: '/markdown-examples' }
],
sidebar: [
{
text: 'Examples',
items: [
{ text: 'Markdown Examples', link: '/markdown-examples' },
{ text: 'Runtime API Examples', link: '/api-examples' }
]
}
],
socialLinks: [
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
]
}<% } %>
})

@ -1,7 +1,7 @@
<script setup<%= useTs ? ' lang="ts"' : '' %>>
import { useData } from 'vitepress'
// https://vitepress.vuejs.org/api/
// https://vitepress.vuejs.org/reference/runtime-api#usedata
const { site, frontmatter } = useData()
</script>

@ -1,10 +1,11 @@
// https://vitepress.vuejs.org/guide/custom-theme
<% if (!defaultTheme) { %>import Layout from './Layout.vue'
import './style.css'
export default {
Layout,
enhanceApp({ app, router, siteData }) {
// TODO link to app level customizatin
// ...
}
}
<% } else { %>import { h } from 'vue'
@ -15,10 +16,10 @@ export default {
...Theme,
Layout: () => {
return h(Theme.Layout, null, {
// TODO link to layout slots
// https://vitepress.vuejs.org/guide/extending-default-theme#layout-slots
})
},
enhanceApp({ app, router, siteData }) {
// TODO link to app level customizatin
// ...
}
}<% } %>

@ -1,4 +1,9 @@
<% if (defaultTheme) { %>/**
* Customize default theme styling by overriding CSS variables:
* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
*/
/**
* Colors
* -------------------------------------------------------------------------- */

@ -52,4 +52,4 @@ const { site, theme, page, frontmatter } = useData()
## More
Check out the documentation for the [full list of runtime APIs](https://vitepress.vuejs.org/api/).
Check out the documentation for the [full list of runtime APIs](https://vitepress.vuejs.org/reference/runtime-api#usedata).

@ -1,4 +1,5 @@
<% if (defaultTheme) { %>---
# https://vitepress.vuejs.org/reference/default-theme-home-page
layout: home
hero:

@ -98,6 +98,13 @@ export namespace DefaultTheme {
*/
returnToTopLabel?: string
/**
* Set custom `aria-label` for language menu button.
*
* @default 'Change language'
*/
langMenuLabel?: string
/**
* The algolia options. Leave it undefined to disable the search feature.
*/

1
types/shared.d.ts vendored

@ -12,6 +12,7 @@ export interface PageData {
headers: Header[]
frontmatter: Record<string, any>
params?: Record<string, any>
isNotFound?: boolean
lastUpdated?: number
}

Loading…
Cancel
Save