mirror of https://github.com/vuejs/vitepress
commit
d8cd3dc4e7
@ -0,0 +1,16 @@
|
|||||||
|
### Description
|
||||||
|
|
||||||
|
<!-- Please insert your description here and provide info about the "what" this PR is solving. -->
|
||||||
|
|
||||||
|
### Linked Issues
|
||||||
|
|
||||||
|
<!-- e.g. fixes #123 -->
|
||||||
|
|
||||||
|
### Additional Context
|
||||||
|
|
||||||
|
<!-- Is there anything you would like the reviewers to focus on? -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> The author of this PR can publish a _preview release_ by commenting `/publish` below.
|
@ -0,0 +1,18 @@
|
|||||||
|
name: Add continuous release label
|
||||||
|
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
label:
|
||||||
|
if: ${{ github.event.issue.pull_request && (github.event.comment.user.id == github.event.issue.user.id || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR') && startsWith(github.event.comment.body, '/publish') }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- run: gh issue edit ${{ github.event.issue.number }} --add-label cr-tracked --repo ${{ github.repository }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.CR_PAT }}
|
@ -0,0 +1,49 @@
|
|||||||
|
name: CR
|
||||||
|
|
||||||
|
env:
|
||||||
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
types: [opened, synchronize, labeled, ready_for_review]
|
||||||
|
paths-ignore:
|
||||||
|
- '.github/**'
|
||||||
|
- '!.github/workflows/cr.yml'
|
||||||
|
- '__tests__/**'
|
||||||
|
- 'art/**'
|
||||||
|
- 'docs/**'
|
||||||
|
- '*.md'
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths-ignore:
|
||||||
|
- '.github/**'
|
||||||
|
- '!.github/workflows/cr.yml'
|
||||||
|
- '__tests__/**'
|
||||||
|
- 'art/**'
|
||||||
|
- 'docs/**'
|
||||||
|
- '*.md'
|
||||||
|
tags-ignore:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.number }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
if: ${{ ((github.event_name == 'pull_request' && !github.event.pull_request.draft && contains(github.event.pull_request.labels.*.name, 'cr-tracked') && !contains(github.event.pull_request.labels.*.name, 'spam') && !contains(github.event.pull_request.labels.*.name, 'invalid')) || (github.event_name == 'push' && !startsWith(github.event.head_commit.message, 'release:'))) && github.repository == 'vuejs/vitepress' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: pnpm/action-setup@v3
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: pnpm
|
||||||
|
- run: pnpm install
|
||||||
|
- run: pnpm build
|
||||||
|
- run: npx pkg-pr-new publish --compact --no-template --pnpm
|
@ -0,0 +1,18 @@
|
|||||||
|
name: Close stale issues and PRs
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v9
|
||||||
|
with:
|
||||||
|
days-before-stale: 30
|
||||||
|
days-before-close: -1
|
||||||
|
stale-issue-label: stale
|
||||||
|
stale-pr-label: stale
|
||||||
|
# action has a bug that keeps removing the stale label from stale PRs
|
||||||
|
# it's thinking that the PR is updated when the stale label is added
|
||||||
|
remove-pr-stale-when-updated: false
|
||||||
|
operations-per-run: 1000
|
@ -1,2 +1,2 @@
|
|||||||
shell-emulator=true
|
shell-emulator=true
|
||||||
resolution-mode=highest
|
auto-install-peers=false
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,83 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useLocalStorage } from '@vueuse/core'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
options: string[]
|
||||||
|
defaultOption: string
|
||||||
|
screenMenu?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// reactivity isn't needed for props here
|
||||||
|
|
||||||
|
const key = removeSpaces(`api-preference-${props.options.join('-')}`)
|
||||||
|
const name = key + (props.screenMenu ? '-screen-menu' : '')
|
||||||
|
|
||||||
|
const selected = useLocalStorage(key, () => props.defaultOption)
|
||||||
|
|
||||||
|
const optionsWithKeys = props.options.map((option) => ({
|
||||||
|
key: name + '-' + removeSpaces(option),
|
||||||
|
value: option
|
||||||
|
}))
|
||||||
|
|
||||||
|
function removeSpaces(str: string) {
|
||||||
|
return str.replace(/\s/g, '_')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="VPApiPreference" :class="{ 'screen-menu': screenMenu }">
|
||||||
|
<template v-for="option in optionsWithKeys" :key="option">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
:id="option.key"
|
||||||
|
:name
|
||||||
|
:value="option.value"
|
||||||
|
v-model="selected"
|
||||||
|
/>
|
||||||
|
<label :for="option.key">{{ option.value }}</label>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPApiPreference {
|
||||||
|
display: flex;
|
||||||
|
margin: 12px 0;
|
||||||
|
border: 1px solid var(--vp-c-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPApiPreference:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPApiPreference:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPApiPreference.screen-menu {
|
||||||
|
margin: 12px 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPApiPreference input[type='radio'] {
|
||||||
|
pointer-events: none;
|
||||||
|
position: fixed;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPApiPreference label {
|
||||||
|
flex: 1;
|
||||||
|
margin: 2px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPApiPreference input[type='radio']:checked + label {
|
||||||
|
background-color: var(--vp-c-default-soft);
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,50 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useRoute } from 'vitepress'
|
||||||
|
import VPNavBarMenuGroup from 'vitepress/dist/client/theme-default/components/VPNavBarMenuGroup.vue'
|
||||||
|
import VPNavScreenMenuGroup from 'vitepress/dist/client/theme-default/components/VPNavScreenMenuGroup.vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
versions: { text: string; link: string }[]
|
||||||
|
screenMenu?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const sortedVersions = computed(() => {
|
||||||
|
return [...props.versions].sort(
|
||||||
|
(a, b) => b.link.split('/').length - a.link.split('/').length
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentVersion = computed(() => {
|
||||||
|
return (
|
||||||
|
sortedVersions.value.find((version) => route.path.startsWith(version.link))
|
||||||
|
?.text || 'Versions'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VPNavBarMenuGroup
|
||||||
|
v-if="!screenMenu"
|
||||||
|
:item="{ text: currentVersion, items: versions }"
|
||||||
|
class="VPNavVersion"
|
||||||
|
/>
|
||||||
|
<VPNavScreenMenuGroup
|
||||||
|
v-else
|
||||||
|
:text="currentVersion"
|
||||||
|
:items="versions"
|
||||||
|
class="VPNavVersion"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPNavVersion :deep(button .text) {
|
||||||
|
color: var(--vp-c-text-1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPNavVersion:hover :deep(button .text) {
|
||||||
|
color: var(--vp-c-text-2) !important;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,12 @@
|
|||||||
|
import type { Theme } from 'vitepress'
|
||||||
|
import DefaultTheme from 'vitepress/theme'
|
||||||
|
import ApiPreference from './components/ApiPreference.vue'
|
||||||
|
import NavVersion from './components/NavVersion.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
extends: DefaultTheme,
|
||||||
|
enhanceApp({ app }) {
|
||||||
|
app.component('ApiPreference', ApiPreference)
|
||||||
|
app.component('NavVersion', NavVersion)
|
||||||
|
}
|
||||||
|
} satisfies Theme
|
@ -0,0 +1,16 @@
|
|||||||
|
import fs from 'node:fs'
|
||||||
|
import { defineLoader } from 'vitepress'
|
||||||
|
|
||||||
|
type Data = Record<string, boolean>[]
|
||||||
|
export declare const data: Data
|
||||||
|
|
||||||
|
export default defineLoader({
|
||||||
|
watch: ['./data/*'],
|
||||||
|
async load(files: string[]): Promise<Data> {
|
||||||
|
const data: Data = []
|
||||||
|
for (const file of files.sort().filter((file) => file.endsWith('.json'))) {
|
||||||
|
data.push(JSON.parse(fs.readFileSync(file, 'utf-8')))
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
})
|
@ -1,20 +0,0 @@
|
|||||||
import fs from 'fs'
|
|
||||||
import { defineLoader } from 'vitepress'
|
|
||||||
|
|
||||||
type Data = Record<string, boolean>[]
|
|
||||||
export declare const data: Data
|
|
||||||
|
|
||||||
export default defineLoader({
|
|
||||||
watch: ['./data/*'],
|
|
||||||
async load(files: string[]): Promise<Data> {
|
|
||||||
const foo = fs.readFileSync(
|
|
||||||
files.find((f) => f.endsWith('foo.json'))!,
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
const bar = fs.readFileSync(
|
|
||||||
files.find((f) => f.endsWith('bar.json'))!,
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
return [JSON.parse(foo), JSON.parse(bar)]
|
|
||||||
}
|
|
||||||
})
|
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"a": true
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"b": true
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
{ "bar": true }
|
|
@ -1 +0,0 @@
|
|||||||
{ "foo": true }
|
|
@ -1,8 +1,14 @@
|
|||||||
export default {
|
import { defineRoutes } from 'vitepress'
|
||||||
async paths() {
|
import paths from './paths'
|
||||||
return [
|
|
||||||
{ params: { id: 'foo' }, content: `# Foo` },
|
export default defineRoutes({
|
||||||
{ params: { id: 'bar' }, content: `# Bar` }
|
async paths(watchedFiles: string[]) {
|
||||||
]
|
// console.log('watchedFiles', watchedFiles)
|
||||||
|
return paths
|
||||||
|
},
|
||||||
|
watch: ['**/data-loading/**/*.json'],
|
||||||
|
async transformPageData(pageData) {
|
||||||
|
// console.log('transformPageData', pageData.filePath)
|
||||||
|
pageData.title += ' - transformed'
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
export default [
|
||||||
|
{ params: { id: 'foo' }, content: `# Foo` },
|
||||||
|
{ params: { id: 'bar' }, content: `# Bar` }
|
||||||
|
]
|
@ -0,0 +1 @@
|
|||||||
|
# Local search config excluded
|
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
search: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Local search frontmatter excluded
|
@ -0,0 +1 @@
|
|||||||
|
# Local search included
|
@ -0,0 +1,31 @@
|
|||||||
|
describe('local search', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await goto('/')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('exclude content from search results', async () => {
|
||||||
|
await page.locator('#local-search button').click()
|
||||||
|
|
||||||
|
const input = await page.waitForSelector('input#localsearch-input')
|
||||||
|
await input.type('local')
|
||||||
|
|
||||||
|
await page.waitForSelector('ul#localsearch-list', { state: 'visible' })
|
||||||
|
|
||||||
|
const searchResults = page.locator('#localsearch-list')
|
||||||
|
expect(await searchResults.locator('li[role=option]').count()).toBe(1)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await searchResults.filter({ hasText: 'Local search included' }).count()
|
||||||
|
).toBe(1)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await searchResults.filter({ hasText: 'Local search excluded' }).count()
|
||||||
|
).toBe(0)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await searchResults
|
||||||
|
.filter({ hasText: 'Local search frontmatter excluded' })
|
||||||
|
.count()
|
||||||
|
).toBe(0)
|
||||||
|
})
|
||||||
|
})
|
@ -1,7 +1,11 @@
|
|||||||
# Foo
|
# Foo
|
||||||
|
|
||||||
|
This is before region
|
||||||
|
|
||||||
<!-- #region snippet -->
|
<!-- #region snippet -->
|
||||||
## Region
|
## Region
|
||||||
|
|
||||||
this is region
|
This is a region
|
||||||
<!-- #endregion snippet -->
|
<!-- #endregion snippet -->
|
||||||
|
|
||||||
|
This is after region
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
# header 1
|
||||||
|
|
||||||
|
header 1 content
|
||||||
|
|
||||||
|
## header 1.1
|
||||||
|
|
||||||
|
header 1.1 content
|
||||||
|
|
||||||
|
### header 1.1.1
|
||||||
|
|
||||||
|
header 1.1.1 content
|
||||||
|
|
||||||
|
### header 1.1.2
|
||||||
|
|
||||||
|
header 1.1.2 content
|
||||||
|
|
||||||
|
## header 1.2
|
||||||
|
|
||||||
|
header 1.2 content
|
||||||
|
|
||||||
|
### header 1.2.1
|
||||||
|
|
||||||
|
header 1.2.1 content
|
||||||
|
|
||||||
|
### header 1.2.2
|
||||||
|
|
||||||
|
header 1.2.2 content
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Nested Include
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--@include: ./foo.md-->
|
||||||
|
|
||||||
|
### After Foo
|
||||||
|
|
||||||
|
<!--@include: ./subfolder/inside-subfolder.md-->
|
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
<!-- #region range-region -->
|
||||||
|
|
||||||
|
## Range Region Line 1
|
||||||
|
|
||||||
|
## Range Region Line 2
|
||||||
|
|
||||||
|
## Range Region Line 3
|
||||||
|
<!-- #endregion range-region -->
|
||||||
|
|
||||||
|
<!-- #region snippet -->
|
||||||
|
## Region Snippet
|
||||||
|
<!-- #endregion snippet -->
|
@ -0,0 +1,3 @@
|
|||||||
|
# Inside sub folder
|
||||||
|
|
||||||
|
<!--@include: ./subsub/subsub.md-->
|
@ -0,0 +1,3 @@
|
|||||||
|
## Sub sub
|
||||||
|
|
||||||
|
<!--@include: ./subsubsub/subsubsub.md-->
|
@ -0,0 +1 @@
|
|||||||
|
### Sub sub sub
|
@ -1,98 +1,72 @@
|
|||||||
import { chromium, type Browser, type Page } from 'playwright-chromium'
|
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
import path from 'path'
|
|
||||||
import fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
import {
|
|
||||||
scaffold,
|
|
||||||
build,
|
|
||||||
createServer,
|
|
||||||
serve,
|
|
||||||
ScaffoldThemeType,
|
|
||||||
type ScaffoldOptions
|
|
||||||
} from 'vitepress'
|
|
||||||
import type { ViteDevServer } from 'vite'
|
|
||||||
import type { Server } from 'net'
|
|
||||||
import getPort from 'get-port'
|
import getPort from 'get-port'
|
||||||
|
import { nanoid } from 'nanoid'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
import { chromium } from 'playwright-chromium'
|
||||||
|
import { createServer, scaffold, ScaffoldThemeType } from 'vitepress'
|
||||||
|
|
||||||
let browser: Browser
|
const tempDir = fileURLToPath(new URL('./.temp', import.meta.url))
|
||||||
let page: Page
|
const getTempRoot = () => path.join(tempDir, nanoid())
|
||||||
|
|
||||||
beforeAll(async () => {
|
const browser = await chromium.launch({
|
||||||
browser = await chromium.connect(process.env['WS_ENDPOINT']!)
|
headless: !process.env.DEBUG,
|
||||||
page = await browser.newPage()
|
args: process.env.CI
|
||||||
|
? ['--no-sandbox', '--disable-setuid-sandbox']
|
||||||
|
: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const page = await browser.newPage()
|
||||||
|
|
||||||
|
const themes = [
|
||||||
|
ScaffoldThemeType.Default,
|
||||||
|
ScaffoldThemeType.DefaultCustom,
|
||||||
|
ScaffoldThemeType.Custom
|
||||||
|
]
|
||||||
|
const usingTs = [false, true]
|
||||||
|
const variations = themes.flatMap((theme) =>
|
||||||
|
usingTs.map(
|
||||||
|
(useTs) => [`${theme}${useTs ? ' + ts' : ''}`, { theme, useTs }] as const
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await page.close()
|
await page.close()
|
||||||
await browser.close()
|
await browser.close()
|
||||||
|
await fs.remove(tempDir)
|
||||||
})
|
})
|
||||||
|
|
||||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'temp')
|
test.each(variations)('init %s', async (_, { theme, useTs }) => {
|
||||||
|
const root = getTempRoot()
|
||||||
async function testVariation(options: ScaffoldOptions) {
|
await fs.remove(root)
|
||||||
fs.removeSync(root)
|
scaffold({ root, theme, useTs, injectNpmScripts: false })
|
||||||
scaffold({
|
|
||||||
...options,
|
|
||||||
root
|
|
||||||
})
|
|
||||||
|
|
||||||
let server: ViteDevServer | Server
|
|
||||||
const port = await getPort()
|
const port = await getPort()
|
||||||
|
const server = await createServer(root, { port })
|
||||||
|
await server.listen()
|
||||||
|
|
||||||
async function goto(path: string) {
|
async function goto(path: string) {
|
||||||
await page.goto(`http://localhost:${port}${path}`)
|
await page.goto(`http://localhost:${port}${path}`)
|
||||||
await page.waitForSelector('#app div')
|
await page.waitForSelector('#app div')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env['VITE_TEST_BUILD']) {
|
|
||||||
await build(root)
|
|
||||||
server = (await serve({ root, port })).server
|
|
||||||
} else {
|
|
||||||
server = await createServer(root, { port })
|
|
||||||
await server!.listen()
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await goto('/')
|
await goto('/')
|
||||||
expect(await page.textContent('h1')).toMatch('My Awesome Project')
|
expect(await page.textContent('h1')).toMatch('My Awesome Project')
|
||||||
|
|
||||||
await page.click('a[href="/markdown-examples.html"]')
|
await page.click('a[href="/markdown-examples.html"]')
|
||||||
await page.waitForSelector('pre code')
|
await page.waitForFunction('document.querySelector("pre code")')
|
||||||
expect(await page.textContent('h1')).toMatch('Markdown Extension Examples')
|
expect(await page.textContent('h1')).toMatch('Markdown Extension Examples')
|
||||||
|
|
||||||
await goto('/')
|
await goto('/')
|
||||||
expect(await page.textContent('h1')).toMatch('My Awesome Project')
|
expect(await page.textContent('h1')).toMatch('My Awesome Project')
|
||||||
|
|
||||||
await page.click('a[href="/api-examples.html"]')
|
await page.click('a[href="/api-examples.html"]')
|
||||||
await page.waitForSelector('pre code')
|
await page.waitForFunction('document.querySelector("pre code")')
|
||||||
expect(await page.textContent('h1')).toMatch('Runtime API Examples')
|
expect(await page.textContent('h1')).toMatch('Runtime API Examples')
|
||||||
} finally {
|
|
||||||
fs.removeSync(root)
|
|
||||||
if ('ws' in server) {
|
|
||||||
await server.close()
|
|
||||||
} else {
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
|
||||||
server.close((error) => (error ? reject(error) : resolve()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const themes = [
|
// teardown
|
||||||
ScaffoldThemeType.Default,
|
} finally {
|
||||||
ScaffoldThemeType.DefaultCustom,
|
await server.close()
|
||||||
ScaffoldThemeType.Custom
|
|
||||||
]
|
|
||||||
const usingTs = [false, true]
|
|
||||||
|
|
||||||
for (const theme of themes) {
|
|
||||||
for (const useTs of usingTs) {
|
|
||||||
test(`${theme}${useTs ? ` + TypeScript` : ``}`, () =>
|
|
||||||
testVariation({
|
|
||||||
root: '.',
|
|
||||||
theme,
|
|
||||||
useTs,
|
|
||||||
injectNpmScripts: false
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import { chromium, type BrowserServer } from 'playwright-chromium'
|
|
||||||
|
|
||||||
let browserServer: BrowserServer
|
|
||||||
|
|
||||||
export async function setup() {
|
|
||||||
browserServer = await chromium.launchServer({
|
|
||||||
headless: !process.env.DEBUG,
|
|
||||||
args: process.env.CI
|
|
||||||
? ['--no-sandbox', '--disable-setuid-sandbox']
|
|
||||||
: undefined
|
|
||||||
})
|
|
||||||
process.env['WS_ENDPOINT'] = browserServer.wsEndpoint()
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function teardown() {
|
|
||||||
await browserServer.close()
|
|
||||||
}
|
|
@ -0,0 +1,72 @@
|
|||||||
|
import { ModuleGraph } from 'node/utils/moduleGraph'
|
||||||
|
|
||||||
|
describe('node/utils/moduleGraph', () => {
|
||||||
|
let graph: ModuleGraph
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
graph = new ModuleGraph()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly delete a module and its dependents', () => {
|
||||||
|
graph.add('A', ['B', 'C'])
|
||||||
|
graph.add('B', ['D'])
|
||||||
|
graph.add('C', [])
|
||||||
|
graph.add('D', [])
|
||||||
|
|
||||||
|
expect(graph.delete('D')).toEqual(new Set(['D', 'B', 'A']))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle shared dependencies correctly', () => {
|
||||||
|
graph.add('A', ['B', 'C'])
|
||||||
|
graph.add('B', ['D'])
|
||||||
|
graph.add('C', ['D']) // Shared dependency
|
||||||
|
graph.add('D', [])
|
||||||
|
|
||||||
|
expect(graph.delete('D')).toEqual(new Set(['A', 'B', 'C', 'D']))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('merges dependencies correctly', () => {
|
||||||
|
// Add module A with dependency B
|
||||||
|
graph.add('A', ['B'])
|
||||||
|
// Merge new dependency C into module A (B should remain)
|
||||||
|
graph.add('A', ['C'])
|
||||||
|
|
||||||
|
// Deleting B should remove A as well, since A depends on B.
|
||||||
|
expect(graph.delete('B')).toEqual(new Set(['B', 'A']))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles cycles gracefully', () => {
|
||||||
|
// Create a cycle: A -> B, B -> C, C -> A.
|
||||||
|
graph.add('A', ['B'])
|
||||||
|
graph.add('B', ['C'])
|
||||||
|
graph.add('C', ['A'])
|
||||||
|
|
||||||
|
// Deleting any module in the cycle should delete all modules in the cycle.
|
||||||
|
expect(graph.delete('A')).toEqual(new Set(['A', 'B', 'C']))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cleans up dependencies when deletion', () => {
|
||||||
|
// Setup A -> B relationship.
|
||||||
|
graph.add('A', ['B'])
|
||||||
|
graph.add('B', [])
|
||||||
|
|
||||||
|
// Deleting B should remove both B and A from the graph.
|
||||||
|
expect(graph.delete('B')).toEqual(new Set(['B', 'A']))
|
||||||
|
|
||||||
|
// After deletion, add modules again.
|
||||||
|
graph.add('C', [])
|
||||||
|
graph.add('A', ['C']) // Now A depends only on C.
|
||||||
|
|
||||||
|
expect(graph.delete('C')).toEqual(new Set(['C', 'A']))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles independent modules', () => {
|
||||||
|
// Modules with no dependencies.
|
||||||
|
graph.add('X', [])
|
||||||
|
graph.add('Y', [])
|
||||||
|
|
||||||
|
// Deletion of one should only remove that module.
|
||||||
|
expect(graph.delete('X')).toEqual(new Set(['X']))
|
||||||
|
expect(graph.delete('Y')).toEqual(new Set(['Y']))
|
||||||
|
})
|
||||||
|
})
|
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 4.3 KiB |
@ -1,2 +1,16 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
import module from 'node:module'
|
||||||
|
|
||||||
|
// https://github.com/vitejs/vite/blob/6c8a5a27e645a182f5b03a4ed6aa726eab85993f/packages/vite/bin/vite.js#L48-L63
|
||||||
|
try {
|
||||||
|
module.enableCompileCache?.()
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
module.flushCompileCache?.()
|
||||||
|
} catch {}
|
||||||
|
}, 10 * 1000).unref()
|
||||||
|
} catch {}
|
||||||
|
|
||||||
import('../dist/node/cli.js')
|
import('../dist/node/cli.js')
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"plugins": {
|
||||||
|
"postcss-rtlcss": {
|
||||||
|
"ltrPrefix": ":where([dir=\"ltr\"])",
|
||||||
|
"rtlPrefix": ":where([dir=\"rtl\"])"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,227 +1,148 @@
|
|||||||
import { createRequire } from 'module'
|
import {
|
||||||
import { defineConfig } from 'vitepress'
|
defineConfig,
|
||||||
|
resolveSiteDataByRoute,
|
||||||
|
type HeadConfig
|
||||||
|
} from 'vitepress'
|
||||||
|
import {
|
||||||
|
groupIconMdPlugin,
|
||||||
|
groupIconVitePlugin,
|
||||||
|
localIconLoader
|
||||||
|
} from 'vitepress-plugin-group-icons'
|
||||||
|
import llmstxt from 'vitepress-plugin-llms'
|
||||||
|
|
||||||
const require = createRequire(import.meta.url)
|
const prod = !!process.env.NETLIFY
|
||||||
const pkg = require('vitepress/package.json')
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
lang: 'en-US',
|
|
||||||
title: 'VitePress',
|
title: 'VitePress',
|
||||||
description: 'Vite & Vue powered static site generator.',
|
|
||||||
|
rewrites: {
|
||||||
|
'en/:rest*': ':rest*'
|
||||||
|
},
|
||||||
|
|
||||||
lastUpdated: true,
|
lastUpdated: true,
|
||||||
cleanUrls: true,
|
cleanUrls: true,
|
||||||
|
metaChunk: true,
|
||||||
|
|
||||||
head: [
|
markdown: {
|
||||||
['meta', { name: 'theme-color', content: '#3c8772' }],
|
math: true,
|
||||||
[
|
codeTransformers: [
|
||||||
'script',
|
// We use `[!!code` in demo to prevent transformation, here we revert it back.
|
||||||
{
|
{
|
||||||
src: 'https://cdn.usefathom.com/script.js',
|
postprocess(code) {
|
||||||
'data-site': 'AZBRSFGG',
|
return code.replace(/\[\!\!code/g, '[!code')
|
||||||
'data-spa': 'auto',
|
}
|
||||||
defer: ''
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
],
|
config(md) {
|
||||||
|
// TODO: remove when https://github.com/vuejs/vitepress/issues/4431 is fixed
|
||||||
|
const fence = md.renderer.rules.fence!
|
||||||
|
md.renderer.rules.fence = function (tokens, idx, options, env, self) {
|
||||||
|
const { localeIndex = 'root' } = env
|
||||||
|
const codeCopyButtonTitle = (() => {
|
||||||
|
switch (localeIndex) {
|
||||||
|
case 'es':
|
||||||
|
return 'Copiar código'
|
||||||
|
case 'fa':
|
||||||
|
return 'کپی کد'
|
||||||
|
case 'ko':
|
||||||
|
return '코드 복사'
|
||||||
|
case 'pt':
|
||||||
|
return 'Copiar código'
|
||||||
|
case 'ru':
|
||||||
|
return 'Скопировать код'
|
||||||
|
case 'zh':
|
||||||
|
return '复制代码'
|
||||||
|
default:
|
||||||
|
return 'Copy code'
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
return fence(tokens, idx, options, env, self).replace(
|
||||||
|
'<button title="Copy Code" class="copy"></button>',
|
||||||
|
`<button title="${codeCopyButtonTitle}" class="copy"></button>`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
md.use(groupIconMdPlugin)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
themeConfig: {
|
sitemap: {
|
||||||
nav: nav(),
|
hostname: 'https://vitepress.dev',
|
||||||
|
transformItems(items) {
|
||||||
|
return items.filter((item) => !item.url.includes('migration'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
sidebar: {
|
/* prettier-ignore */
|
||||||
'/guide/': sidebarGuide(),
|
head: [
|
||||||
'/reference/': sidebarReference()
|
['link', { rel: 'icon', type: 'image/svg+xml', href: '/vitepress-logo-mini.svg' }],
|
||||||
},
|
['link', { rel: 'icon', type: 'image/png', href: '/vitepress-logo-mini.png' }],
|
||||||
|
['meta', { name: 'theme-color', content: '#5f67ee' }],
|
||||||
|
['meta', { property: 'og:type', content: 'website' }],
|
||||||
|
['meta', { property: 'og:site_name', content: 'VitePress' }],
|
||||||
|
['meta', { property: 'og:image', content: 'https://vitepress.dev/vitepress-og.jpg' }],
|
||||||
|
['meta', { property: 'og:url', content: 'https://vitepress.dev/' }],
|
||||||
|
['script', { src: 'https://cdn.usefathom.com/script.js', 'data-site': 'AZBRSFGG', 'data-spa': 'auto', defer: '' }]
|
||||||
|
],
|
||||||
|
|
||||||
editLink: {
|
themeConfig: {
|
||||||
pattern: 'https://github.com/vuejs/vitepress/edit/main/docs/:path',
|
logo: { src: '/vitepress-logo-mini.svg', width: 24, height: 24 },
|
||||||
text: 'Edit this page on GitHub'
|
|
||||||
},
|
|
||||||
|
|
||||||
socialLinks: [
|
socialLinks: [
|
||||||
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
|
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
|
||||||
],
|
],
|
||||||
|
|
||||||
footer: {
|
|
||||||
message: 'Released under the MIT License.',
|
|
||||||
copyright: 'Copyright © 2019-present Evan You'
|
|
||||||
},
|
|
||||||
|
|
||||||
search: {
|
search: {
|
||||||
provider: 'algolia',
|
provider: 'algolia',
|
||||||
options: {
|
options: {
|
||||||
appId: '8J64VVRP8K',
|
appId: '8J64VVRP8K',
|
||||||
apiKey: 'a18e2f4cc5665f6602c5631fd868adfd',
|
apiKey: '52f578a92b88ad6abde815aae2b0ad7c',
|
||||||
indexName: 'vitepress'
|
indexName: 'vitepress'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
carbonAds: {
|
carbonAds: { code: 'CEBDT27Y', placement: 'vuejsorg' }
|
||||||
code: 'CEBDT27Y',
|
},
|
||||||
placement: 'vuejsorg'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function nav() {
|
locales: {
|
||||||
return [
|
root: { label: 'English' },
|
||||||
{ text: 'Guide', link: '/guide/what-is-vitepress', activeMatch: '/guide/' },
|
zh: { label: '简体中文' },
|
||||||
{
|
pt: { label: 'Português' },
|
||||||
text: 'Reference',
|
ru: { label: 'Русский' },
|
||||||
link: '/reference/site-config',
|
es: { label: 'Español' },
|
||||||
activeMatch: '/reference/'
|
ko: { label: '한국어' },
|
||||||
},
|
fa: { label: 'فارسی' }
|
||||||
{
|
},
|
||||||
text: pkg.version,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
text: 'Changelog',
|
|
||||||
link: 'https://github.com/vuejs/vitepress/blob/main/CHANGELOG.md'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Contributing',
|
|
||||||
link: 'https://github.com/vuejs/vitepress/blob/main/.github/contributing.md'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
function sidebarGuide() {
|
vite: {
|
||||||
return [
|
plugins: [
|
||||||
{
|
groupIconVitePlugin({
|
||||||
text: 'Introduction',
|
customIcon: {
|
||||||
collapsed: false,
|
vitepress: localIconLoader(
|
||||||
items: [
|
import.meta.url,
|
||||||
{ text: 'What is VitePress?', link: '/guide/what-is-vitepress' },
|
'../public/vitepress-logo-mini.svg'
|
||||||
{ text: 'Getting Started', link: '/guide/getting-started' },
|
),
|
||||||
{ text: 'Routing', link: '/guide/routing' },
|
firebase: 'logos:firebase'
|
||||||
{ text: 'Deploy', link: '/guide/deploy' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Writing',
|
|
||||||
collapsed: false,
|
|
||||||
items: [
|
|
||||||
{ text: 'Markdown Extensions', link: '/guide/markdown' },
|
|
||||||
{ text: 'Asset Handling', link: '/guide/asset-handling' },
|
|
||||||
{ text: 'Frontmatter', link: '/guide/frontmatter' },
|
|
||||||
{ text: 'Using Vue in Markdown', link: '/guide/using-vue' },
|
|
||||||
{ text: 'Internationalization', link: '/guide/i18n' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Customization',
|
|
||||||
collapsed: false,
|
|
||||||
items: [
|
|
||||||
{ text: 'Using a Custom Theme', link: '/guide/custom-theme' },
|
|
||||||
{
|
|
||||||
text: 'Extending the Default Theme',
|
|
||||||
link: '/guide/extending-default-theme'
|
|
||||||
},
|
|
||||||
{ text: 'Build-Time Data Loading', link: '/guide/data-loading' },
|
|
||||||
{ text: 'SSR Compatibility', link: '/guide/ssr-compat' },
|
|
||||||
{ text: 'Connecting to a CMS', link: '/guide/cms' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Experimental',
|
|
||||||
collapsed: false,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
text: 'MPA Mode',
|
|
||||||
link: '/guide/mpa-mode'
|
|
||||||
}
|
}
|
||||||
]
|
}),
|
||||||
},
|
prod &&
|
||||||
// {
|
llmstxt({
|
||||||
// text: 'Migrations',
|
workDir: 'en',
|
||||||
// collapsed: false,
|
ignoreFiles: ['index.md']
|
||||||
// items: [
|
})
|
||||||
// {
|
]
|
||||||
// text: 'Migration from VuePress',
|
},
|
||||||
// link: '/guide/migration-from-vuepress'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// text: 'Migration from VitePress 0.x',
|
|
||||||
// link: '/guide/migration-from-vitepress-0'
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
text: 'Config & API Reference',
|
|
||||||
link: '/reference/site-config'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
function sidebarReference() {
|
transformPageData: prod
|
||||||
return [
|
? (pageData, ctx) => {
|
||||||
{
|
const site = resolveSiteDataByRoute(
|
||||||
text: 'Reference',
|
ctx.siteConfig.site,
|
||||||
items: [
|
pageData.relativePath
|
||||||
{ text: 'Site Config', link: '/reference/site-config' },
|
)
|
||||||
{ text: 'Frontmatter Config', link: '/reference/frontmatter-config' },
|
const title = `${pageData.title || site.title} | ${pageData.description || site.description}`
|
||||||
{ text: 'Runtime API', link: '/reference/runtime-api' },
|
;((pageData.frontmatter.head ??= []) as HeadConfig[]).push(
|
||||||
{ text: 'CLI', link: '/reference/cli' },
|
['meta', { property: 'og:locale', content: site.lang }],
|
||||||
{
|
['meta', { property: 'og:title', content: title }]
|
||||||
text: 'Default Theme',
|
)
|
||||||
items: [
|
}
|
||||||
{
|
: undefined
|
||||||
text: 'Overview',
|
})
|
||||||
link: '/reference/default-theme-config'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Nav',
|
|
||||||
link: '/reference/default-theme-nav'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Sidebar',
|
|
||||||
link: '/reference/default-theme-sidebar'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Home Page',
|
|
||||||
link: '/reference/default-theme-home-page'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Footer',
|
|
||||||
link: '/reference/default-theme-footer'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Layout',
|
|
||||||
link: '/reference/default-theme-layout'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Badge',
|
|
||||||
link: '/reference/default-theme-badge'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Team Page',
|
|
||||||
link: '/reference/default-theme-team-page'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Prev / Next Links',
|
|
||||||
link: '/reference/default-theme-prev-next-links'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Edit Link',
|
|
||||||
link: '/reference/default-theme-edit-link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Last Updated Timestamp',
|
|
||||||
link: '/reference/default-theme-last-updated'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Search',
|
|
||||||
link: '/reference/default-theme-search'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Carbon Ads',
|
|
||||||
link: '/reference/default-theme-carbon-ads'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
import Theme from 'vitepress/theme'
|
||||||
|
import 'virtual:group-icons.css'
|
||||||
|
import './styles.css'
|
||||||
|
|
||||||
|
export default Theme
|
@ -0,0 +1,43 @@
|
|||||||
|
:root:where(:lang(fa)) {
|
||||||
|
--vp-font-family-base:
|
||||||
|
'Vazirmatn', 'Inter', ui-sans-serif, system-ui, sans-serif,
|
||||||
|
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--vp-home-hero-name-color: transparent;
|
||||||
|
--vp-home-hero-name-background: -webkit-linear-gradient(
|
||||||
|
120deg,
|
||||||
|
#bd34fe 30%,
|
||||||
|
#41d1ff
|
||||||
|
);
|
||||||
|
--vp-home-hero-image-background-image: linear-gradient(
|
||||||
|
-45deg,
|
||||||
|
#bd34fe 50%,
|
||||||
|
#47caff 50%
|
||||||
|
);
|
||||||
|
--vp-home-hero-image-filter: blur(44px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
:root {
|
||||||
|
--vp-home-hero-image-filter: blur(56px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
:root {
|
||||||
|
--vp-home-hero-image-filter: blur(68px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPHero .VPImage {
|
||||||
|
filter: drop-shadow(-2px 4px 6px rgba(0, 0, 0, 0.2));
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* used in reference/default-theme-search */
|
||||||
|
img[src='/search.png'] {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
import { createRequire } from 'module'
|
||||||
|
import { defineAdditionalConfig, type DefaultTheme } from 'vitepress'
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url)
|
||||||
|
const pkg = require('vitepress/package.json')
|
||||||
|
|
||||||
|
export default defineAdditionalConfig({
|
||||||
|
lang: 'en-US',
|
||||||
|
description: 'Vite & Vue powered static site generator.',
|
||||||
|
|
||||||
|
themeConfig: {
|
||||||
|
nav: nav(),
|
||||||
|
|
||||||
|
sidebar: {
|
||||||
|
'/guide/': { base: '/guide/', items: sidebarGuide() },
|
||||||
|
'/reference/': { base: '/reference/', items: sidebarReference() }
|
||||||
|
},
|
||||||
|
|
||||||
|
editLink: {
|
||||||
|
pattern: 'https://github.com/vuejs/vitepress/edit/main/docs/:path',
|
||||||
|
text: 'Edit this page on GitHub'
|
||||||
|
},
|
||||||
|
|
||||||
|
footer: {
|
||||||
|
message: 'Released under the MIT License.',
|
||||||
|
copyright: 'Copyright © 2019-present Evan You'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function nav(): DefaultTheme.NavItem[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: 'Guide',
|
||||||
|
link: '/guide/what-is-vitepress',
|
||||||
|
activeMatch: '/guide/'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Reference',
|
||||||
|
link: '/reference/site-config',
|
||||||
|
activeMatch: '/reference/'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: pkg.version,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Changelog',
|
||||||
|
link: 'https://github.com/vuejs/vitepress/blob/main/CHANGELOG.md'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Contributing',
|
||||||
|
link: 'https://github.com/vuejs/vitepress/blob/main/.github/contributing.md'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function sidebarGuide(): DefaultTheme.SidebarItem[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: 'Introduction',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: 'What is VitePress?', link: 'what-is-vitepress' },
|
||||||
|
{ text: 'Getting Started', link: 'getting-started' },
|
||||||
|
{ text: 'Routing', link: 'routing' },
|
||||||
|
{ text: 'Deploy', link: 'deploy' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Writing',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: 'Markdown Extensions', link: 'markdown' },
|
||||||
|
{ text: 'Asset Handling', link: 'asset-handling' },
|
||||||
|
{ text: 'Frontmatter', link: 'frontmatter' },
|
||||||
|
{ text: 'Using Vue in Markdown', link: 'using-vue' },
|
||||||
|
{ text: 'Internationalization', link: 'i18n' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Customization',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: 'Using a Custom Theme', link: 'custom-theme' },
|
||||||
|
{
|
||||||
|
text: 'Extending the Default Theme',
|
||||||
|
link: 'extending-default-theme'
|
||||||
|
},
|
||||||
|
{ text: 'Build-Time Data Loading', link: 'data-loading' },
|
||||||
|
{ text: 'SSR Compatibility', link: 'ssr-compat' },
|
||||||
|
{ text: 'Connecting to a CMS', link: 'cms' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Experimental',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: 'MPA Mode', link: 'mpa-mode' },
|
||||||
|
{ text: 'Sitemap Generation', link: 'sitemap-generation' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ text: 'Config & API Reference', base: '/reference/', link: 'site-config' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function sidebarReference(): DefaultTheme.SidebarItem[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: 'Reference',
|
||||||
|
items: [
|
||||||
|
{ text: 'Site Config', link: 'site-config' },
|
||||||
|
{ text: 'Frontmatter Config', link: 'frontmatter-config' },
|
||||||
|
{ text: 'Runtime API', link: 'runtime-api' },
|
||||||
|
{ text: 'CLI', link: 'cli' },
|
||||||
|
{
|
||||||
|
text: 'Default Theme',
|
||||||
|
base: '/reference/default-theme-',
|
||||||
|
items: [
|
||||||
|
{ text: 'Overview', link: 'config' },
|
||||||
|
{ text: 'Nav', link: 'nav' },
|
||||||
|
{ text: 'Sidebar', link: 'sidebar' },
|
||||||
|
{ text: 'Home Page', link: 'home-page' },
|
||||||
|
{ text: 'Footer', link: 'footer' },
|
||||||
|
{ text: 'Layout', link: 'layout' },
|
||||||
|
{ text: 'Badge', link: 'badge' },
|
||||||
|
{ text: 'Team Page', link: 'team-page' },
|
||||||
|
{ text: 'Prev / Next Links', link: 'prev-next-links' },
|
||||||
|
{ text: 'Edit Link', link: 'edit-link' },
|
||||||
|
{ text: 'Last Updated Timestamp', link: 'last-updated' },
|
||||||
|
{ text: 'Search', link: 'search' },
|
||||||
|
{ text: 'Carbon Ads', link: 'carbon-ads' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,339 @@
|
|||||||
|
---
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# Deploy Your VitePress Site
|
||||||
|
|
||||||
|
The following guides are based on some shared assumptions:
|
||||||
|
|
||||||
|
- The VitePress site is inside the `docs` directory of your project.
|
||||||
|
- You are using the default build output directory (`.vitepress/dist`).
|
||||||
|
- VitePress is installed as a local dependency in your project, and you have set up the following scripts in your `package.json`:
|
||||||
|
|
||||||
|
```json [package.json]
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"docs:build": "vitepress build docs",
|
||||||
|
"docs:preview": "vitepress preview docs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build and Test Locally
|
||||||
|
|
||||||
|
1. Run this command to build the docs:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm run docs:build
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Once built, preview it locally by running:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm run docs:preview
|
||||||
|
```
|
||||||
|
|
||||||
|
The `preview` command will boot up a local static web server that will serve the output directory `.vitepress/dist` at `http://localhost:4173`. You can use this to make sure everything looks good before pushing to production.
|
||||||
|
|
||||||
|
3. You can configure the port of the server by passing `--port` as an argument.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"docs:preview": "vitepress preview docs --port 8080"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now the `docs:preview` method will launch the server at `http://localhost:8080`.
|
||||||
|
|
||||||
|
## Setting a Public Base Path
|
||||||
|
|
||||||
|
By default, we assume the site is going to be deployed at the root path of a domain (`/`). If your site is going to be served at a sub-path, e.g. `https://mywebsite.com/blog/`, then you need to set the [`base`](../reference/site-config#base) option to `'/blog/'` in the VitePress config.
|
||||||
|
|
||||||
|
**Example:** If you're using Github (or GitLab) Pages and deploying to `user.github.io/repo/`, then set your `base` to `/repo/`.
|
||||||
|
|
||||||
|
## HTTP Cache Headers
|
||||||
|
|
||||||
|
If you have control over the HTTP headers on your production server, you can configure `cache-control` headers to achieve better performance on repeated visits.
|
||||||
|
|
||||||
|
The production build uses hashed file names for static assets (JavaScript, CSS and other imported assets not in `public`). If you inspect the production preview using your browser devtools' network tab, you will see files like `app.4f283b18.js`.
|
||||||
|
|
||||||
|
This `4f283b18` hash is generated from the content of this file. The same hashed URL is guaranteed to serve the same file content - if the contents change, the URLs change too. This means you can safely use the strongest cache headers for these files. All such files will be placed under `assets/` in the output directory, so you can configure the following header for them:
|
||||||
|
|
||||||
|
```
|
||||||
|
Cache-Control: max-age=31536000,immutable
|
||||||
|
```
|
||||||
|
|
||||||
|
::: details Example Netlify `_headers` file
|
||||||
|
|
||||||
|
```
|
||||||
|
/assets/*
|
||||||
|
cache-control: max-age=31536000
|
||||||
|
cache-control: immutable
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: the `_headers` file should be placed in the [public directory](./asset-handling#the-public-directory) - in our case, `docs/public/_headers` - so that it is copied verbatim to the output directory.
|
||||||
|
|
||||||
|
[Netlify custom headers documentation](https://docs.netlify.com/routing/headers/)
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: details Example Vercel config in `vercel.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"source": "/assets/(.*)",
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "Cache-Control",
|
||||||
|
"value": "max-age=31536000, immutable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: the `vercel.json` file should be placed at the root of your **repository**.
|
||||||
|
|
||||||
|
[Vercel documentation on headers config](https://vercel.com/docs/concepts/projects/project-configuration#headers)
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Platform Guides
|
||||||
|
|
||||||
|
### Netlify / Vercel / Cloudflare Pages / AWS Amplify / Render
|
||||||
|
|
||||||
|
Set up a new project and change these settings using your dashboard:
|
||||||
|
|
||||||
|
- **Build Command:** `npm run docs:build`
|
||||||
|
- **Output Directory:** `docs/.vitepress/dist`
|
||||||
|
- **Node Version:** `20` (or above)
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
Don't enable options like _Auto Minify_ for HTML code. It will remove comments from output which have meaning to Vue. You may see hydration mismatch errors if they get removed.
|
||||||
|
:::
|
||||||
|
|
||||||
|
### GitHub Pages
|
||||||
|
|
||||||
|
1. Create a file named `deploy.yml` inside `.github/workflows` directory of your project with some content like this:
|
||||||
|
|
||||||
|
```yaml [.github/workflows/deploy.yml]
|
||||||
|
# Sample workflow for building and deploying a VitePress site to GitHub Pages
|
||||||
|
#
|
||||||
|
name: Deploy VitePress site to Pages
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Runs on pushes targeting the `main` branch. Change this to `master` if you're
|
||||||
|
# using the `master` branch as the default branch.
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||||
|
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||||
|
concurrency:
|
||||||
|
group: pages
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Build job
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Not needed if lastUpdated is not enabled
|
||||||
|
# - uses: pnpm/action-setup@v3 # Uncomment this block if you're using pnpm
|
||||||
|
# with:
|
||||||
|
# version: 9 # Not needed if you've set "packageManager" in package.json
|
||||||
|
# - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: npm # or pnpm / yarn
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v4
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci # or pnpm install / yarn install / bun install
|
||||||
|
- name: Build with VitePress
|
||||||
|
run: npm run docs:build # or pnpm docs:build / yarn docs:build / bun run docs:build
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: docs/.vitepress/dist
|
||||||
|
|
||||||
|
# Deployment job
|
||||||
|
deploy:
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Deploy
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
|
```
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
Make sure the `base` option in your VitePress is properly configured. See [Setting a Public Base Path](#setting-a-public-base-path) for more details.
|
||||||
|
:::
|
||||||
|
|
||||||
|
2. In your repository's settings under "Pages" menu item, select "GitHub Actions" in "Build and deployment > Source".
|
||||||
|
|
||||||
|
3. Push your changes to the `main` branch and wait for the GitHub Actions workflow to complete. You should see your site deployed to `https://<username>.github.io/[repository]/` or `https://<custom-domain>/` depending on your settings. Your site will automatically be deployed on every push to the `main` branch.
|
||||||
|
|
||||||
|
### GitLab Pages
|
||||||
|
|
||||||
|
1. Set `outDir` in VitePress config to `../public`. Configure `base` option to `'/<repository>/'` if you want to deploy to `https://<username>.gitlab.io/<repository>/`. You don't need `base` if you're deploying to custom domain, user or group pages, or have "Use unique domain" setting enabled in GitLab.
|
||||||
|
|
||||||
|
2. Create a file named `.gitlab-ci.yml` in the root of your project with the content below. This will build and deploy your site whenever you make changes to your content:
|
||||||
|
|
||||||
|
```yaml [.gitlab-ci.yml]
|
||||||
|
image: node:18
|
||||||
|
pages:
|
||||||
|
cache:
|
||||||
|
paths:
|
||||||
|
- node_modules/
|
||||||
|
script:
|
||||||
|
# - apk add git # Uncomment this if you're using small docker images like alpine and have lastUpdated enabled
|
||||||
|
- npm install
|
||||||
|
- npm run docs:build
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- public
|
||||||
|
only:
|
||||||
|
- main
|
||||||
|
```
|
||||||
|
|
||||||
|
### Azure Static Web Apps
|
||||||
|
|
||||||
|
1. Follow the [official documentation](https://docs.microsoft.com/en-us/azure/static-web-apps/build-configuration).
|
||||||
|
|
||||||
|
2. Set these values in your configuration file (and remove the ones you don't require, like `api_location`):
|
||||||
|
|
||||||
|
- **`app_location`**: `/`
|
||||||
|
- **`output_location`**: `docs/.vitepress/dist`
|
||||||
|
- **`app_build_command`**: `npm run docs:build`
|
||||||
|
|
||||||
|
### Firebase
|
||||||
|
|
||||||
|
1. Create `firebase.json` and `.firebaserc` at the root of your project:
|
||||||
|
|
||||||
|
`firebase.json`:
|
||||||
|
|
||||||
|
```json [firebase.json]
|
||||||
|
{
|
||||||
|
"hosting": {
|
||||||
|
"public": "docs/.vitepress/dist",
|
||||||
|
"ignore": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`.firebaserc`:
|
||||||
|
|
||||||
|
```json [.firebaserc]
|
||||||
|
{
|
||||||
|
"projects": {
|
||||||
|
"default": "<YOUR_FIREBASE_ID>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. After running `npm run docs:build`, run this command to deploy:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
firebase deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Surge
|
||||||
|
|
||||||
|
1. After running `npm run docs:build`, run this command to deploy:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx surge docs/.vitepress/dist
|
||||||
|
```
|
||||||
|
|
||||||
|
### Heroku
|
||||||
|
|
||||||
|
1. Follow documentation and guide given in [`heroku-buildpack-static`](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-static).
|
||||||
|
|
||||||
|
2. Create a file called `static.json` in the root of your project with the below content:
|
||||||
|
|
||||||
|
```json [static.json]
|
||||||
|
{
|
||||||
|
"root": "docs/.vitepress/dist"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Edgio
|
||||||
|
|
||||||
|
Refer [Creating and Deploying a VitePress App To Edgio](https://docs.edg.io/guides/vitepress).
|
||||||
|
|
||||||
|
### Kinsta Static Site Hosting
|
||||||
|
|
||||||
|
You can deploy your VitePress website on [Kinsta](https://kinsta.com/static-site-hosting/) by following these [instructions](https://kinsta.com/docs/vitepress-static-site-example/).
|
||||||
|
|
||||||
|
### Stormkit
|
||||||
|
|
||||||
|
You can deploy your VitePress project to [Stormkit](https://www.stormkit.io) by following these [instructions](https://stormkit.io/blog/how-to-deploy-vitepress).
|
||||||
|
|
||||||
|
### Nginx
|
||||||
|
|
||||||
|
Here is a example of an Nginx server block configuration. This setup includes gzip compression for common text-based assets, rules for serving your VitePress site's static files with proper caching headers as well as handling `cleanUrls: true`.
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||||
|
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# content location
|
||||||
|
root /app;
|
||||||
|
|
||||||
|
# exact matches -> reverse clean urls -> folders -> not found
|
||||||
|
try_files $uri $uri.html $uri/ =404;
|
||||||
|
|
||||||
|
# non existent pages
|
||||||
|
error_page 404 /404.html;
|
||||||
|
|
||||||
|
# a folder without index.html raises 403 in this setup
|
||||||
|
error_page 403 /404.html;
|
||||||
|
|
||||||
|
# adjust caching headers
|
||||||
|
# files in the assets folder have hashes filenames
|
||||||
|
location ~* ^/assets/ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This configuration assumes that your built VitePress site is located in the `/app` directory on your server. Adjust the `root` directive accordingly if your site's files are located elsewhere.
|
||||||
|
|
||||||
|
::: warning Do not default to index.html
|
||||||
|
The try_files resolution must not default to index.html like in other Vue applications. This would result in an invalid page state.
|
||||||
|
:::
|
||||||
|
|
||||||
|
Further information can be found in the [official nginx documentation](https://nginx.org/en/docs/), in these issues [#2837](https://github.com/vuejs/vitepress/discussions/2837), [#3235](https://github.com/vuejs/vitepress/issues/3235) as well as in this [blog post](https://blog.mehdi.cc/articles/vitepress-cleanurls-on-nginx-environment#readings) by Mehdi Merah.
|
@ -0,0 +1,58 @@
|
|||||||
|
# Sitemap Generation
|
||||||
|
|
||||||
|
VitePress comes with out-of-the-box support for generating a `sitemap.xml` file for your site. To enable it, add the following to your `.vitepress/config.js`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
sitemap: {
|
||||||
|
hostname: 'https://example.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To have `<lastmod>` tags in your `sitemap.xml`, you can enable the [`lastUpdated`](../reference/default-theme-last-updated) option.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
Sitemap support is powered by the [`sitemap`](https://www.npmjs.com/package/sitemap) module. You can pass any options supported by it to the `sitemap` option in your config file. These will be passed directly to the `SitemapStream` constructor. Refer to the [`sitemap` documentation](https://www.npmjs.com/package/sitemap#options-you-can-pass) for more details. Example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
sitemap: {
|
||||||
|
hostname: 'https://example.com',
|
||||||
|
lastmodDateOnly: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're using `base` in your config, you should append it to the `hostname` option:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
base: '/my-site/',
|
||||||
|
sitemap: {
|
||||||
|
hostname: 'https://example.com/my-site/'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `transformItems` Hook
|
||||||
|
|
||||||
|
You can use the `sitemap.transformItems` hook to modify the sitemap items before they are written to the `sitemap.xml` file. This hook is called with an array of sitemap items and expects an array of sitemap items to be returned. Example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
sitemap: {
|
||||||
|
hostname: 'https://example.com',
|
||||||
|
transformItems: (items) => {
|
||||||
|
// add new items or modify/filter existing items
|
||||||
|
items.push({
|
||||||
|
url: '/extra-page',
|
||||||
|
changefreq: 'monthly',
|
||||||
|
priority: 0.8
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
layout: home
|
||||||
|
|
||||||
|
hero:
|
||||||
|
name: VitePress
|
||||||
|
text: Vite & Vue Powered Static Site Generator
|
||||||
|
tagline: Markdown to Beautiful Docs in Minutes
|
||||||
|
actions:
|
||||||
|
- theme: brand
|
||||||
|
text: What is VitePress?
|
||||||
|
link: /guide/what-is-vitepress
|
||||||
|
- theme: alt
|
||||||
|
text: Quickstart
|
||||||
|
link: /guide/getting-started
|
||||||
|
- theme: alt
|
||||||
|
text: GitHub
|
||||||
|
link: https://github.com/vuejs/vitepress
|
||||||
|
image:
|
||||||
|
src: /vitepress-logo-large.svg
|
||||||
|
alt: VitePress
|
||||||
|
|
||||||
|
features:
|
||||||
|
- icon: 📝
|
||||||
|
title: Focus on Your Content
|
||||||
|
details: Effortlessly create beautiful documentation sites with just markdown.
|
||||||
|
- icon: <svg xmlns="http://www.w3.org/2000/svg" width="30" viewBox="0 0 256 256.32"><defs><linearGradient id="a" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"/><stop offset="100%" stop-color="#BD34FE"/></linearGradient><linearGradient id="b" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"/><stop offset="8.333%" stop-color="#FFDD35"/><stop offset="100%" stop-color="#FFA800"/></linearGradient></defs><path fill="url(#a)" d="M255.153 37.938 134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"/><path fill="url(#b)" d="M185.432.063 96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028 72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"/></svg>
|
||||||
|
title: Enjoy the Vite DX
|
||||||
|
details: Instant server start, lightning fast hot updates, and leverage Vite ecosystem plugins.
|
||||||
|
- icon: <svg xmlns="http://www.w3.org/2000/svg" width="30" viewBox="0 0 256 220.8"><path fill="#41B883" d="M204.8 0H256L128 220.8 0 0h97.92L128 51.2 157.44 0h47.36Z"/><path fill="#41B883" d="m0 0 128 220.8L256 0h-51.2L128 132.48 50.56 0H0Z"/><path fill="#35495E" d="M50.56 0 128 133.12 204.8 0h-47.36L128 51.2 97.92 0H50.56Z"/></svg>
|
||||||
|
title: Customize with Vue
|
||||||
|
details: Use Vue syntax and components directly in markdown, or build custom themes with Vue.
|
||||||
|
- icon: 🚀
|
||||||
|
title: Ship Fast Sites
|
||||||
|
details: Fast initial load with static HTML, fast post-load navigation with client-side routing.
|
||||||
|
---
|
@ -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 relative to **cwd** (default: `<root>/.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
|
||||||
|
```
|
@ -0,0 +1,383 @@
|
|||||||
|
---
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# Search
|
||||||
|
|
||||||
|
## Local Search
|
||||||
|
|
||||||
|
VitePress supports fuzzy full-text search using a in-browser index thanks to [minisearch](https://github.com/lucaong/minisearch/). To enable this feature, simply set the `themeConfig.search.provider` option to `'local'` in your `.vitepress/config.ts` file:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig } from 'vitepress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
themeConfig: {
|
||||||
|
search: {
|
||||||
|
provider: 'local'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Example result:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Alternatively, you can use [Algolia DocSearch](#algolia-search) or some community plugins like:
|
||||||
|
|
||||||
|
- <https://www.npmjs.com/package/vitepress-plugin-search>
|
||||||
|
- <https://www.npmjs.com/package/vitepress-plugin-pagefind>
|
||||||
|
- <https://www.npmjs.com/package/@orama/plugin-vitepress>
|
||||||
|
|
||||||
|
### i18n {#local-search-i18n}
|
||||||
|
|
||||||
|
You can use a config like this to use multilingual search:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig } from 'vitepress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
themeConfig: {
|
||||||
|
search: {
|
||||||
|
provider: 'local',
|
||||||
|
options: {
|
||||||
|
locales: {
|
||||||
|
zh: { // make this `root` if you want to translate the default locale
|
||||||
|
translations: {
|
||||||
|
button: {
|
||||||
|
buttonText: '搜索',
|
||||||
|
buttonAriaLabel: '搜索'
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
displayDetails: '显示详细列表',
|
||||||
|
resetButtonTitle: '重置搜索',
|
||||||
|
backButtonTitle: '关闭搜索',
|
||||||
|
noResultsText: '没有结果',
|
||||||
|
footer: {
|
||||||
|
selectText: '选择',
|
||||||
|
selectKeyAriaLabel: '输入',
|
||||||
|
navigateText: '导航',
|
||||||
|
navigateUpKeyAriaLabel: '上箭头',
|
||||||
|
navigateDownKeyAriaLabel: '下箭头',
|
||||||
|
closeText: '关闭',
|
||||||
|
closeKeyAriaLabel: 'esc'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### miniSearch options
|
||||||
|
|
||||||
|
You can configure MiniSearch like this:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig } from 'vitepress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
themeConfig: {
|
||||||
|
search: {
|
||||||
|
provider: 'local',
|
||||||
|
options: {
|
||||||
|
miniSearch: {
|
||||||
|
/**
|
||||||
|
* @type {Pick<import('minisearch').Options, 'extractField' | 'tokenize' | 'processTerm'>}
|
||||||
|
*/
|
||||||
|
options: {
|
||||||
|
/* ... */
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @type {import('minisearch').SearchOptions}
|
||||||
|
* @default
|
||||||
|
* { fuzzy: 0.2, prefix: true, boost: { title: 4, text: 2, titles: 1 } }
|
||||||
|
*/
|
||||||
|
searchOptions: {
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Learn more in [MiniSearch docs](https://lucaong.github.io/minisearch/classes/MiniSearch.MiniSearch.html).
|
||||||
|
|
||||||
|
### Custom content renderer
|
||||||
|
|
||||||
|
You can customize the function used to render the markdown content before indexing it:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig } from 'vitepress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
themeConfig: {
|
||||||
|
search: {
|
||||||
|
provider: 'local',
|
||||||
|
options: {
|
||||||
|
/**
|
||||||
|
* @param {string} src
|
||||||
|
* @param {import('vitepress').MarkdownEnv} env
|
||||||
|
* @param {import('markdown-it-async')} md
|
||||||
|
*/
|
||||||
|
async _render(src, env, md) {
|
||||||
|
// return html string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
This function will be stripped from client-side site data, so you can use Node.js APIs in it.
|
||||||
|
|
||||||
|
#### Example: Excluding pages from search
|
||||||
|
|
||||||
|
You can exclude pages from search by adding `search: false` to the frontmatter of the page. Alternatively:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig } from 'vitepress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
themeConfig: {
|
||||||
|
search: {
|
||||||
|
provider: 'local',
|
||||||
|
options: {
|
||||||
|
async _render(src, env, md) {
|
||||||
|
const html = await md.renderAsync(src, env)
|
||||||
|
if (env.frontmatter?.search === false) return ''
|
||||||
|
if (env.relativePath.startsWith('some/path')) return ''
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
::: warning Note
|
||||||
|
In case a custom `_render` function is provided, you need to handle the `search: false` frontmatter yourself. Also, the `env` object won't be completely populated before `md.renderAsync` is called, so any checks on optional `env` properties like `frontmatter` should be done after that.
|
||||||
|
:::
|
||||||
|
|
||||||
|
#### Example: Transforming content - adding anchors
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig } from 'vitepress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
themeConfig: {
|
||||||
|
search: {
|
||||||
|
provider: 'local',
|
||||||
|
options: {
|
||||||
|
async _render(src, env, md) {
|
||||||
|
const html = await md.renderAsync(src, env)
|
||||||
|
if (env.frontmatter?.title)
|
||||||
|
return await md.renderAsync(`# ${env.frontmatter.title}`) + html
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Algolia Search
|
||||||
|
|
||||||
|
VitePress supports searching your docs site using [Algolia DocSearch](https://docsearch.algolia.com/docs/what-is-docsearch). Refer their getting started guide. In your `.vitepress/config.ts` you'll need to provide at least the following to make it work:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig } from 'vitepress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
themeConfig: {
|
||||||
|
search: {
|
||||||
|
provider: 'algolia',
|
||||||
|
options: {
|
||||||
|
appId: '...',
|
||||||
|
apiKey: '...',
|
||||||
|
indexName: '...'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### i18n {#algolia-search-i18n}
|
||||||
|
|
||||||
|
You can use a config like this to use multilingual search:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig } from 'vitepress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
themeConfig: {
|
||||||
|
search: {
|
||||||
|
provider: 'algolia',
|
||||||
|
options: {
|
||||||
|
appId: '...',
|
||||||
|
apiKey: '...',
|
||||||
|
indexName: '...',
|
||||||
|
locales: {
|
||||||
|
zh: {
|
||||||
|
placeholder: '搜索文档',
|
||||||
|
translations: {
|
||||||
|
button: {
|
||||||
|
buttonText: '搜索文档',
|
||||||
|
buttonAriaLabel: '搜索文档'
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
searchBox: {
|
||||||
|
resetButtonTitle: '清除查询条件',
|
||||||
|
resetButtonAriaLabel: '清除查询条件',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
cancelButtonAriaLabel: '取消'
|
||||||
|
},
|
||||||
|
startScreen: {
|
||||||
|
recentSearchesTitle: '搜索历史',
|
||||||
|
noRecentSearchesText: '没有搜索历史',
|
||||||
|
saveRecentSearchButtonTitle: '保存至搜索历史',
|
||||||
|
removeRecentSearchButtonTitle: '从搜索历史中移除',
|
||||||
|
favoriteSearchesTitle: '收藏',
|
||||||
|
removeFavoriteSearchButtonTitle: '从收藏中移除'
|
||||||
|
},
|
||||||
|
errorScreen: {
|
||||||
|
titleText: '无法获取结果',
|
||||||
|
helpText: '你可能需要检查你的网络连接'
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
selectText: '选择',
|
||||||
|
navigateText: '切换',
|
||||||
|
closeText: '关闭',
|
||||||
|
searchByText: '搜索提供者'
|
||||||
|
},
|
||||||
|
noResultsScreen: {
|
||||||
|
noResultsText: '无法找到相关结果',
|
||||||
|
suggestedQueryText: '你可以尝试查询',
|
||||||
|
reportMissingResultsText: '你认为该查询应该有结果?',
|
||||||
|
reportMissingResultsLinkText: '点击反馈'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
[These options](https://github.com/vuejs/vitepress/blob/main/types/docsearch.d.ts) can be overridden. Refer official Algolia docs to learn more about them.
|
||||||
|
|
||||||
|
### Crawler Config
|
||||||
|
|
||||||
|
Here is an example config based on what this site uses:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
new Crawler({
|
||||||
|
appId: '...',
|
||||||
|
apiKey: '...',
|
||||||
|
rateLimit: 8,
|
||||||
|
startUrls: ['https://vitepress.dev/'],
|
||||||
|
renderJavaScript: false,
|
||||||
|
sitemaps: [],
|
||||||
|
exclusionPatterns: [],
|
||||||
|
ignoreCanonicalTo: false,
|
||||||
|
discoveryPatterns: ['https://vitepress.dev/**'],
|
||||||
|
schedule: 'at 05:10 on Saturday',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
indexName: 'vitepress',
|
||||||
|
pathsToMatch: ['https://vitepress.dev/**'],
|
||||||
|
recordExtractor: ({ $, helpers }) => {
|
||||||
|
return helpers.docsearch({
|
||||||
|
recordProps: {
|
||||||
|
lvl1: '.content h1',
|
||||||
|
content: '.content p, .content li',
|
||||||
|
lvl0: {
|
||||||
|
selectors: 'section.has-active div h2',
|
||||||
|
defaultValue: 'Documentation'
|
||||||
|
},
|
||||||
|
lvl2: '.content h2',
|
||||||
|
lvl3: '.content h3',
|
||||||
|
lvl4: '.content h4',
|
||||||
|
lvl5: '.content h5'
|
||||||
|
},
|
||||||
|
indexHeadings: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
initialIndexSettings: {
|
||||||
|
vitepress: {
|
||||||
|
attributesForFaceting: ['type', 'lang'],
|
||||||
|
attributesToRetrieve: ['hierarchy', 'content', 'anchor', 'url'],
|
||||||
|
attributesToHighlight: ['hierarchy', 'hierarchy_camel', 'content'],
|
||||||
|
attributesToSnippet: ['content:10'],
|
||||||
|
camelCaseAttributes: ['hierarchy', 'hierarchy_radio', 'content'],
|
||||||
|
searchableAttributes: [
|
||||||
|
'unordered(hierarchy_radio_camel.lvl0)',
|
||||||
|
'unordered(hierarchy_radio.lvl0)',
|
||||||
|
'unordered(hierarchy_radio_camel.lvl1)',
|
||||||
|
'unordered(hierarchy_radio.lvl1)',
|
||||||
|
'unordered(hierarchy_radio_camel.lvl2)',
|
||||||
|
'unordered(hierarchy_radio.lvl2)',
|
||||||
|
'unordered(hierarchy_radio_camel.lvl3)',
|
||||||
|
'unordered(hierarchy_radio.lvl3)',
|
||||||
|
'unordered(hierarchy_radio_camel.lvl4)',
|
||||||
|
'unordered(hierarchy_radio.lvl4)',
|
||||||
|
'unordered(hierarchy_radio_camel.lvl5)',
|
||||||
|
'unordered(hierarchy_radio.lvl5)',
|
||||||
|
'unordered(hierarchy_radio_camel.lvl6)',
|
||||||
|
'unordered(hierarchy_radio.lvl6)',
|
||||||
|
'unordered(hierarchy_camel.lvl0)',
|
||||||
|
'unordered(hierarchy.lvl0)',
|
||||||
|
'unordered(hierarchy_camel.lvl1)',
|
||||||
|
'unordered(hierarchy.lvl1)',
|
||||||
|
'unordered(hierarchy_camel.lvl2)',
|
||||||
|
'unordered(hierarchy.lvl2)',
|
||||||
|
'unordered(hierarchy_camel.lvl3)',
|
||||||
|
'unordered(hierarchy.lvl3)',
|
||||||
|
'unordered(hierarchy_camel.lvl4)',
|
||||||
|
'unordered(hierarchy.lvl4)',
|
||||||
|
'unordered(hierarchy_camel.lvl5)',
|
||||||
|
'unordered(hierarchy.lvl5)',
|
||||||
|
'unordered(hierarchy_camel.lvl6)',
|
||||||
|
'unordered(hierarchy.lvl6)',
|
||||||
|
'content'
|
||||||
|
],
|
||||||
|
distinct: true,
|
||||||
|
attributeForDistinct: 'url',
|
||||||
|
customRanking: [
|
||||||
|
'desc(weight.pageRank)',
|
||||||
|
'desc(weight.level)',
|
||||||
|
'asc(weight.position)'
|
||||||
|
],
|
||||||
|
ranking: [
|
||||||
|
'words',
|
||||||
|
'filters',
|
||||||
|
'typo',
|
||||||
|
'attribute',
|
||||||
|
'proximity',
|
||||||
|
'exact',
|
||||||
|
'custom'
|
||||||
|
],
|
||||||
|
highlightPreTag: '<span class="algolia-docsearch-suggestion--highlight">',
|
||||||
|
highlightPostTag: '</span>',
|
||||||
|
minWordSizefor1Typo: 3,
|
||||||
|
minWordSizefor2Typos: 7,
|
||||||
|
allowTyposOnNumericTokens: false,
|
||||||
|
minProximity: 1,
|
||||||
|
ignorePlurals: true,
|
||||||
|
advancedSyntax: true,
|
||||||
|
attributeCriteriaComputedByMinProximity: true,
|
||||||
|
removeWordsIfNoResults: 'allOptional'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue