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
|
||||
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 {
|
||||
async paths() {
|
||||
return [
|
||||
{ params: { id: 'foo' }, content: `# Foo` },
|
||||
{ params: { id: 'bar' }, content: `# Bar` }
|
||||
]
|
||||
import { defineRoutes } from 'vitepress'
|
||||
import paths from './paths'
|
||||
|
||||
export default defineRoutes({
|
||||
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
|
||||
|
||||
This is before region
|
||||
|
||||
<!-- #region snippet -->
|
||||
## Region
|
||||
|
||||
this is region
|
||||
<!-- #endregion snippet -->
|
||||
This is a region
|
||||
<!-- #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 {
|
||||
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 { 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
|
||||
let page: Page
|
||||
const tempDir = fileURLToPath(new URL('./.temp', import.meta.url))
|
||||
const getTempRoot = () => path.join(tempDir, nanoid())
|
||||
|
||||
beforeAll(async () => {
|
||||
browser = await chromium.connect(process.env['WS_ENDPOINT']!)
|
||||
page = await browser.newPage()
|
||||
const browser = await chromium.launch({
|
||||
headless: !process.env.DEBUG,
|
||||
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 () => {
|
||||
await page.close()
|
||||
await browser.close()
|
||||
await fs.remove(tempDir)
|
||||
})
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'temp')
|
||||
|
||||
async function testVariation(options: ScaffoldOptions) {
|
||||
fs.removeSync(root)
|
||||
scaffold({
|
||||
...options,
|
||||
root
|
||||
})
|
||||
test.each(variations)('init %s', async (_, { theme, useTs }) => {
|
||||
const root = getTempRoot()
|
||||
await fs.remove(root)
|
||||
scaffold({ root, theme, useTs, injectNpmScripts: false })
|
||||
|
||||
let server: ViteDevServer | Server
|
||||
const port = await getPort()
|
||||
const server = await createServer(root, { port })
|
||||
await server.listen()
|
||||
|
||||
async function goto(path: string) {
|
||||
await page.goto(`http://localhost:${port}${path}`)
|
||||
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 {
|
||||
await goto('/')
|
||||
expect(await page.textContent('h1')).toMatch('My Awesome Project')
|
||||
|
||||
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')
|
||||
|
||||
await goto('/')
|
||||
expect(await page.textContent('h1')).toMatch('My Awesome Project')
|
||||
|
||||
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')
|
||||
} 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 = [
|
||||
ScaffoldThemeType.Default,
|
||||
ScaffoldThemeType.DefaultCustom,
|
||||
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
|
||||
}))
|
||||
// teardown
|
||||
} finally {
|
||||
await server.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -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
|
||||
// @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')
|
||||
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"plugins": {
|
||||
"postcss-rtlcss": {
|
||||
"ltrPrefix": ":where([dir=\"ltr\"])",
|
||||
"rtlPrefix": ":where([dir=\"rtl\"])"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,227 +1,148 @@
|
||||
import { createRequire } from 'module'
|
||||
import { defineConfig } from 'vitepress'
|
||||
import {
|
||||
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 pkg = require('vitepress/package.json')
|
||||
const prod = !!process.env.NETLIFY
|
||||
|
||||
export default defineConfig({
|
||||
lang: 'en-US',
|
||||
title: 'VitePress',
|
||||
description: 'Vite & Vue powered static site generator.',
|
||||
|
||||
rewrites: {
|
||||
'en/:rest*': ':rest*'
|
||||
},
|
||||
|
||||
lastUpdated: true,
|
||||
cleanUrls: true,
|
||||
metaChunk: true,
|
||||
|
||||
head: [
|
||||
['meta', { name: 'theme-color', content: '#3c8772' }],
|
||||
[
|
||||
'script',
|
||||
markdown: {
|
||||
math: true,
|
||||
codeTransformers: [
|
||||
// We use `[!!code` in demo to prevent transformation, here we revert it back.
|
||||
{
|
||||
src: 'https://cdn.usefathom.com/script.js',
|
||||
'data-site': 'AZBRSFGG',
|
||||
'data-spa': 'auto',
|
||||
defer: ''
|
||||
postprocess(code) {
|
||||
return code.replace(/\[\!\!code/g, '[!code')
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
],
|
||||
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: {
|
||||
nav: nav(),
|
||||
sitemap: {
|
||||
hostname: 'https://vitepress.dev',
|
||||
transformItems(items) {
|
||||
return items.filter((item) => !item.url.includes('migration'))
|
||||
}
|
||||
},
|
||||
|
||||
sidebar: {
|
||||
'/guide/': sidebarGuide(),
|
||||
'/reference/': sidebarReference()
|
||||
},
|
||||
/* prettier-ignore */
|
||||
head: [
|
||||
['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: {
|
||||
pattern: 'https://github.com/vuejs/vitepress/edit/main/docs/:path',
|
||||
text: 'Edit this page on GitHub'
|
||||
},
|
||||
themeConfig: {
|
||||
logo: { src: '/vitepress-logo-mini.svg', width: 24, height: 24 },
|
||||
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
|
||||
],
|
||||
|
||||
footer: {
|
||||
message: 'Released under the MIT License.',
|
||||
copyright: 'Copyright © 2019-present Evan You'
|
||||
},
|
||||
|
||||
search: {
|
||||
provider: 'algolia',
|
||||
options: {
|
||||
appId: '8J64VVRP8K',
|
||||
apiKey: 'a18e2f4cc5665f6602c5631fd868adfd',
|
||||
apiKey: '52f578a92b88ad6abde815aae2b0ad7c',
|
||||
indexName: 'vitepress'
|
||||
}
|
||||
},
|
||||
|
||||
carbonAds: {
|
||||
code: 'CEBDT27Y',
|
||||
placement: 'vuejsorg'
|
||||
}
|
||||
}
|
||||
})
|
||||
carbonAds: { code: 'CEBDT27Y', placement: 'vuejsorg' }
|
||||
},
|
||||
|
||||
function nav() {
|
||||
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'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
locales: {
|
||||
root: { label: 'English' },
|
||||
zh: { label: '简体中文' },
|
||||
pt: { label: 'Português' },
|
||||
ru: { label: 'Русский' },
|
||||
es: { label: 'Español' },
|
||||
ko: { label: '한국어' },
|
||||
fa: { label: 'فارسی' }
|
||||
},
|
||||
|
||||
function sidebarGuide() {
|
||||
return [
|
||||
{
|
||||
text: 'Introduction',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'What is VitePress?', link: '/guide/what-is-vitepress' },
|
||||
{ text: 'Getting Started', link: '/guide/getting-started' },
|
||||
{ text: 'Routing', link: '/guide/routing' },
|
||||
{ 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'
|
||||
vite: {
|
||||
plugins: [
|
||||
groupIconVitePlugin({
|
||||
customIcon: {
|
||||
vitepress: localIconLoader(
|
||||
import.meta.url,
|
||||
'../public/vitepress-logo-mini.svg'
|
||||
),
|
||||
firebase: 'logos:firebase'
|
||||
}
|
||||
]
|
||||
},
|
||||
// {
|
||||
// text: 'Migrations',
|
||||
// collapsed: false,
|
||||
// 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'
|
||||
}
|
||||
]
|
||||
}
|
||||
}),
|
||||
prod &&
|
||||
llmstxt({
|
||||
workDir: 'en',
|
||||
ignoreFiles: ['index.md']
|
||||
})
|
||||
]
|
||||
},
|
||||
|
||||
function sidebarReference() {
|
||||
return [
|
||||
{
|
||||
text: 'Reference',
|
||||
items: [
|
||||
{ text: 'Site Config', link: '/reference/site-config' },
|
||||
{ text: 'Frontmatter Config', link: '/reference/frontmatter-config' },
|
||||
{ text: 'Runtime API', link: '/reference/runtime-api' },
|
||||
{ text: 'CLI', link: '/reference/cli' },
|
||||
{
|
||||
text: 'Default Theme',
|
||||
items: [
|
||||
{
|
||||
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'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
transformPageData: prod
|
||||
? (pageData, ctx) => {
|
||||
const site = resolveSiteDataByRoute(
|
||||
ctx.siteConfig.site,
|
||||
pageData.relativePath
|
||||
)
|
||||
const title = `${pageData.title || site.title} | ${pageData.description || site.description}`
|
||||
;((pageData.frontmatter.head ??= []) as HeadConfig[]).push(
|
||||
['meta', { property: 'og:locale', content: site.lang }],
|
||||
['meta', { property: 'og:title', content: title }]
|
||||
)
|
||||
}
|
||||
: undefined
|
||||
})
|
||||
|
@ -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