Merge pull request #687 from vuejs/next-theme

feat: 1.0.0-alpha merge
pull/699/head
Kia King Ishii 2 years ago committed by GitHub
commit baf8083e66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -34,16 +34,24 @@ $ pnpm install
### Setup VitePress Dev Environment
You may start VitePress local dev environment by running `pnpm run dev`.
At first, execute the `pnpm run build` command.
```bash
$ pnpm run dev
$ pnpm run build
```
The easiest way to start testing out VitePress is to tweak the VitePress docs. You may run `pnpm run docs` folder to boot up VitePress documentation site locally, with live reloading of the source code.
You only need to do this once for your fresh project. It copies required files and makes sure everything is in place. After this, you only need to run `dev` related commands.
The easiest way to start testing out VitePress is to tweak the VitePress docs. You may run `pnpm run docs` to boot up VitePress documentation site locally, with live reloading of the source code.
```bash
$ pnpm run docs
```
After executing the above command, visit http://localhost:3000 and try modifying the source code. You'll get live update.
If you don't need docs site up and running, you may start VitePress local dev environment with `pnpm run dev`.
```bash
$ pnpm run dev
```

@ -13,7 +13,9 @@ jobs:
uses: actions/checkout@v3
- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v2.0.1
with:
version: 7.0.1
- name: Set node version to ${{ matrix.node_version }}
uses: actions/setup-node@v3
@ -24,8 +26,11 @@ jobs:
- name: Install deps
run: pnpm install
- name: Build
run: pnpm run build
- name: Lint
run: pnpm run lint-fail
- name: Test
run: pnpm run test
run: pnpm run test-run
- name: Build
run: pnpm run build

6
.gitignore vendored

@ -3,10 +3,10 @@
/src/node/shared.ts
*.log
.DS_Store
.idea
.vite_opt_cache
.vscode
dist
node_modules
TODOs.md
.vscode
.idea
pnpm-global
TODOs.md

@ -0,0 +1 @@
shell-emulator=true

@ -0,0 +1,7 @@
/docs
/examples
*.css
*.md
*.vue
dist
pnpm-lock.yaml

@ -1,4 +1,4 @@
# (WIP) VitePress 📝💨
# VitePress (alpha) 📝💨
[![Test](https://github.com/vuejs/vitepress/workflows/Test/badge.svg)](https://github.com/vuejs/vitepress/actions)
[![npm](https://img.shields.io/npm/v/vitepress)](https://www.npmjs.com/package/vitepress)
@ -7,6 +7,8 @@
VitePress is [VuePress](https://vuepress.vuejs.org)' spiritual successor, built on top of [vite](https://github.com/vitejs/vite).
Currently, it's in the `alpha` stage. It is already suitable for out-of-the-box documentation use, but the config and theming API may still change between minor releases.
## Documentation
To check out docs, visit [vitepress.vuejs.org](https://vitepress.vuejs.org).

@ -1,134 +0,0 @@
import {
isSideBarEmpty,
getSideBarConfig,
getFlatSideBarLinks
} from 'client/theme-default/support/sideBar'
describe('client/theme-default/support/sideBar', () => {
it('checks if the given sidebar is empty', () => {
expect(isSideBarEmpty(undefined)).toBe(true)
expect(isSideBarEmpty(false)).toBe(true)
expect(isSideBarEmpty([])).toBe(true)
expect(isSideBarEmpty('auto')).toBe(false)
expect(isSideBarEmpty([{ text: 'a', link: '/a' }])).toBe(false)
})
it('gets the correct sidebar items', () => {
expect(getSideBarConfig(false, '')).toEqual(false)
expect(getSideBarConfig('auto', '')).toEqual('auto')
const sidebar = [{ text: 'Title 01', link: 'title-01' }]
const expected = [{ text: 'Title 01', link: 'title-01' }]
expect(getSideBarConfig(sidebar, '')).toEqual(expected)
})
it('gets the correct sidebar items from the given path', () => {
const sidebar = {
'/guide/': [{ text: 'G', link: 'g' }],
'/': [{ text: 'R', link: 'r' }]
}
expect(getSideBarConfig(sidebar, '/')).toEqual(sidebar['/'])
expect(getSideBarConfig(sidebar, '/guide/')).toEqual(sidebar['/guide/'])
})
it('gets the correct sidebar items with various combination', () => {
const s = {
'/guide/': [{ text: 'G', link: 'g' }],
api: [{ text: 'A', link: 'a' }]
}
expect(getSideBarConfig(s, '/guide/')).toEqual(s['/guide/'])
// no ending slash should not match
expect(getSideBarConfig(s, '/guide')).not.toEqual(s['/guide/'])
expect(getSideBarConfig(s, 'guide/')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, 'guide/nested')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, '/guide/nested')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, 'guide/nested/')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, '/api/')).toEqual(s['api'])
expect(getSideBarConfig(s, '/api')).toEqual(s['api'])
expect(getSideBarConfig(s, 'api/')).toEqual(s['api'])
expect(getSideBarConfig(s, 'api/nested')).toEqual(s['api'])
expect(getSideBarConfig(s, '/api/nested')).toEqual(s['api'])
expect(getSideBarConfig(s, 'api/nested/')).toEqual(s['api'])
expect(getSideBarConfig(s, '/')).toEqual('auto')
})
it('creates flat sidebar links', () => {
const sidebar = [
{ text: 'Title 01', link: '/title-01' },
{ text: 'Title 02', link: '/title-02' },
{ text: 'Title 03', link: '/title-03' }
]
const expected = [
{ text: 'Title 01', link: '/title-01' },
{ text: 'Title 02', link: '/title-02' },
{ text: 'Title 03', link: '/title-03' }
]
expect(getFlatSideBarLinks(sidebar)).toEqual(expected)
})
it('creates flat sidebar links with mixed sidebar group', () => {
const sidebar = [
{
text: 'Title 01',
link: '/title-01',
children: [
{ text: 'Children 01', link: '/children-01' },
{ text: 'Children 02', link: '/children-02' }
]
},
{ text: 'Title 02', link: '/title-02' },
{ text: 'Title 03', link: '/title-03' }
]
const expected = [
{ text: 'Title 01', link: '/title-01' },
{ text: 'Children 01', link: '/children-01' },
{ text: 'Children 02', link: '/children-02' },
{ text: 'Title 02', link: '/title-02' },
{ text: 'Title 03', link: '/title-03' }
]
expect(getFlatSideBarLinks(sidebar)).toEqual(expected)
})
it('ignores any items with no `link` property', () => {
const sidebar = [
{
text: 'Title 01',
children: [
{ text: 'Children 01', link: '/children-01' },
{ text: 'Children 02', link: '/children-02' }
]
},
{ text: 'Title 02', link: '/title-02' }
]
const expected = [
{ text: 'Children 01', link: '/children-01' },
{ text: 'Children 02', link: '/children-02' },
{ text: 'Title 02', link: '/title-02' }
]
expect(getFlatSideBarLinks(sidebar)).toEqual(expected)
})
it('removes `.md` or `.html` extention', () => {
const sidebar = [
{ text: 'Title 01', link: '/title-01.md' },
{ text: 'Title 02', link: '/title-02.html' }
]
const expected = [
{ text: 'Title 01', link: '/title-01' },
{ text: 'Title 02', link: '/title-02' }
]
expect(getFlatSideBarLinks(sidebar)).toEqual(expected)
})
})

@ -1,41 +0,0 @@
import * as Utils from 'client/theme-default/utils'
describe('client/theme-default/utils', () => {
describe('ensureStartingSlash', () => {
it('should add slash to the beginning of the given path', () => {
expect(Utils.ensureStartingSlash('path')).toBe('/path')
expect(Utils.ensureStartingSlash('path/nested')).toBe('/path/nested')
expect(Utils.ensureStartingSlash('/path')).toBe('/path')
expect(Utils.ensureStartingSlash('/path/nested')).toBe('/path/nested')
})
})
describe('ensureEndingSlash', () => {
it('should add slash to the end of the given path', () => {
expect(Utils.ensureEndingSlash('path')).toBe('path/')
expect(Utils.ensureEndingSlash('path/nested')).toBe('path/nested/')
expect(Utils.ensureEndingSlash('path/')).toBe('path/')
expect(Utils.ensureEndingSlash('path/nested/')).toBe('path/nested/')
expect(Utils.ensureEndingSlash('path/page.html')).toBe('path/page.html')
})
})
describe('removeExtention', () => {
it('removes `.md` or `.html` extention from the path', () => {
expect(Utils.removeExtention('/')).toBe('/')
expect(Utils.removeExtention('index')).toBe('/')
expect(Utils.removeExtention('index.md')).toBe('/')
expect(Utils.removeExtention('index.html')).toBe('/')
expect(Utils.removeExtention('/index')).toBe('/')
expect(Utils.removeExtention('/index.md')).toBe('/')
expect(Utils.removeExtention('/index.html')).toBe('/')
expect(Utils.removeExtention('path')).toBe('path')
expect(Utils.removeExtention('path.md')).toBe('path')
expect(Utils.removeExtention('path.html')).toBe('path')
expect(Utils.removeExtention('path/')).toBe('path/')
expect(Utils.removeExtention('path/nested.md')).toBe('path/nested')
expect(Utils.removeExtention('path/nested.html')).toBe('path/nested')
expect(Utils.removeExtention('path/nested/index')).toBe('path/nested/')
})
})
})

@ -1,28 +1,29 @@
import { test, expect } from 'vitest'
import { deeplyParseHeader } from 'node/utils/parseHeader'
test('deeplyParseHeader', () => {
const asserts: Record<string, string> = {
// Remove tail html
// remove tail html
'# `H1` <Comp></Comp>': '# H1',
'# *H1* <Comp/>': '# H1',
// Reserve code-wrapped tail html
// reserve code-wrapped tail html
'# `H1` `<Comp></Comp>`': '# H1 <Comp></Comp>',
'# *H1* `<Comp/>`': '# H1 <Comp/>',
// Remove leading html
// remove leading html
'# <Comp></Comp> `H1`': '# H1',
'# <Comp/> *H1*': '# H1',
// Reserve code-wrapped leading html
// reserve code-wrapped leading html
'# `<Comp></Comp>` `H1`': '# <Comp></Comp> H1',
'# `<Comp/>` *H1*': '# <Comp/> H1',
// Remove middle html
// remove middle html
'# `H1` <Comp></Comp> `H2`': '# H1 H2',
'# `H1` <Comp/> `H2`': '# H1 H2',
// Reserve middle html
// reserve middle html
'# `H1` `<Comp></Comp>` `H2`': '# H1 <Comp></Comp> H2',
'# `H1` `<Comp/>` `H2`': '# H1 <Comp/> H2'
}

@ -1,3 +1,4 @@
import { describe, test, expect } from 'vitest'
import { parseHeader } from 'node/utils/parseHeader'
describe('parseHeader', () => {

@ -1,3 +1,4 @@
import { test, expect } from 'vitest'
import { removeNonCodeWrappedHTML } from 'node/utils/parseHeader'
test('removeNonCodeWrappedHTML', () => {

@ -1,14 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"noUnusedLocals": true,
"skipLibCheck": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"types": ["node", "vitest/global"],
"types": ["node", "vitest/globals"],
"paths": {
"node/*": ["../src/node/*"],
"client/*": ["../src/client/*"]

@ -1,10 +0,0 @@
import path from 'path'
export default {
resolve: {
alias: {
node: path.resolve(__dirname, '../src/node'),
client: path.resolve(__dirname, '../src/client')
}
}
}

@ -0,0 +1,17 @@
import { dirname, resolve } from 'path'
import { fileURLToPath } from 'url'
import { defineConfig } from 'vite'
const dir = dirname(fileURLToPath(import.meta.url))
export default defineConfig({
resolve: {
alias: {
node: resolve(dir, '../src/node'),
client: resolve(dir, '../src/client')
}
},
test: {
globals: true
}
})

@ -1,53 +0,0 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"projectFolder": "./src/client",
"mainEntryPointFilePath": "./dist/temp/index.d.ts",
"dtsRollup": {
"enabled": true,
"publicTrimmedFilePath": "./dist/client/index.d.ts"
},
"apiReport": {
"enabled": false
},
"docModel": {
"enabled": false
},
"tsdocMetadata": {
"enabled": false
},
"messages": {
"compilerMessageReporting": {
"default": {
"logLevel": "warning"
}
},
"extractorMessageReporting": {
"default": {
"logLevel": "warning",
"addToApiReportFile": true
},
"ae-missing-release-tag": {
"logLevel": "none"
}
},
"tsdocMessageReporting": {
"default": {
"logLevel": "warning"
},
"tsdoc-undefined-tag": {
"logLevel": "none"
}
}
}
}

@ -1,53 +0,0 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"projectFolder": "./src/node",
"mainEntryPointFilePath": "./dist/temp/index.d.ts",
"dtsRollup": {
"enabled": true,
"publicTrimmedFilePath": "./dist/node/index.d.ts"
},
"apiReport": {
"enabled": false
},
"docModel": {
"enabled": false
},
"tsdocMetadata": {
"enabled": false
},
"messages": {
"compilerMessageReporting": {
"default": {
"logLevel": "warning"
}
},
"extractorMessageReporting": {
"default": {
"logLevel": "warning",
"addToApiReportFile": true
},
"ae-missing-release-tag": {
"logLevel": "none"
}
},
"tsdocMessageReporting": {
"default": {
"logLevel": "warning"
},
"tsdoc-undefined-tag": {
"logLevel": "none"
}
}
}
}

@ -1,2 +1,2 @@
#!/usr/bin/env node
require('../dist/node/cli')
import('../dist/node/cli.js')

5
client.d.ts vendored

@ -1,4 +1,3 @@
// re-export vite client types
// with strict installers like pnpm, user won't be able to reference vite/client
// in project root
// re-export vite client types. with strict installers like pnpm, user won't
// be able to reference vite/client in project root.
/// <reference types="vite/client" />

@ -4,15 +4,32 @@ export default defineConfig({
lang: 'en-US',
title: 'VitePress',
description: 'Vite & Vue powered static site generator.',
lastUpdated: true,
themeConfig: {
repo: 'vuejs/vitepress',
docsDir: 'docs',
docsBranch: 'main',
editLinks: true,
editLinkText: 'Edit this page on GitHub',
lastUpdated: 'Last Updated',
nav: nav(),
sidebar: {
'/guide/': sidebarGuide(),
'/config/': sidebarConfig()
},
editLink: {
repo: 'vuejs/vitepress',
branch: 'next',
dir: 'docs',
text: 'Edit this page on GitHub'
},
socialLinks: [
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
],
footer: {
message: 'Released under the MIT License.',
copyright: 'Copyright © 2019-present Evan You'
},
algolia: {
appId: '8J64VVRP8K',
@ -21,73 +38,89 @@ export default defineConfig({
},
carbonAds: {
carbon: 'CEBDT27Y',
custom: 'CKYD62QM',
code: 'CEBDT27Y',
placement: 'vuejsorg'
},
nav: [
{ text: 'Guide', link: '/', activeMatch: '^/$|^/guide/' },
{
text: 'Config Reference',
link: '/config/basics',
activeMatch: '^/config/'
},
{
text: 'Release Notes',
link: 'https://github.com/vuejs/vitepress/releases'
}
],
sidebar: {
'/guide/': getGuideSidebar(),
'/config/': getConfigSidebar(),
'/': getGuideSidebar()
}
}
})
function getGuideSidebar() {
function nav() {
return [
{ text: 'Guide', link: '/guide/what-is-vitepress', activeMatch: '/guide/' },
{ text: 'Configs', link: '/config/introduction', activeMatch: '/config/' },
{
text: 'Changelog',
link: 'https://github.com/vuejs/vitepress/blob/main/CHANGELOG.md'
}
]
}
function sidebarGuide() {
return [
{
text: 'Introduction',
children: [
{ text: 'What is VitePress?', link: '/' },
collapsible: true,
items: [
{ text: 'What is VitePress?', link: '/guide/what-is-vitepress' },
{ text: 'Getting Started', link: '/guide/getting-started' },
{ text: 'Configuration', link: '/guide/configuration' },
{ text: 'Asset Handling', link: '/guide/assets' },
{ text: 'Markdown Extensions', link: '/guide/markdown' },
{ text: 'Using Vue in Markdown', link: '/guide/using-vue' },
{ text: 'Deploying', link: '/guide/deploy' }
{ text: 'Deploying', link: '/guide/deploying' }
]
},
{
text: 'Advanced',
children: [
text: 'Writing',
collapsible: true,
items: [
{ text: 'Markdown', link: '/guide/markdown' },
{ text: 'Asset Handling', link: '/guide/asset-handling' },
{ text: 'Frontmatter', link: '/guide/frontmatter' },
{ text: 'Theming', link: '/guide/theming' },
{ text: 'API Reference', link: '/guide/api' },
{ text: 'Using Vue in Markdown', link: '/guide/using-vue' },
{ text: 'API Reference', link: '/guide/api' }
]
},
{
text: 'Theme',
collapsible: true,
items: [
{ text: 'Introduction', link: '/guide/theme-introduction' },
{ text: 'Nav', link: '/guide/theme-nav' },
{ text: 'Sidebar', link: '/guide/theme-sidebar' },
{ text: 'Prev Next Link', link: '/guide/theme-prev-next-link' },
{ text: 'Edit Link', link: '/guide/theme-edit-link' },
{ text: 'Last Updated', link: '/guide/theme-last-updated' },
{ text: 'Layout', link: '/guide/theme-layout' },
{ text: 'Homepage', link: '/guide/theme-homepage' },
{ text: 'Footer', link: '/guide/theme-footer' },
{ text: 'Search', link: '/guide/theme-search' },
{ text: 'Carbon Ads', link: '/guide/theme-carbon-ads' }
]
},
{
text: 'Migrations',
collapsible: true,
items: [
{
text: 'Migration from VuePress',
link: '/guide/migration-from-vuepress'
},
{
text: 'Differences from Vuepress',
link: '/guide/differences-from-vuepress'
text: 'Migration from VitePress 0.x',
link: '/guide/migration-from-vitepress-0'
}
]
}
]
}
function getConfigSidebar() {
function sidebarConfig() {
return [
{
text: 'App Config',
children: [{ text: 'Basics', link: '/config/basics' }]
},
{
text: 'Theme Config',
children: [
{ text: 'Homepage', link: '/config/homepage' },
{ text: 'Algolia Search', link: '/config/algolia-search' },
{ text: 'Carbon Ads', link: '/config/carbon-ads' }
text: 'Config',
items: [
{ text: 'Introduction', link: '/config/introduction' },
{ text: 'App Configs', link: '/config/app-configs' },
{ text: 'Theme Configs', link: '/config/theme-configs' },
{ text: 'Frontmatter Configs', link: '/config/frontmatter-configs' }
]
}
]

@ -1,53 +0,0 @@
# Theme Config: Algolia Search
The `themeConfig.algolia` option allows you to use [Algolia DocSearch](https://docsearch.algolia.com). To enable it, you need to provide at least appId, apiKey and indexName:
```js
module.exports = {
themeConfig: {
algolia: {
appId: 'your_app_id',
apiKey: 'your_api_key',
indexName: 'index_name'
}
}
}
```
For more options, check out [Algolia DocSearch's documentation](https://docsearch.algolia.com/docs/api/). You can pass any extra option alongside other options, e.g. passing `searchParameters`:
```js
module.exports = {
themeConfig: {
algolia: {
appId: 'your_app_id',
apiKey: 'your_api_key',
indexName: 'index_name',
searchParameters: {
facetFilters: ['tags:guide,api']
}
}
}
}
```
## Internationalization (i18n)
If you have multiple locales in your documentation and you have defined a `locales` object in your `themeConfig`:
```js
module.exports = {
themeConfig: {
locales: {
// ...
},
algolia: {
appId: 'your_app_id',
apiKey: 'your_api_key',
indexName: 'index_name'
}
}
}
```
VitePress will automatically add a `lang` _facetFilter_ to the `searchParameters.facetFilter` array with the correct language value. Algolia automatically adds the correct facet filter based on the `lang` attribute on the `<html>` tag. This will match search results with the currently viewed language of the page.

@ -0,0 +1,161 @@
# App Configs
App configs are where you can define the global settings of the site. App configs define fundamental settings that are not only limited to the theme configs such as configuration for "base directory", or the "title" of the site.
```ts
export default {
// These are app level configs.
lang: 'en-US',
title: 'VitePress',
description: 'Vite & Vue powered static site generator.',
...
}
```
## base
- Type: `string`
- Default: `/`
The base URL the site will be deployed at. You will need to set this if you plan to deploy your site under a sub path, for example, GitHub pages. If you plan to deploy your site to `https://foo.github.io/bar/`, then you should set base to `'/bar/'`. It should always start and end with a slash.
The base is automatically prepended to all the URLs that start with / in other options, so you only need to specify it once.
```ts
export default {
base: '/base/'
}
```
## lang
- Type: `string`
- Default: `en-US`
The lang attribute for the site. This will render as a `<html lang="en-US">` tag in the page HTML.
```ts
export default {
lang: 'en-US'
}
```
## title
- Type: `string`
- Default: `VitePress`
Title for the site. This will be displayed in the nav bar. Also used as the suffix for all page titles unless `titleTemplate` is defined.
```ts
export default {
title: 'VitePress'
}
```
## titleTemplate
- Type: `string | boolean`
The suffix for the title. For example, if you set `title` as `VitePress` and set `titleTemplate` as `My Site`, the html title becomes `VitePress | My Site`.
Set `false` to disable the feature. If the option is `undefined`, then the value of `title` option will be used.
```ts
export default {
title: 'VitePress',
titleTemplate: 'Vite & Vue powered static site generator'
}
```
## description
- Type: `string`
- Default: `A VitePress site`
Description for the site. This will render as a `<meta>` tag in the page HTML.
```ts
export default {
description: 'A VitePress site'
}
```
## markdown
- Type: `MarkdownOption`
Configre Markdown parser options. VitePress uses [Markdown-it](https://github.com/markdown-it/markdown-it) as the parser, and [Shiki](https://shiki.matsu.io/) to highlight language syntax. Inside this option, you may pass various Markdown related options to fit your needs.
```js
export default {
markdown: {
theme: 'material-palenight',
lineNumbers: true
}
}
```
Below shows the the full option you may define within this object.
```ts
interface MarkdownOptions extends MarkdownIt.Options {
// Syntax highlight theme for Shiki.
// See: https://github.com/shikijs/shiki/blob/main/docs/themes.md#all-themes
theme?: Shiki.Theme | { light: Shiki.Theme, dark: Shiki.Theme }
// Enable line numbers in code block.
lineNumbers?: boolean
// markdown-it-anchor plugin options.
// See: https://github.com/valeriangalliat/markdown-it-anchor
anchor?: {
permalink?: anchor.AnchorOptions['permalink']
}
// markdown-it-attrs plugin options.
// See: https://github.com/arve0/markdown-it-attrs
attrs?: {
leftDelimiter?: string
rightDelimiter?: string
allowedAttributes?: string[]
disable?: boolean
}
// markdown-it-toc-done-right plugin options
// https://github.com/nagaozen/markdown-it-toc-done-right
toc?: any
// Configure the Markdown-it instance to fully customize
// how it works.
config?: (md: MarkdownIt) => void
}
```
## appearance
- Type: `boolean`
- Default: `true`
Whether to enable "Dark Mode" or not. If the option is set to `true`, it adds `.dark` class to the `<html>` tag depending on the users preference.
It also injects inline script that tries to read users settings from local storage by `vitepress-theme-appearance` key and restores users preferred color mode.
```ts
export default {
appearance: true
}
```
## lastUpdated
- Type: `boolean`
- Default: `false`
Use git commit to get the timestamp. This option enables the default theme to display the page's last updated time. You can customize the text via [`themeConfig.lastUpdatedText`](theme-configs#lastupdatedtext) option.
```ts
export default {
lastUpdated: true
}
```

@ -1,59 +0,0 @@
# App Config: Basics
::: tip
The config reference is incomplete since the config format may still receive further changes. For a complete reference of the current available options, refer to [config.ts](https://github.com/vuejs/vitepress/blob/45b65ce8b63bd54f345bfc3383eb2416b6769dc9/src/node/config.ts#L30-L65).
:::
## base
- Type: `string`
- Default: `/`
The base URL the site will be deployed at. You will need to set this if you plan to deploy your site under a sub path, for example, GitHub pages. If you plan to deploy your site to `https://foo.github.io/bar/`, then you should set base to `'/bar/'`. It should always start and end with a slash.
The `base` is automatically prepended to all the URLs that start with `/` in other options, so you only need to specify it once.
```js
module.exports = {
base: '/base/'
}
```
## lang
- Type: `string`
- Default: `en-US`
The `lang` attribute for the site. This will render as a `<html lang="en-US">` tag in the page HTML.
```js
module.exports = {
lang: 'en-US'
}
```
## title
- Type: `string`
- Default: `VitePress`
Title for the site. This will be the suffix for all page titles, and displayed in the navbar.
```js
module.exports = {
title: 'VitePress'
}
```
## description
- Type: `string`
- Default: `A VitePress site`
Description for the site. This will render as a `<meta>` tag in the page HTML.
```js
module.exports = {
description: 'A VitePress site'
}
```

@ -1,15 +0,0 @@
# Theme Config: Carbon Ads
VitePress has built in native support for [Carbon Ads](https://www.carbonads.net). By defining the Carbon Ads credentials in config, VitePress will display ads on the page.
```js
module.exports = {
themeConfig: {
carbonAds: {
carbon: 'your-carbon-key',
custom: 'your-carbon-custom',
placement: 'your-carbon-placement'
}
}
}
```

@ -0,0 +1,188 @@
# Frontmatter Configs
Frontmatter enables page based configuration. On every markdown, youre free to add Any settings to override any global app or theme configs. Also, there are configs which you can only define in Frontmatter.
```yaml
---
title: Docs with VitePress
editLink: true
---
```
You may access frontmatter by `$frontmatter` helper inside any markdown file.
```md
{{ $frontmatter.title }}
```
## title
- Type: `string`
Title for the page. It's same as [config.title](../config/app-configs#title), and it overrides the app config.
```yaml
---
title: VitePress
---
```
## titleTemplate
- Type: `string | boolean`
The suffix for the title. It's same as [config.titleTemplate](../config/app-configs#titleTemplate), and it overrides the app config.
```yaml
---
title: VitePress,
titleTemplate: Vite & Vue powered static site generator.
---
```
## description
- Type: `string`
Title for the page. It's same as [config.description](../config/app-configs#description), and it overrides the app config.
```yaml
---
description: VitePress
---
```
### head
- Type: `Head[]`
Specify extra head tags to be injected:
```yaml
---
head:
- - meta
- name: description
content: hello
- - meta
- name: keywords
content: super duper SEO
---
```
```ts
type Head =
| [string, Record<string, string>]
| [string, Record<string, string>, string]
```
## layout
- Type: `doc | home | page`
- Default: `doc`
Determines the layout of the page.
- `doc` - It applies default documentation styles to the markdown content.
- `home` - Special layout for "Home Page". You may add extra options such as `hero` and `features` to rappidly create beautiful landing page.
- `page` - Behave similar to `doc` but it aplies no styles to the content. Useful when you want to create a fully custom page.
```yaml
---
layout: doc
---
```
## hero
- Type: `Hero`
This option only take effect when `layout` is set to `home`.
It defines contents of home hero section.
```yaml
---
layout: home
hero:
name: VuePress
text: Vite & Vue powered static site generator.
tagline: Lorem ipsum...
actions:
- theme: brand
text: Get Started
link: /guide/what-is-vitepress
- theme: alt
text: View on GitHub
link: https://github.com/vuejs/vitepress
---
```
```ts
interface Hero {
// The string shown top of `text`. Comes with brand color
// and expected to be short, such as product name.
name?: string
// The main text for the hero section. This will be defined
// as `h1` tag.
text: string
// Tagline displayed below `text`.
tagline?: string
// Action buttons to display in home hero section.
actions?: HeroAction[]
}
interface HeroAction {
// Color theme of the button. Defaults to `brand`.
theme?: 'brand' | 'alt'
// Label of the button.
text: string
// Destination link of the button.
link: string
}
```
## features
- Type: `Feature[]`
This option only take effect when `layout` is set to `home`.
It defines items to display in features section.
```yaml
---
layout: home
features:
- icon: ⚡️
title: Vite, The DX that can't be beat
details: Lorem ipsum...
- icon: 🖖
title: Power of Vue meets Markdown
details: Lorem ipsum...
- icon: 🛠️
title: Simple and minimal, always
details: Lorem ipsum...
---
```
```ts
interface Feature {
// Show icon on each feature box. Currently, only emojis
// are supported.
icon?: string
// Title of the feature.
title: string
// Details of the feature.
details: string
}
```

@ -1,23 +0,0 @@
# Theme Config: Homepage
VitePress provides a homepage layout. To use it, specify `home: true` plus some other metadata in your root `index.md`'s [YAML frontmatter](../guide/frontmatter). This is an example of how it works:
```yaml
---
home: true
heroImage: /logo.png
heroAlt: Logo image
heroText: Hero Title
tagline: Hero subtitle
actionText: Get Started
actionLink: /guide/
features:
- title: Simplicity First
details: Minimal setup with markdown-centered project structure helps you focus on writing.
- title: Vue-Powered
details: Enjoy the dev experience of Vue + webpack, use Vue components in markdown, and develop custom themes with Vue.
- title: Performant
details: VitePress generates pre-rendered static HTML for each page, and runs as an SPA once a page is loaded.
footer: MIT Licensed | Copyright © 2019-present Evan You
---
```

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

@ -0,0 +1,230 @@
# Theme Configs
Theme configs let you customize your theme. You can define theme configs by adding `themeConfig` key to the config file.
```ts
export default {
lang: 'en-US',
title: 'VitePress',
description: 'Vite & Vue powered static site generator.',
// Theme related configurations.
themeConfig: {
logo: '/logo.svg',
nav: [...],
sidebar: { ... }
}
}
```
Here it describes the settings for the VitePress default theme. If you're using a custom theme created by others, these settings may not have any effect, or might behave differently.
## logo
- Type: `string`
Logo file to display in nav bar, right before the site title.
```ts
export default {
themeConfig: {
logo: '/logo.svg'
}
}
```
## siteTitle
- Type: `string | false`
You can customize this item to replace the default site title (`title` in app config) in nav. When set to `false`, title in nav will be disabled. Useful when you have `logo` that already contains the site title text.
```ts
export default {
themeConfig: {
siteTitle: 'Hello World'
}
}
```
## nav
- Type: `NavItem`
The configuration for the nav menu item. You may learn more details at [Theme: Nav](../guide/theme-nav#navigation-links).
```js
export default {
themeConfig: {
nav: [
{ text: 'Guide', link: '/guide' },
{
text: 'Dropdown Menu',
items: [
{ text: 'Item A', link: '/item-1' },
{ text: 'Item B', link: '/item-2' },
{ text: 'Item C', link: '/item-3' }
]
}
]
}
}
```
```ts
type NavItem = NavItemWithLink | NavItemWithChildren
type NavItemWithLink = {
text: string
link: string
activeMatch?: string
}
interface NavItemWithChildren {
text?: string
items: NavItemWithLink[]
}
```
## sidebar
- Type: `Sidebar`
The configuration for the sidebar menu item. You may learn more details at [Theme: Nav](../guide/theme-sidebar).
```js
export default {
themeConfig: {
sidebar: [
{
text: 'Guide',
items: [
{ text: 'Introduction', link: '/introduction' },
{ text: 'Getting Started', link: '/getting-started' },
...
]
}
]
}
}
```
```ts
type Sidebar = SidebarGroup[] | SidebarMulti
interface SidebarMulti {
[path: string]: SidebarGroup[]
}
interface SidebarGroup {
text: string
items: SidebarItem[]
collapsible?: boolean
collapsed?: boolean
}
interface SidebarItem {
text: string
link: string
}
```
## socialLinks
- Type: `SocialLink`
You may define this option to show your social account links with icons in nav.
```js
export default {
themeConfig: {
socialLinks: [
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' },
{ icon: 'twitter', link: '...' },
{ icon: 'discord', link: '...' }
]
}
}
```
```ts
interface SocialLink {
icon: SocialLinkIcon
link: string
}
type SocialLinkIcon =
| 'discord'
| 'facebook'
| 'github'
| 'instagram'
| 'linkedin'
| 'slack'
| 'twitter'
| 'youtube'
```
## footer
- Type: `Footer`
Footer configuration. You can add a message and copyright. The footer will displayed only when the page doesn't contain sidebar due to design reason.
```ts
export default {
themeConfig: {
footer: {
message: 'Released under the MIT License.',
copyright: 'Copyright © 2019-present Evan You'
}
}
}
```
```ts
export interface Footer {
message?: string
copyright?: string
}
```
## lastUpdatedText
- Type: `string`
- Default: `Last updated`
The prefix text showing right before the last updated time.
```ts
export default {
themeConfig: {
lastUpdatedText: 'Updated Date'
}
}
```
## carbonAds
- Type: `CarbonAds`
A option to display [Carbon Ads](https://www.carbonads.net/).
```ts
export default {
themeConfig: {
carbonAds: {
code: 'your-carbon-code',
placement: 'your-carbon-placement'
}
}
}
```
```ts
export interface CarbonAds {
code: string,
placement: string
}
```
Learn more in [Theme: Carbon Ads](../guide/theme-carbon-ads)

@ -1,12 +1,12 @@
# API Reference
## Helper Methods
VitePress offers several built in API to let you access app data. VitePress also comes with few built-in component that can be used globally.
The following methods are globally importable from `vitepress` and are typically used in custom theme Vue components. However, they are also usable inside `.md` pages because markdown files are compiled into Vue single-file components.
The helper methods are globally importable from `vitepress` and are typically used in custom theme Vue components. However, they are also usable inside `.md` pages because markdown files are compiled into Vue single-file components.
Methods that start with `use*` indicates that it is a [Vue 3 Composition API](https://vuejs.org/guide/introduction.html#composition-api) function that can only be used inside `setup()` or `<script setup>`.
### `useData`
## `useData`
Returns page-specific data. The returned object has the following type:
@ -16,9 +16,9 @@ interface VitePressData {
page: Ref<PageData>
theme: Ref<any> // themeConfig from .vitepress/config.js
frontmatter: Ref<PageData['frontmatter']>
lang: Ref<string>
title: Ref<string>
description: Ref<string>
lang: Ref<string>
localePath: Ref<string>
}
```
@ -28,15 +28,16 @@ interface VitePressData {
```vue
<script setup>
import { useData } from 'vitepress'
const { theme } = useData()
</script>
<template>
<h1>{{ theme.heroText }}</h1>
<h1>{{ theme.footer.copyright }}</h1>
</template>
```
### `useRoute`
## `useRoute`
Returns the current route object with the following type:
@ -48,7 +49,7 @@ interface Route {
}
```
### `useRouter`
## `useRouter`
Returns the VitePress router instance so you can programmatically navigate to another page.
@ -59,19 +60,15 @@ interface Router {
}
```
### `withBase`
## `withBase`
- **Type**: `(path: string) => string`
Appends the configured [`base`](../config/basics#base) to a given URL path. Also see [Base URL](./assets#base-url).
## Global Components
VitePress comes with few built-in component that can be used globally. You may use these components in your markdown or your custom theme configuration.
Appends the configured [`base`](../config/app-configs#base) to a given URL path. Also see [Base URL](./asset-handling#base-url).
### `<Content/>`
## `<Content />`
The `<Content/>` component displays the rendered markdown contents. Useful [when creating your own theme](./theming).
The `<Content />` component displays the rendered markdown contents. Useful [when creating your own theme](./theme-introduction).
```vue
<template>
@ -80,15 +77,15 @@ The `<Content/>` component displays the rendered markdown contents. Useful [when
</template>
```
### `<ClientOnly/>`
## `<ClientOnly />`
The `<ClientOnly/>` component renders its slot only at client side.
The `<ClientOnly />` component renders its slot only at client side.
Because VitePress applications are server-rendered in Node.js when generating static builds, any Vue usage must conform to the universal code requirements. In short, make sure to only access Browser / DOM APIs in beforeMount or mounted hooks.
If you are using or demoing components that are not SSR-friendly (for example, contain custom directives), you can wrap them inside the `ClientOnly` component.
```html
```vue-html
<ClientOnly>
<NonSSRFriendlyComponent />
</ClientOnly>

@ -1,10 +1,8 @@
# Configuration
## Overview
Without any configuration, the page is pretty minimal, and the user has no way to navigate around the site. To customize your site, let's first create a `.vitepress` directory inside your docs directory. This is where all VitePress-specific files will be placed. Your project structure is probably like this:
Without any configuration, the page is pretty minimal, and the user has no way to navigate around the site. To customize your site, lets first create a `.vitepress` directory inside your docs directory. This is where all VitePress-specific files will be placed. Your project structure is probably like this:
```bash
```
.
├─ docs
│ ├─ .vitepress
@ -22,58 +20,8 @@ export default {
}
```
Check out the [Config Reference](../config/basics) for a full list of options.
## Config Intellisense
Since VitePress ships with TypeScript typings, you can leverage your IDE's intellisense with jsdoc type hints:
```js
/**
* @type {import('vitepress').UserConfig}
*/
const config = {
// ...
}
export default config
```
Alternatively, you can use the `defineConfig` helper at which should provide intellisense without the need for jsdoc annotations:
```js
import { defineConfig } from 'vitepress'
export default defineConfig({
// ...
})
```
VitePress also directly supports TS config files. You can use `.vitepress/config.ts` with the `defineConfig` helper as well.
## Typed Theme Config
By default, `defineConfig` helper leverages the theme config type from default theme:
In the above example, the site will have the title of `VitePress`, and `Just playing around` as description meta tag.
```ts
import { defineConfig } from 'vitepress'
Learn everything about VitePress features at [Theme: Introduction](./theme-introduction) to find how to configure specific features with in this config file.
export default defineConfig({
themeConfig: {
// Type is `DefaultTheme.Config`
}
})
```
If you use a custom theme and want type checks for the theme config, you'll need to use `defineConfigWithTheme` instead, and pass the config type for your custom theme via a generic argument:
```ts
import { defineConfigWithTheme } from 'vitepress'
import { ThemeConfig } from 'your-theme'
export default defineConfigWithTheme<ThemeConfig>({
themeConfig: {
// Type is `ThemeConfig`
}
})
```
You may also find all configuration references at [Configs](../config/introduction).

@ -1,7 +1,3 @@
---
sidebarDepth: 3
---
# Deploying
The following guides are based on some shared assumptions:
@ -19,7 +15,7 @@ The following guides are based on some shared assumptions:
}
```
## Building The Docs
## Build and test locally
You may run `yarn docs:build` command to build the docs.
@ -29,8 +25,6 @@ $ yarn docs:build
By default, the build output will be placed at `.vitepress/dist`. You may deploy this `dist` folder to any of your preferred platforms.
### Testing The Docs Locally
Once you've built the docs, you may test them locally by running `yarn docs:serve` command.
```bash
@ -94,7 +88,7 @@ cd -
You can also run the above script in your CI setup to enable automatic deployment on each push.
:::
### GitHub Pages and Travis CI
## GitHub Pages and Travis CI
1. Set the correct `base` in `docs/.vitepress/config.js`.
@ -141,9 +135,8 @@ deploy:
3. Create a file called `.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
image: node:16.5.0
image: node:10.22.0
pages:
stage: deploy
cache:
paths:
- node_modules/
@ -153,8 +146,8 @@ pages:
artifacts:
paths:
- public
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
only:
- main
```
## Netlify

@ -1,119 +0,0 @@
---
sidebarDepth: 2
---
# Differences from VuePress
VitePress and VuePress have different [design goals](../index.md). Both projects share similar config naming conventions. VitePress aims to have the bare minimum features needed for authoring docs. Other features are pushed to Themes. On the other hand, VuePress has more features out-of-the-box or enabled by its ecosystem of plugins.
::: tip
If you are using VuePress, there is no need to migrate to VitePress. Both projects are going to continue to co-exist for the foreseeable future.
:::
::: warning
Note this is early WIP! Currently, the focus is on making Vite stable and feature-complete first. It is not recommended to use this for anything serious yet.
:::
In case you decide to move your project to VitePress, this is a list of differences from [VuePress v1.7.1](https://github.com/vuejs/vuepress/releases/tag/v1.7.1) that you need to take into account.
## General
- Missing
- YAML and TOML are not supported formats for site config. Only javascript is supported for `.vitepress/config.js`
- [Plugins](https://vuepress.vuejs.org/plugin/) support, features are implemented in themes
- [permalink support](https://vuepress.vuejs.org/guide/permalinks.html)
- `.vitepress/templates`
- Components in `.vitepress/components` [are not auto registered as global components](https://vuepress.vuejs.org)
- Differences
- [Public files](https://vuepress.vuejs.org/guide/assets.html#public-files) that are directly copied to dist root moved from `.vitepress/public/` is `public/`
- [styling](https://vuepress.vuejs.org/config/#styling) `.vitepress/styles/index.styl` and `.vitepress/styles/palette.styl` is not supported. See [Customizing CSS](./theming#customizing-css).
- [App Level Enhancements](https://vuepress.vuejs.org/guide/basic-config.html#app-level-enhancements) API, app enhancements `.vitepress/enhanceApp.js` is now done in `.vitepress/theme/index.js`. See [Extending the Default Theme](./theming#extending-the-default-theme).
## Markdown
- Missing
- Support for [toml in frontmatter](https://vuepress.vuejs.org/guide/frontmatter.html#alternative-frontmatter-formats)
- [details block](https://vuepress.vuejs.org/guide/markdown.html#custom-containers)
- [markdown slots](https://vuepress.vuejs.org/guide/markdown-slot.html)
- `~` prefix to explicitly specify a url is a [webpack module request](https://vuepress.vuejs.org/guide/assets.html#relative-urls)
## Site Config
- Missing
- `temp`
- `dest`
- [`theme` from a dependency](https://vuepress.vuejs.org/theme/using-a-theme.html#using-a-theme-from-a-dependency)
- `permalink`
- [`port`](https://vuepress.vuejs.org/config/#port)
- [`shouldPrefetch`](https://vuepress.vuejs.org/config/#shouldprefetch)
- [`cache`](https://vuepress.vuejs.org/config/#cache)
- [`extraWatchFiles`](https://vuepress.vuejs.org/config/#extrawatchfiles)
- [`patterns`](https://vuepress.vuejs.org/config/#patterns)
- [`plugins`](https://vuepress.vuejs.org/config/#pluggable)
- [`markdown.pageSuffix`](https://vuepress.vuejs.org/config/#markdown-pagesuffix)
- [`markdown.slugify`](https://vuepress.vuejs.org/config/#markdown-slugify)
- [`markdown.plugins`](https://vuepress.vuejs.org/config/#markdown-plugins)
- [`markdown.extractHeaders`](https://vuepress.vuejs.org/config/#markdown-extractheaders)
- `markdown.extendMarkdown` to `markdown.config`
- `configureWebpack`, `chainWebpack`, `postcss`, `Stylus`, `scss`, `Sass`, `less` configs
- [`evergreen`](https://vuepress.vuejs.org/config/#evergreen)
## Default Theme Config
- Missing
- [`smoothScroll`](https://vuepress.vuejs.org/theme/default-theme-config.html#smooth-scrolling)
- [`displayAllHeaders`](https://vuepress.vuejs.org/theme/default-theme-config.html#displaying-header-links-of-all-pages)
- [`activeHeaderLinks`](https://vuepress.vuejs.org/theme/default-theme-config.html#active-header-links)
- `sidebarDepth` and `initialOpenGroupIndex` for [sidebar groups](https://vuepress.vuejs.org/theme/default-theme-config.html#sidebar-groups)
- Differences
- `searchMaxSuggestions` is `search.maxSuggestions`
- `algolia` is `search.algolia`
- `searchPlaceholder` is `search.placeholder`
## Default Theme
- Missing
- [`<code-group>` and `<code-block>`](https://vuepress.vuejs.org/theme/default-theme-config.html#code-groups-and-code-blocks)
## Computed Globals
- Missing
- `$lang`
- `$localePath`
## Frontmatter Predefined Variables
- Missing
- `description`
- [`meta`](https://vuepress.vuejs.org/guide/frontmatter.html#meta)
- [`metaTitle`](https://vuepress.vuejs.org/guide/frontmatter.html#predefined-variables)
- `lang`
- [`layout`](https://vuepress.vuejs.org/guide/frontmatter.html#layout)
- [`permalink`](https://vuepress.vuejs.org/guide/frontmatter.html#predefined-variables)
- [`canonicalUrl`](https://vuepress.vuejs.org/guide/frontmatter.html#predefined-variables)
## Frontmatter Default Theme Variables
- Missing
- `prev`, `next`
- [`search`](https://vuepress.vuejs.org/guide/frontmatter.html#search)
- [`tags`](https://vuepress.vuejs.org/guide/frontmatter.html#tags)
- [`pageClass`](https://vuepress.vuejs.org/theme/default-theme-config.html#custom-page-class)
- [`layout`](https://vuepress.vuejs.org/theme/default-theme-config.html#custom-layout-for-specific-pages)
## siteData
- Missing
- [`pages`](https://vuepress.vuejs.org/theme/writing-a-theme.html#site-and-page-metadata)
## pageData
- Missing
- `key`
- `path`
- `regularPath`
## Global Components
- Missing
- [`<Badge>`](https://vuepress.vuejs.org/guide/using-vue.html#badge)

@ -9,7 +9,7 @@ editLink: true
---
```
Between the triple-dashed lines, you can set [predefined variables](#predefined-variables), or even create custom ones of your own. These variables can be used via the special <code>$frontmatter</code> variable.
Between the triple-dashed lines, you can set [predefined variables](../config/frontmatter-configs), or even create custom ones of your own. These variables can be used via the special <code>$frontmatter</code> variable.
Heres an example of how you could use it in your Markdown file:
@ -24,7 +24,7 @@ editLink: true
Guide content
```
## Alternative frontmatter Formats
## Alternative Frontmatter Formats
VitePress also supports JSON frontmatter syntax, starting and ending in curly braces:
@ -36,52 +36,3 @@ VitePress also supports JSON frontmatter syntax, starting and ending in curly br
}
---
```
## Predefined Variables
### title
- Type: `string`
- Default: `h1_title || siteData.title`
Title of the current page.
### head
- Type: `array`
- Default: `undefined`
Specify extra head tags to be injected:
```yaml
---
head:
- - meta
- name: description
content: hello
- - meta
- name: keywords
content: super duper SEO
---
```
### navbar
- Type: `boolean`
- Default: `undefined`
You can disable the navbar on a specific page with `navbar: false`
### sidebar
- Type: `boolean|'auto'`
- Default: `undefined`
You can decide to show the sidebar on a specific page with `sidebar: auto` or disable it with `sidebar: false`
### editLink
- Type: `boolean`
- Default: `undefined`
Define if this page should include an edit link.

@ -1,51 +1,87 @@
# Getting Started
This section will help you build a basic VitePress documentation site from ground up. If you already have an existing project and would like to keep documentation inside the project, start from Step 3.
This section will help you build a basic VitePress documentation site from ground up. If you already have an existing project and would like to keep documentation inside the project, start from Step 2.
- **Step. 1:** Create and change into a new directory.
::: warning
VitePress is currently in `alpha` status. It is already suitable for out-of-the-box documentation use, but the config and theming API may still change between minor releases.
:::
```bash
$ mkdir vitepress-starter && cd vitepress-starter
```
## Step. 1: Create a new project
- **Step. 2:** Initialize with your preferred package manager.
Create and change into a new directory.
```bash
$ yarn init
```
```bash
$ mkdir vitepress-starter && cd vitepress-starter
```
- **Step. 3:** Install VitePress locally.
Then, initialize with your preferred package manager.
```bash
$ yarn add --dev vitepress
```
```bash
$ yarn init
```
- **Step. 4:** Create your first document.
## Step. 2: Install VitePress
```bash
$ mkdir docs && echo '# Hello VitePress' > docs/index.md
```
Add VitePress as a dependency for the project.
- **Step. 5:** Add some scripts to `package.json`.
```bash
$ yarn add --dev vitepress
```
```json
{
"scripts": {
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:serve": "vitepress serve docs"
}
}
```
Create your first document.
- **Step. 6:** Serve the documentation site in the local server.
```bash
$ mkdir docs && echo '# Hello VitePress' > docs/index.md
```
```bash
$ yarn docs:dev
```
## Step. 3: Boot up dev environment
VitePress will start a hot-reloading development server at `http://localhost:3000`.
Add some scripts to `package.json`.
By now, you should have a basic but functional VitePress documentation site.
```json
{
...
"scripts": {
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:serve": "vitepress serve docs"
},
...
}
```
When your documentation site starts to take shape, be sure to read the [deployment guide](./deploy).
Serve the documentation site in the local server.
```bash
$ yarn docs:dev
```
VitePress will start a hot-reloading development server at `http://localhost:3000`.
## Step. 4: Add more pages
Let's add another page to the site. Create a file name `getting-started.md` along with `index.md` you've created in Step. 2. Now your directory structure should look like this.
```
.
├─ docs
│ ├─ getting-started.md
│ └─ index.md
└─ package.json
```
Then, try to access `http://localhost:3000/getting-started` and you should see the content of `getting-started` is shown.
This is how VitePress works basically. The directory structure corresponds with the URL path. You add files, and just try to access it.
## What's next?
By now, you should have a basic but functional VitePress documentation site. But currently, the user has no way to navigate around the site because it's missing for example sidebar menu we have on this site.
To enable those navigations, we must add some configurations to the site. Head to [configuration guide](./configuration) to learn how to configure VitePress.
If you would like to know more about what you can do within the page, for example, writing markdown contents, or using Vue Component, check out the "Writing" section of the docs. [Markdown guide](./markdown) would be a greate starting point.
If you want to know how to customize how the site looks (Theme), and find out the features VitePress's default theme provides, visit [Theme: Introduction](./theme-introduction).
When your documentation site starts to take shape, be sure to read the [deployment guide](./deploying).

@ -1,32 +0,0 @@
# Global Component
VitePress comes with few built-in component that can be used globally. You may use these components in your markdown or your custom theme configuration.
## Content
The `Content` component displays the rendered markdown contents. Useful [when creating your own theme](./theming).
```vue
<template>
<h1>Custom Layout!</h1>
<Content />
</template>
```
## ClientOnly
The `ClientOnly` component renderes its slot only at client side.
Because VitePress applications are server-rendered in Node.js when generating static builds, any Vue usage must conform to the universal code requirements. In short, make sure to only access Browser / DOM APIs in beforeMount or mounted hooks.
If you are using or demoing components that are not SSR-friendly (for example, contain custom directives), you can wrap them inside the `ClientOnly` component.
```html
<ClientOnly>
<NonSSRFriendlyComponent />
</ClientOnly>
```
## OutboundLink
The indicator `OutboundLink` is used to denote external links. In VitePress, this component has been followed by every external link.

@ -1,15 +1,15 @@
---
sidebarDepth: 3
---
# Markdown Extensions
VitePress comes with built in Markdown Extensions.
## Header Anchors
Headers automatically get anchor links applied. Rendering of anchors can be configured using the `markdown.anchor` option.
## Links
Both internal and external links gets special treatments.
### Internal Links
Internal links are converted to router link for SPA navigation. Also, every `index.md` contained in each sub-directory will automatically be converted to `index.html`, with corresponding URL `/`.
@ -123,51 +123,53 @@ Custom containers can be defined by their types, titles, and contents.
**Input**
```md
::: tip
This is a tip
::: info
This is an info box.
:::
::: info
This is an info box
::: tip
This is a tip.
:::
::: warning
This is a warning
This is a warning.
:::
::: danger
This is a dangerous warning
This is a dangerous warning.
:::
::: details
This is a details block, which does not work in Internet Explorer or old versions of Edge.
This is a details block.
:::
```
**Output**
::: tip
This is a tip
::: info
This is an info box.
:::
::: info
This is an info box
::: tip
This is a tip.
:::
::: warning
This is a warning
This is a dangerous warning.
:::
::: danger
This is a dangerous warning
This is a dangerous warning.
:::
::: details
This is a details block, which does not work in Internet Explorer or Edge.
This is a details block.
:::
### Custom Title
You may set custom title by appending the text right after the "type" of the container.
**Input**
````md
@ -176,11 +178,9 @@ Danger zone, do not proceed
:::
::: details Click me to view the code
```js
console.log('Hello, VitePress!')
```
:::
````
@ -191,16 +191,14 @@ Danger zone, do not proceed
:::
::: details Click me to view the code
```js
console.log('Hello, VitePress!')
```
:::
## Syntax Highlighting in Code Blocks
VitePress uses [Prism](https://prismjs.com) to highlight language syntax in Markdown code blocks, using coloured text. Prism supports a wide variety of programming languages. All you need to do is append a valid language alias to the beginning backticks for the code block:
VitePress uses [Shiki](https://shiki.matsu.io/) to highlight language syntax in Markdown code blocks, using coloured text. Shiki supports a wide variety of programming languages. All you need to do is append a valid language alias to the beginning backticks for the code block:
**Input**
@ -213,17 +211,6 @@ export default {
```
````
**Output**
```js
export default {
name: 'MyComponent'
// ...
}
```
**Input**
````
```html
<ul>
@ -236,15 +223,24 @@ export default {
**Output**
```js
export default {
name: 'MyComponent'
// ...
}
```
```html
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
{{ todo.text }}
</li>
</ul>
```
A [list of valid languages](https://prismjs.com/#languages-list) is available on Prisms site.
A [list of valid languages](https://github.com/shikijs/shiki/blob/main/docs/languages.md) is available on Shikis repository.
You may also customize syntax highlight theme in app config. Please see [`markdown` options](../config/app-configs#markdown) for more details.
## Line Highlighting in Code Blocks
@ -283,7 +279,7 @@ In addition to a single line, you can also specify multiple single lines, ranges
**Input**
````
```js{1,4,6-8}
```js{1,4,6-7}
export default { // Highlighted
data () {
return {
@ -291,7 +287,7 @@ export default { // Highlighted
This line isn't highlighted,
but this and the next 2 are.`,
motd: 'VitePress is awesome',
lorem: 'ipsum',
lorem: 'ipsum'
}
}
}
@ -300,7 +296,7 @@ export default { // Highlighted
**Output**
```js{1,4,6-8}
```js{1,4,6-7}
export default { // Highlighted
data () {
return {
@ -319,46 +315,14 @@ export default { // Highlighted
You can enable line numbers for each code blocks via config:
```js
module.exports = {
export default {
markdown: {
lineNumbers: true
}
}
```
- Demo:
<picture>
<source srcset="../images/line-numbers-mobile.gif" media="(max-width: 719px)">
<img class="line-numbers-mobile-snap" src="../images/line-numbers-mobile.gif" alt="Image">
</picture>
<picture>
<source srcset="../images/line-numbers-desktop.png" media="(min-width: 720px)">
<img class="line-numbers-desktop-snap" src="../images/line-numbers-desktop.png" alt="Image">
</picture>
<style>
.line-numbers-mobile-snap {
margin: 0 -1.5rem;
width: 100vw;
max-width: none !important;
}
.line-numbers-desktop-snap {
display: none;
}
@media (min-width: 720px) {
.line-numbers-mobile-snap {
display: none;
}
.line-numbers-desktop-snap {
display: block;
}
}
</style>
Please see [`markdown` options](../config/app-configs#markdown) for more details.
## Import Code Snippets
@ -439,8 +403,8 @@ module.exports = {
permalink: anchor.permalink.headerLink()
},
// options for markdown-it-table-of-contents
toc: { includeLevel: [1, 2] },
// options for markdown-it-toc-done-right
toc: { level: [1, 2] },
config: (md) => {
// use more markdown-it plugins!
@ -449,3 +413,5 @@ module.exports = {
}
}
```
See full list of configurable properties in [Configs: App Configs](../config/app-configs#markdown).

@ -0,0 +1,23 @@
# Migration from VitePress 0.x
If you're coming from VitePress 0.x version, there're several breaking changes due to new features and enhancement. Please follow this guide to see how to migrate your app over to the latest VitePress.
## App Config
- The internationalization feature is not yet implemented.
## Theme Config
- `sidebar` option has changed its structure.
- `children` key is now named `items`.
- Top level item may not contain `link` at the moment. We're planning to bring it back.
- `repo`, `repoLabel`, `docsDir`, `docsBranch`, `editLinks`, `editLinkText` are removed in favor of more flexible api.
- For adding GitHub link with icon to the nav, use [Social Links](./theme-nav.html#navigation-links) feature.
- For adding "Edit this page" feature, use [Edit Link](./theme-edit-link) feature.
- `lastUpdated` option is now split into `config.lastUpdated` and `themeConfig.lastUpdatedText`.
- `carbonAds.carbon` is changed to `carbonAds.code`.
## Frontmatter Config
- `home: true` option has changed to `layout: home`. Also, many Homepage related settings have been modified to provide additional features. See [Homepage guide](./theme-homepage) for details.
- `footer` option is moved to [`themeConfig.footer`](../config/theme-configs#footer).

@ -0,0 +1,3 @@
# Migration from VuePress
Coming soon...

@ -0,0 +1,22 @@
# Carbon Ads
VitePress has built in native support for [Carbon Ads](https://www.carbonads.net/). By defining the Carbon Ads credentials in config, VitePress will display ads on the page.
```js
export default {
themeConfig: {
carbonAds: {
code: 'your-carbon-code',
placement: 'your-carbon-placement'
}
}
}
```
These values are used to call carbon CDN script as shown below.
```js
`//cdn.carbonads.com/carbon.js?serve=${code}&placement=${placement}`
```
To learn more about Carbon Ads configuration, please visit [Carbon Ads website](https://www.carbonads.net/).

@ -0,0 +1,3 @@
# Edit Link
Documentation coming soon...

@ -0,0 +1,26 @@
# Footer
When the [page layout](./theme-layout) is set to either `home` or `page`, VitePress will display global footer at the bottom of the page. Set `themeConfig.footer` to configure footer content.
```ts
export default {
themeConfig: {
footer: {
message: 'Released under the MIT License.',
copyright: 'Copyright © 2019-present Evan You'
}
}
}
```
```ts
export interface Footer {
// The message shown rigth before copyright.
message?: string
// The actual copyright text.
copyright?: string
}
```
Note that footer will not be displayed when the page layout is set to `doc`.

@ -0,0 +1,116 @@
# Homepage
VitePress default theme provides a homepage layout, which you can also see used on [the homepage of this site](../). You may use it on any of your pages by specifying `layout: home` in the [frontmatter](./frontmatter).
```yaml
---
layout: home
---
```
However, this option alone wouldn't do much. You can add several different pre templated "sections" to the homepage by setting additional other options such as `hero` and `feaures`.
## Hero Section
The Hero section comes at the top of the homepage. Here's how you can configure the Hero section.
```yaml
---
layout: home
hero:
name: VuePress
text: Vite & Vue powered static site generator.
tagline: Lorem ipsum...
actions:
- theme: brand
text: Get Started
link: /guide/what-is-vitepress
- theme: alt
text: View on GitHub
link: https://github.com/vuejs/vitepress
---
```
```ts
interface Hero {
// The string shown top of `text`. Comes with brand color
// and expected to be short, such as product name.
name?: string
// The main text for the hero section. This will be defined
// as `h1` tag.
text: string
// Tagline displayed below `text`.
tagline?: string
// Action buttons to display in home hero section.
actions?: HeroAction[]
}
interface HeroAction {
// Color theme of the button. Defaults to `brand`.
theme?: 'brand' | 'alt'
// Label of the button.
text: string
// Destination link of the button.
link: string
}
```
### Customizing the name color
VitePress uses the brand color (`--vp-c-brand`) for the `name`. However, you may customize this color by overriding `--vp-home-hero-name-color` variable.
```css
:root {
--vp-home-hero-name-color: blue;
}
```
Also you may customize it further by combining `--vp-home-hero-name-background` to give the `name` gradient color.
```css
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, #bd34fe, #41d1ff);
}
```
## Features Section
In Features section, you can list any number of features you would like to show right after the Hero section. To cinfugure it, pass `features` option to the frontmatter.
```yaml
---
layout: home
features:
- icon: ⚡️
title: Vite, The DX that can't be beat
details: Lorem ipsum...
- icon: 🖖
title: Power of Vue meets Markdown
details: Lorem ipsum...
- icon: 🛠️
title: Simple and minimal, always
details: Lorem ipsum...
---
```
```ts
interface Feature {
// Show icon on each feature box. Currently, only emojis
// are supported.
icon?: string
// Title of the feature.
title: string
// Details of the feature.
details: string
}
```

@ -1,10 +1,25 @@
# Theming
# Theme Introduction
VitePress comes with its default theme providing many features out of the box. Learn more about each feature on its dedicated page listed below.
- [Nav](./theme-nav)
- [Sidebar](./theme-sidebar)
- [Prev Next Link](./theme-prev-next-link)
- [Edit Link](./theme-edit-link)
- [Last Updated](./theme-last-updated)
- [Layout](./theme-layout)
- [Homepage](./theme-homepage)
- [Footer](./theme-footer)
- [Search](./theme-search)
- [Carbon Ads](./theme-carbon-ads)
If you don't find the features you're looking for, or you would rather create your own theme, you may customize VitePress to fit your requirements.
## Using a Custom Theme
You can enable a custom theme by adding the `.vitepress/theme/index.js` file (the "theme entry file").
```bash
```
.
├─ docs
│ ├─ .vitepress
@ -39,10 +54,14 @@ import Layout from './Layout.vue'
export default {
Layout,
NotFound: () => 'custom 404', // <- this is a Vue 3 functional component
// this is a Vue 3 functional component
NotFound: () => 'custom 404',
enhanceApp({ app, router, siteData }) {
// app is the Vue 3 app instance from `createApp()`. router is VitePress'
// custom router. `siteData` is a `ref` of current site-level metadata.
// app is the Vue 3 app instance from `createApp()`.
// router is VitePress' custom router. `siteData` is
// a `ref` of current site-level metadata.
}
}
```
@ -53,7 +72,9 @@ export default {
<!-- .vitepress/theme/Layout.vue -->
<template>
<h1>Custom Layout!</h1>
<Content /><!-- this is where markdown content will be rendered -->
<!-- this is where markdown content will be rendered -->
<Content />
</template>
```
@ -64,6 +85,7 @@ To distribute a theme, simply export the object in your package entry. To consum
```js
// .vitepress/theme/index.js
import Theme from 'awesome-vitepress-theme'
export default Theme
```
@ -81,7 +103,7 @@ export default {
...DefaultTheme,
enhanceApp({ app }) {
// register global components
app.component('MyGlobalComponent' /* ... */)
app.component('MyGlobalComponent', /* ... */)
}
}
```
@ -103,8 +125,8 @@ export default DefaultTheme
```css
/* .vitepress/theme/custom.css */
:root {
--c-brand: #646cff;
--c-brand-light: #747bff;
--vp-c-brand: #646cff;
--vp-c-brand-light: #747bff;
}
```
@ -112,7 +134,7 @@ See [default theme CSS variables](https://github.com/vuejs/vitepress/blob/main/s
### Layout Slots
The default theme's `<Layout/>` component has a few slots that can be used to inject content at certain locations of the page. Here's an example of injecting a component into the top of the sidebar:
The default theme's `<Layout/>` component has a few slots that can be used to inject content at certain locations of the page. Here's an example of injecting a component into the before outline:
```js
// .vitepress/theme/index.js
@ -121,7 +143,8 @@ import MyLayout from './MyLayout.vue'
export default {
...DefaultTheme,
// override the Layout with a wrapper component that injects the slots
// override the Layout with a wrapper component that
// injects the slots
Layout: MyLayout
}
```
@ -130,26 +153,47 @@ export default {
<!--.vitepress/theme/MyLayout.vue-->
<script setup>
import DefaultTheme from 'vitepress/theme'
const { Layout } = DefaultTheme
</script>
<template>
<Layout>
<template #sidebar-top>My custom sidebar top content</template>
<template #aside-outline-before>
My custom sidebar top content
</template>
</Layout>
</template>
```
Or you could use render function as well.
```js
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
import MyComponent from './MyComponent.vue'
export default {
...DefaultTheme,
Layout() {
return h(DefaultTheme.Layout, null, {
'sidebar-top': () => h(MyComponent)
})
}
}
```
Full list of slots available in the default theme layout:
- `navbar-search`
- `sidebar-top`
- `sidebar-bottom`
- `page-top-ads`
- `page-top`
- `page-bottom`
- `page-bottom-ads`
- Only when `home: true` is enabled via frontmatter:
- `home-hero`
- `home-features`
- `home-footer`
- When `layout: 'doc'` (default) is enabled via frontmatter:
- `aside-top`
- `aside-bottom`
- `aside-outline-before`
- `aside-outline-after`
- `aside-ads-before`
- `aside-ads-after`
- When `layout: 'home'` is enabled via frontmatter:
- `home-hero-before`
- `home-hero-after`
- `home-features-before`
- `home-features-after`

@ -0,0 +1,3 @@
# Last Updated
Documentation coming soon...

@ -0,0 +1,38 @@
---
layout: doc
---
# Layout
You may choose the page layout by setting `layout` option to the page [frontmatter](./frontmatter). There are 3 layout options, `doc`, `page`, and `home`. If nothing is specified, then the page is treated as `doc` page.
```yaml
---
layout: doc
---
```
## Doc Layout
Option `doc` is the default layout and it styles the whole Markdown content into "documentation" look. It works by wrapping whole content within `vp-doc` css class, and applying styles to elements underneath it.
Almost all generic elements such as `p`, or `h2` get special styling. Therefore, keep in mind that if you add any custom HTML inside a Markdown content, those will get affected by those styles as well.
It also provides documentation specific features listed below. These features are only enabled in this layout.
- Edit Link
- Prev Next Link
- Outline
- [Carbon Ads](./theme-carbon-ads)
## Page Layout
Option `page` is treated as "blank page". The Markdown will still be parsed, and all of the [Markdown Extensions](./markdown) work as same as `doc` layout, but it wouldn't get any default stylings.
The page layout will let you style everything by you without VitePress theme affecting the markup. This is useful when you want to create your own custom page.
Note that even in this layout, sidebar will still show up if the page has a matching sidebar config.
## Home Layout
Option `home` will generate templated "Homepage". In this layout, you can set extra options such as `hero` and `features` to customize the content further. Please visit [Theme: Home Page](./theme-homepage) for more details.

@ -0,0 +1,164 @@
# Nav
The Nav is the navigation bar displayed on top of the page. It contains the site title, global menu links, etc.
## Site Title and Logo
By default, nav shows the title of the site refferencing [`config.title`](../config/app-configs.html#title) value. If you would like to change what's displayed on nav, you may define custom text in `themeConfig.siteTitle` option.
```js
export default {
themeConfig: {
siteTitle: 'My Custom Title'
}
}
```
If you have a logo for your site, you can display it by passing in the path to the image. You should place the logo within `public` directly, and define the absolute path to it.
```js
export default {
themeConfig: {
logo: '/my-logo.svg'
}
}
```
When adding a logo, it gets displayed along with the site title. If your logo is all you need and if you would like to hide the site title text, set `false` to the `siteTitle` option.
```js
export default {
themeConfig: {
logo: '/my-logo.svg',
siteTitle: false
}
}
```
## Navigation Links
You may define `themeConfig.nav` option to add links to your nav.
```js
export default {
themeConfig: {
nav: [
{ text: 'Guide', link: '/guide' },
{ text: 'Configs', link: '/configs' },
{ text: 'Changelog', link: 'https://github.com/...' }
]
}
}
```
The `text` is the actual text displayed in nav, and the `link` is the link that will be navigated to when the text is clicked. For the link, set path to the actual file without `.md` prefix, and alsways start with `/`.
Nav links can also be dropdown menus. To do this, set `items` key on link option.
```js
export default {
themeConfig: {
nav: [
{ text: 'Guide', link: '/guide' },
{
text: 'Dropdown Menu',
items: [
{ text: 'Item A', link: '/item-1' },
{ text: 'Item B', link: '/item-2' },
{ text: 'Item C', link: '/item-3' }
]
}
]
}
}
```
Note that dropdown menu title (`Dropdown Menu` in the above example) can not have `link` property since it becomes a button to open dropdown dialog.
You may further add "sections" to the dropdown menu items as well by passing in more nested items.
```js
export default {
themeConfig: {
nav: [
{ text: 'Guide', link: '/guide' },
{
text: 'Dropdown Menu',
items: [
{
// Title for the section.
text: 'Section A Title',
items: [
{ text: 'Section A Item A', link: '...' },
{ text: 'Section B Item B', link: '...' }
]
}
]
},
{
text: 'Dropdown Menu',
items: [
{
// You may also omit the title.
items: [
{ text: 'Section A Item A', link: '...' },
{ text: 'Section B Item B', link: '...' }
]
}
]
}
]
}
}
```
### Customize link's "active" state
Nav menu items will be highlighted when the current page is under the matching path. if you would like to customize the path to be mathced, define `activeMatch` property and regex as a string value.
```js
export default {
themeConfig: {
nav: [
// This link gets active state when the user is
// on `/config/` path.
{
text: 'Guide',
link: '/guide',
activeMatch: '/config/'
}
]
}
}
```
::: warning
`activeMatch` is expected to be a regex string, but you must define it as a string. We can't use actual RegExp object here because it isn't serializable during the build time.
:::
## Social Links
You may define `socialLinks` option to show your social account links with icons.
```js
export default {
themeConfig: {
socialLinks: [
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' },
{ icon: 'twitter', link: '...' },
{ icon: 'discord', link: '...' }
]
}
}
```
The list of supported icons are shown below.
- `discord`
- `facebook`
- `github`
- `instagram`
- `linkedin`
- `slack`
- `twitter`
- `youtube`

@ -0,0 +1,3 @@
# Prev Next Link
Documentation coming soon...

@ -0,0 +1,3 @@
# Search
Documentation coming soon...

@ -0,0 +1,159 @@
# Sidebar
The sidebar is the main navigation block for your documentation. You can configure the sidebar menu in `themeConfig.sidebar`.
```js
export default {
themeConfig: {
sidebar: [
{
text: 'Guide',
items: [
{ text: 'Introduction', link: '/introduction' },
{ text: 'Getting Started', link: '/getting-started' },
...
]
}
]
}
}
```
## The Basics
The simplest form of the sidebar menu is passing in a single array of links. The first level item defines the "section" for the sidebar. It should contain `text`, which is the title of the section, and `items` which are the actual navigation links.
```js
export default {
themeConfig: {
sidebar: [
{
text: 'Section Title A',
items: [
{ text: 'Item A', link: '/item-a' },
{ text: 'Item B', link: '/item-b' },
...
]
},
{
text: 'Section Title B',
items: [
{ text: 'Item C', link: '/item-c' },
{ text: 'Item D', link: '/item-d' },
...
]
}
]
}
}
```
Each `link` should specify the path to the actual file starting with `/`. If you add trailing slash to the end of link, it will show `index.md` of the corresponding directory.
```js
export default {
themeConfig: {
sidebar: [
{
text: 'Guide',
items: [
// This shows `/guide/index.md` page.
{ text: 'Introduction', link: '/guide/' }
]
}
]
}
}
```
## Multiple Sidebars
You may show different sidebar depending on the page path. For example, as shown on this site, you might want to create a separate sections of content in your documentation like "Guide" page and `Config` page.
To do so, first organize your pages into directories for each desired section:
```
.
├─ guide/
│ ├─ index.md
│ ├─ one.md
│ └─ two.md
└─ config/
├─ index.md
├─ three.md
└─ four.md
```
Then, update your configuration to define your sidebar for each section. This time, you should pass an object instead of an array.
```js
export default {
themeConfig: {
sidebar: {
// This sidebar gets displayed when user is
// under `guide` directory.
'/guide/': {
text: 'Guide',
items: [
// This shows `/guide/index.md` page.
{ text: 'Index', link: '/guide/' }, // /guide/index.md
{ text: 'One', link: '/guide/one' }, // /guide/one.md
{ text: 'Two', link: '/guide/two' } // /guide/two.md
]
},
// This sidebar gets displayed when user is
// under `config` directory.
'/config/': {
text: 'Config',
items: [
// This shows `/guide/index.md` page.
{ text: 'Index', link: '/config/' }, // /config/index.mdasdfasdfasdfasdfaf
{ text: 'Three', link: '/config/three' }, // /config/three.md
{ text: 'Four', link: '/config/four' } // /config/four.md
]
}
}
}
}
```
## Collapsible Sidebar Groups
By adding `collapsible` option to the sidebar group, it shows a toggle button to hide/show each section.
```js
export default {
themeConfig: {
sidebar: [
{
text: 'Section Title A',
collapsible: true,
items: [...]
},
{
text: 'Section Title B',
collapsible: true,
items: [...]
}
]
}
}
```
All sections are "open" by default. If you would like them to be "closed" on intial page load, set `collapsed` option to `true`.
```js
export default {
themeConfig: {
sidebar: [
{
text: 'Section Title A',
collapsible: true,
collapsed: true,
items: [...]
}
]
}
}
```

@ -1,7 +1,3 @@
---
sidebarDepth: 3
---
# Using Vue in Markdown
In VitePress, each markdown file is compiled into HTML and then processed as a Vue Single-File Component. This means you can use any Vue features inside the markdown, including dynamic templating, using Vue components, or arbitrary in-page Vue component logic by adding a `<script>` tag.
@ -30,8 +26,8 @@ Directives also work:
**Input**
```md
<span v-for="i in 3">{{ i }} </span>
```html
<span v-for="i in 3">{{ i }}</span>
```
**Output**
@ -44,9 +40,10 @@ You can use the [`useData` helper](./api#usedata) in a `<script>` block and expo
**Input**
```md
```html
<script setup>
import { useData } from 'vitepress'
const { page } = useData()
</script>
@ -107,7 +104,7 @@ This is a .md using a custom component
### Registering global components in the theme
If the components are going to be used across several pages in the docs, they can be registered globally in the theme (or as part of extending the default VitePress theme). Check out the [Theming Guide](./theming) for more information.
If the components are going to be used across several pages in the docs, they can be registered globally in the theme (or as part of extending the default VitePress theme). Check out the [Theming Guide](./theme-introduction) for more information.
In `.vitepress/theme/index.js`, the `enhanceApp` function receives the Vue `app` instance so you can [register components](https://vuejs.org/guide/components/registration.html) as you would do in a regular Vue application.
@ -200,7 +197,7 @@ export default {
## Built-In Components
VitePress provides Built-In Vue Components like `ClientOnly` and `OutboundLink`, check out the [Global Component Guide](./global-component) for more information.
VitePress provides Built-In Vue Components like `ClientOnly` and `OutboundLink`, check out the [Global Component Guide](./api) for more information.
**Also see:**
@ -214,7 +211,7 @@ If you are using or demoing components that are not SSR-friendly (for example, c
```md
<ClientOnly>
<NonSSRFriendlyComponent/>
<NonSSRFriendlyComponent />
</ClientOnly>
```
@ -236,7 +233,10 @@ If your module `export default` a Vue component, you can register it dynamically
```vue
<template>
<component v-if="dynamicComponent" :is="dynamicComponent"></component>
<component
v-if="dynamicComponent"
:is="dynamicComponent">
</component>
</template>
<script>

@ -0,0 +1,53 @@
# What is VitePress?
VitePress is [VuePress](https://vuepress.vuejs.org/)' little brother, built on top of [Vite](https://vitejs.dev/).
::: warning
VitePress is currently in `alpha` status. It is already suitable for out-of-the-box documentation use, but the config and theming API may still change between minor releases.
:::
## Motivation
We love VuePress v1, but being built on top of Webpack, the time it takes to spin up the dev server for a simple doc site with a few pages is just becoming unbearable. Even HMR updates can take up to seconds to reflect in the browser!
Fundamentally, this is because VuePress v1 is a Webpack app under the hood. Even with just two pages, it's a full on Webpack project (including all the theme source files) being compiled. It gets even worse when the project has many pages every page must first be fully compiled before the server can even display anything!
Incidentally, Vite solves these problems really well: nearly instant server start, an on-demand compilation that only compiles the page being served, and lightning-fast HMR. Plus, there are a few additional design issues I have noted in VuePress v1 over time but never had the time to fix due to the amount of refactoring it would require.
Now, with Vite and Vue 3, it is time to rethink what a "Vue-powered static site generator" can really be.
## Improvements over VuePress v1
There're couple of things that are improved from VuePress v1....
### It uses Vue 3
Leverages Vue 3's improved template static analysis to stringify static content as much as possible. Static content is sent as string literals instead of JavaScript render function code the JS payload is therefore much cheaper to parse, and hydration also becomes faster.
Note the optimization is applied while still allowing the user to freely mix Vue components inside markdown content the compiler does the static/dynamic separation for you automatically and you never need to think about it.
### It uses Vite under the hood
- Faster dev server start
- Faster hot updates
- Faster build (uses Rollup internally)
### Lighter page weight
Vue 3 tree-shaking + Rollup code splitting
- Does not ship metadata for every page on every request. This decouples page weight from total number of pages. Only the current page's metadata is sent. Client side navigation fetches the new page's component and metadata together.
- Does not use vue-router because the need of VitePress is very simple and specific - a simple custom router (under 200 LOC) is used instead.
### Other differences
VitePress is more opinionated and less configurable: VitePress aims to scale back the complexity in the current VuePress and restart from its minimalist roots.
VitePress is future oriented: VitePress only targets browsers that support native ES module imports. It encourages the use of native JavaScript without transpilation, and CSS variables for theming.
## Will this become the next vuepress in the future?
We already have [vuepress-next](https://github.com/vuepress/vuepress-next), which would be the next major version of VuePress. It also makes lots of improvements over VuePress v1, and also supports Vite now.
VitePress is not compatible with the current VuePress ecosystem (mostly themes and plugins). The overall idea is that VitePress will have a drastically more minimal theming API (preferring JavaScript APIs instead of file layout conventions) and likely no plugins (all customization is done in themes).
There is an [ongoing disccussion](https://github.com/vuejs/vitepress/discussions/548) about this topic. If you're curious, please leave your thoughts!

@ -1,56 +1,28 @@
---
sidebarDepth: 2
layout: home
title: VitePress
titleTemplate: Vite & Vue Powered Static Site Generator
hero:
name: VitePress
text: Vite & Vue Powered Static Site Generator
tagline: Simple, powerful, and performant. Meet the modern SSG framework you've always wanted.
actions:
- theme: brand
text: Get Started
link: /guide/getting-started
- theme: alt
text: View on GitHub
link: https://github.com/vuejs/vitepress
features:
- title: "Vite: The DX that can't be beat"
details: Feel the speed of Vite. Instant server start and lightning fast HMR that stays fast regardless of the app size.
- title: Designed to be simplicity first
details: With Markdown-centered content, it's built to help you focus on writing and deployed with minimum configuration.
- title: Power of Vue meets Markdown
details: Enhance your content with all the features of Vue in Markdown, while being able to customize your site with Vue.
- title: Fully static yet still dynamic
details: Go wild with true SSG + SPA architecture. Static on page load, but engage users with 100% interactivity from there.
---
# What is VitePress?
::: warning WARNING
VitePress is currently in 0.x status. It is already suitable for out-of-the-box documentation use, but the config and theming API may still change between minor releases.
:::
VitePress is [VuePress](https://vuepress.vuejs.org)' little brother, built on top of [Vite](https://github.com/vitejs/vite).
## Motivation
We love VuePress v1, but being built on top of Webpack, the time it takes to spin up the dev server for a simple doc site with a few pages is just becoming unbearable. Even HMR updates can take up to seconds to reflect in the browser!
Fundamentally, this is because VuePress v1 is a Webpack app under the hood. Even with just two pages, it's a full on Webpack project (including all the theme source files) being compiled. It gets even worse when the project has many pages every page must first be fully compiled before the server can even display anything!
Incidentally, Vite solves these problems really well: nearly instant server start, an on-demand compilation that only compiles the page being served, and lightning-fast HMR. Plus, there are a few additional design issues I have noted in VuePress v1 over time but never had the time to fix due to the amount of refactoring it would require.
Now, with Vite and Vue 3, it is time to rethink what a "Vue-powered static site generator" can really be.
## Improvements Over VuePress v1
There're couple of things that are improved from VuePress v1....
### It Uses Vue 3
Leverages Vue 3's improved template static analysis to stringify static content as much as possible. Static content is sent as string literals instead of JavaScript render function code the JS payload is therefore _much_ cheaper to parse, and hydration also becomes faster.
Note the optimization is applied while still allowing the user to freely mix Vue components inside markdown content the compiler does the static/dynamic separation for you automatically and you never need to think about it.
### It Uses Vite Under The Hood
- Faster dev server start
- Faster hot updates
- Faster build (uses Rollup internally)
### Lighter Page Weight
- Vue 3 tree-shaking + Rollup code splitting
- Does not ship metadata for every page on every request. This decouples page weight from total number of pages. Only the current page's metadata is sent. Client side navigation fetches the new page's component and metadata together.
- Does not use `vue-router` because the need of VitePress is very simple and specific - a simple custom router (under 200 LOC) is used instead.
- (WIP) i18n locale data should also be fetched on demand.
## Other Differences
VitePress is more opinionated and less configurable: VitePress aims to scale back the complexity in the current VuePress and restart from its minimalist roots.
VitePress is future oriented: VitePress only targets browsers that support native ES module imports. It encourages the use of native JavaScript without transpilation, and CSS variables for theming.
## Will This Become The Next VuePress in The Future?
We already have [vuepress-next](https://github.com/vuepress/vuepress-next), which would be the next major version of VuePress. It also makes lots of improvements over VuePress v1, and also supports Vite now.
VitePress is not compatible with the current VuePress ecosystem (mostly themes and plugins). The overall idea is that VitePress will have a drastically more minimal theming API (preferring JavaScript APIs instead of file layout conventions) and likely no plugins (all customization is done in themes).

@ -1,71 +1,21 @@
# Hello, World!
# Lorem Ipsum
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
## General Markdown
## What is Lorem Ipsum?
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Ut enim ad minim veniam, [link to Vue.js](https://vuejs.org) quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
Ut enim ad minim veniam, **strong opinion** quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Lorem ipsum dolor sit amet, consectetur _and emphasize_ adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
## Where does it come from?
## Lists
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.
- List item 1
- Nested item A
- Nested item B
- List item 2
- List item 3
## Why do we use it?
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).
1. Ordered item 1
1. Nested ordered item A
1. Nested ordered item A
1. Ordered item 2
1. Ordered item 3
## Where can I get some?
## Code Block and Block Quote
Lorem ipsum dolor sit amet, consectetur adipisicing elit, `exampleCodeBlock` sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
```js
function exampleCodeBlock() {
console.log('It is Working.')
}
```
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
> The best is yet to come.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
## Custom Alerts
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
### Tips
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
::: tip TIP
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
:::
### Warning
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
::: warning WARNING
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
:::
### Danger
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
::: danger DANGER
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.

@ -1,10 +1,19 @@
{
"name": "vitepress",
"version": "0.22.4",
"packageManager": "pnpm@7.1.2",
"version": "1.0.0-draft.8",
"description": "Vite & Vue powered static site generator",
"type": "module",
"packageManager": "pnpm@7.1.7",
"main": "dist/node/index.js",
"typings": "types/index.d.ts",
"types": "types/index.d.ts",
"exports": {
".": {
"types": "./types/index.d.ts",
"import": "./dist/node/index.js",
"require": "./dist/node-cjs/index.cjs"
},
"./dist/client/*": "./dist/client/*"
},
"bin": {
"vitepress": "bin/vitepress.js"
},
@ -15,24 +24,42 @@
"client.d.ts",
"theme.d.ts"
],
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vitepress.git"
},
"keywords": [
"vite",
"vue",
"vitepress"
],
"author": "Evan You",
"license": "MIT",
"homepage": "https://github.com/vuejs/vitepress/tree/main/#readme",
"bugs": {
"url": "https://github.com/vuejs/vitepress/issues"
},
"engines": {
"node": ">=14.6.0"
},
"scripts": {
"dev": "run-s dev-shared dev-start",
"dev": "rimraf dist && run-s dev-shared dev-start",
"dev-start": "run-p dev-client dev-node dev-watch",
"dev-client": "tsc -w -p src/client",
"dev-node": "tsc -w -p src/node",
"dev-node": "DEV=true pnpm run build-node -w",
"dev-shared": "node scripts/copyShared",
"dev-watch": "node scripts/watchAndCopy",
"build": "run-s build-prepare build-client build-node build-types",
"build-prepare": "rimraf -rf dist && node scripts/copyShared",
"build": "run-s build-prepare build-client build-node",
"build-prepare": "rimraf dist && node scripts/copyShared",
"build-client": "tsc -p src/client && node scripts/copyClient",
"build-node": "rollup -c scripts/rollup.config.js",
"build-types": "run-s build-types-client build-types-node",
"build-types-client": "tsc -p src/client --declaration --emitDeclarationOnly --outDir dist/temp && api-extractor run -c api-extractor.client.json && rimraf dist/temp",
"build-types-node": "tsc -p src/node --declaration --emitDeclarationOnly --outDir dist/temp && api-extractor run -c api-extractor.node.json && rimraf dist/temp",
"lint": "run-s lint:js lint:ts",
"lint:js": "prettier --check --write \"{bin,docs,scripts,src}/**/*.js\"",
"lint:ts": "prettier --check --write --parser typescript \"{__tests__,src,docs,types}/**/*.ts\"",
"test": "vitest run __tests__ -c __tests__/vitest.config.js --globals",
"build-node": "rollup --config rollup.config.ts --configPlugin esbuild",
"format": "prettier --check --write .",
"format-fail": "prettier --check .",
"lint": "pnpm run format",
"lint-fail": "pnpm run format-fail",
"test": "vitest -r __tests__",
"test-run": "vitest run -r __tests__",
"check": "run-s lint test-run docs-build",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"release": "node scripts/release.js",
"docs": "run-p dev docs-dev",
@ -41,51 +68,25 @@
"docs-build": "run-s build docs-build-only",
"docs-build-only": "node ./bin/vitepress build docs",
"docs-serve": "node ./bin/vitepress serve docs",
"ci-docs": "run-s build docs-build"
},
"engines": {
"node": ">=14.0.0"
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.js": [
"prettier --write"
],
"*.ts": [
"prettier --parser=typescript --write"
]
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vitepress.git"
},
"keywords": [
"vite",
"vue",
"vitepress"
],
"author": "Evan You",
"license": "MIT",
"homepage": "https://github.com/vuejs/vitepress/tree/master/#readme",
"bugs": {
"url": "https://github.com/vuejs/vitepress/issues"
"ci-docs": "run-s docs-build"
},
"dependencies": {
"@docsearch/css": "^3.0.0",
"@docsearch/js": "^3.0.0",
"@vitejs/plugin-vue": "^2.3.2",
"prismjs": "^1.25.0",
"@vueuse/core": "^8.5.0",
"body-scroll-lock": "^4.0.0-beta.0",
"shiki": "^0.10.1",
"vite": "^2.9.7",
"vue": "^3.2.33"
},
"devDependencies": {
"@microsoft/api-extractor": "^7.23.1",
"@rollup/plugin-alias": "^3.1.5",
"@rollup/plugin-commonjs": "^20.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.0.4",
"@rollup/plugin-replace": "^4.0.0",
"@types/body-scroll-lock": "^3.1.0",
"@types/compression": "^1.7.0",
"@types/cross-spawn": "^6.0.2",
"@types/debug": "^4.1.7",
@ -98,7 +99,7 @@
"@types/minimist": "^1.2.2",
"@types/node": "^15.6.1",
"@types/polka": "^0.5.3",
"chalk": "^4.1.1",
"@types/prompts": "^2.0.14",
"chokidar": "^3.5.1",
"compression": "^1.7.4",
"conventional-changelog-cli": "^2.1.1",
@ -108,10 +109,9 @@
"enquirer": "^2.3.6",
"esbuild": "^0.14.0",
"escape-html": "^1.0.3",
"execa": "^5.0.0",
"execa": "^6.1.0",
"fast-glob": "^3.2.7",
"fs-extra": "^10.0.0",
"globby": "^11.0.3",
"gray-matter": "^4.0.3",
"lint-staged": "^11.0.0",
"lru-cache": "^6.0.0",
@ -120,30 +120,42 @@
"markdown-it-attrs": "^4.1.3",
"markdown-it-container": "^3.0.0",
"markdown-it-emoji": "^2.0.0",
"markdown-it-table-of-contents": "^0.6.0",
"markdown-it-toc-done-right": "^4.2.0",
"micromatch": "^4.0.4",
"minimist": "^1.2.5",
"npm-run-all": "^4.1.5",
"ora": "^5.4.0",
"picocolors": "^1.0.0",
"polka": "^0.5.2",
"prettier": "^2.3.0",
"prompts": "^2.4.2",
"rimraf": "^3.0.2",
"rollup": "^2.56.3",
"rollup-plugin-dts": "^4.2.2",
"rollup-plugin-esbuild": "^4.8.2",
"semver": "^7.3.5",
"simple-git-hooks": "^2.7.0",
"sirv": "^1.0.12",
"typescript": "^4.6.4",
"vitest": "^0.10.4",
"yorkie": "^2.0.0"
"supports-color": "^9.2.2",
"typescript": "^4.7.2",
"vitest": "^0.10.4"
},
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"@algolia/client-search",
"@types/react",
"react",
"react-dom",
"@types/react"
"react-dom"
]
}
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
},
"lint-staged": {
"*": [
"prettier --write --ignore-unknown"
]
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,109 @@
import { promises as fs } from 'fs'
import { resolve } from 'path'
import { fileURLToPath } from 'url'
import { RollupOptions, defineConfig } from 'rollup'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import esbuild from 'rollup-plugin-esbuild'
import json from '@rollup/plugin-json'
import replace from '@rollup/plugin-replace'
import alias from '@rollup/plugin-alias'
import dts from 'rollup-plugin-dts'
import pkg from './package.json'
const DEV = !!process.env.DEV
const PROD = !DEV
const ROOT = fileURLToPath(import.meta.url)
const r = (p: string) => resolve(ROOT, '..', p)
const external = [...Object.keys(pkg.dependencies), 'buffer', 'punycode']
const plugins = [
alias({
entries: {
'readable-stream': 'stream'
}
}),
replace({
// polyfill broken browser check from bundled deps
'navigator.userAgentData': 'undefined',
'navigator.userAgent': 'undefined',
preventAssignment: true
}),
commonjs(),
nodeResolve(),
esbuild({ target: 'node14' }),
json()
]
const esmBuild: RollupOptions = {
input: [r('src/node/index.ts'), r('src/node/cli.ts')],
output: {
format: 'esm',
entryFileNames: `[name].js`,
chunkFileNames: 'serve-[hash].js',
dir: r('dist/node')
},
external,
plugins,
onwarn(warning, warn) {
if (warning.code !== 'EVAL') warn(warning)
}
}
const cjsBuild: RollupOptions = {
input: [r('src/node/index.ts'), r('src/node/cli.ts')],
output: {
format: 'cjs',
dir: r('dist/node-cjs'),
entryFileNames: `[name].cjs`,
chunkFileNames: 'serve-[hash].cjs'
},
external,
plugins,
onwarn(warning, warn) {
if (warning.code !== 'EVAL') warn(warning)
}
}
const nodeTypes: RollupOptions = {
input: r('src/node/index.ts'),
output: {
format: 'esm',
file: 'dist/node/index.d.ts'
},
plugins: [dts()]
}
const clientTypes: RollupOptions = {
input: r('dist/client-types/index.d.ts'),
output: {
format: 'esm',
file: 'dist/client/index.d.ts'
},
plugins: [
dts(),
{
name: 'cleanup',
async closeBundle() {
if (PROD) {
await fs.rm(r('dist/client-types'), { recursive: true })
}
}
}
]
}
const config = defineConfig([])
config.push(esmBuild)
if (PROD) {
config.push(cjsBuild)
}
config.push(nodeTypes)
config.push(clientTypes)
export default config

@ -1,11 +1,11 @@
const fs = require('fs-extra')
const glob = require('globby')
import { copy } from 'fs-extra'
import fg from 'fast-glob'
function toDest(file) {
return file.replace(/^src\//, 'dist/')
}
glob.sync('src/client/**').forEach((file) => {
fg.sync('src/client/**').forEach((file) => {
if (/(\.ts|tsconfig\.json)$/.test(file)) return
fs.copy(file, toDest(file))
copy(file, toDest(file))
})

@ -1,7 +1,7 @@
const fs = require('fs-extra')
const glob = require('globby')
import { copy } from 'fs-extra'
import fg from 'fast-glob'
glob.sync('src/shared/**/*.ts').forEach((file) => {
fs.copy(file, file.replace(/^src\/shared\//, 'src/node/'))
fs.copy(file, file.replace(/^src\/shared\//, 'src/client/'))
fg.sync('src/shared/**/*.ts').map(async (file) => {
await copy(file, file.replace(/^src\/shared\//, 'src/node/'))
await copy(file, file.replace(/^src\/shared\//, 'src/client/'))
})

@ -1,22 +1,24 @@
const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const semver = require('semver')
const { prompt } = require('enquirer')
const execa = require('execa')
const currentVersion = require('../package.json').version
import { readFileSync, writeFileSync } from 'fs'
import { resolve } from 'path'
import c from 'picocolors'
import { inc as _inc, valid } from 'semver'
import prompts from 'prompts'
import { execa } from 'execa'
import { version as currentVersion } from '../package.json'
import { fileURLToPath } from 'url'
const versionIncrements = ['patch', 'minor', 'major']
const inc = (i) => semver.inc(currentVersion, i)
const dir = dirname(fileURLToPath(import.meta.url))
const inc = (i) => _inc(currentVersion, i)
const run = (bin, args, opts = {}) =>
execa(bin, args, { stdio: 'inherit', ...opts })
const step = (msg) => console.log(chalk.cyan(msg))
const step = (msg) => console.log(c.cyan(msg))
async function main() {
let targetVersion
const { release } = await prompt({
const { release } = await prompts({
type: 'select',
name: 'release',
message: 'Select release type',
@ -25,7 +27,7 @@ async function main() {
if (release === 'custom') {
targetVersion = (
await prompt({
await prompts({
type: 'input',
name: 'version',
message: 'Input custom version',
@ -36,11 +38,11 @@ async function main() {
targetVersion = release.match(/\((.*)\)/)[1]
}
if (!semver.valid(targetVersion)) {
if (!valid(targetVersion)) {
throw new Error(`Invalid target version: ${targetVersion}`)
}
const { yes: tagOk } = await prompt({
const { yes: tagOk } = await prompts({
type: 'confirm',
name: 'yes',
message: `Releasing v${targetVersion}. Confirm?`
@ -63,7 +65,7 @@ async function main() {
await run('pnpm', ['changelog'])
await run('pnpm', ['prettier', '--write', 'CHANGELOG.md'])
const { yes: changelogOk } = await prompt({
const { yes: changelogOk } = await prompts({
type: 'confirm',
name: 'yes',
message: `Changelog generated. Does it look good?`
@ -90,12 +92,12 @@ async function main() {
}
function updatePackage(version) {
const pkgPath = path.resolve(path.resolve(__dirname, '..'), 'package.json')
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
const pkgPath = resolve(resolve(dir, '..'), 'package.json')
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
pkg.version = version
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
}
main().catch((err) => console.error(err))

@ -1,35 +0,0 @@
import { defineConfig } from 'rollup'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import esbuild from 'rollup-plugin-esbuild'
import json from '@rollup/plugin-json'
import alias from '@rollup/plugin-alias'
import { resolve } from 'path'
const r = (p) => resolve(__dirname, '../', p)
const pkg = require('../package.json')
export default defineConfig({
input: [r('src/node/index.ts'), r('src/node/cli.ts')],
output: {
format: 'cjs',
dir: r('dist/node')
},
external: [...Object.keys(pkg.dependencies), 'buffer', 'punycode'],
plugins: [
alias({
entries: {
'readable-stream': 'stream'
}
}),
commonjs(),
nodeResolve(),
esbuild({
target: 'node12'
}),
json()
],
onwarn(warning, warn) {
if (warning.code !== 'EVAL') warn(warning)
}
})

@ -1,15 +1,15 @@
const fs = require('fs-extra')
const chokidar = require('chokidar')
const { normalizePath } = require('vite')
import { copy, remove } from 'fs-extra'
import { watch } from 'chokidar'
import { normalizePath } from 'vite'
function toClientAndNode(method, file) {
file = normalizePath(file)
if (method === 'copy') {
fs.copy(file, file.replace(/^src\/shared\//, 'src/node/'))
fs.copy(file, file.replace(/^src\/shared\//, 'src/client/'))
copy(file, file.replace(/^src\/shared\//, 'src/node/'))
copy(file, file.replace(/^src\/shared\//, 'src/client/'))
} else if (method === 'remove') {
fs.remove(file.replace(/^src\/shared\//, 'src/node/'))
fs.remove(file.replace(/^src\/shared\//, 'src/client/'))
remove(file.replace(/^src\/shared\//, 'src/node/'))
remove(file.replace(/^src\/shared\//, 'src/client/'))
}
}
@ -18,16 +18,14 @@ function toDist(file) {
}
// copy shared files to the client and node directory whenever they change.
chokidar
.watch('src/shared/**/*.ts')
watch('src/shared/**/*.ts')
.on('change', (file) => toClientAndNode('copy', file))
.on('add', (file) => toClientAndNode('copy', file))
.on('unlink', (file) => toClientAndNode('remove', file))
// copy non ts files, such as an html or css, to the dist directory whenever
// they change.
chokidar
.watch('src/client/**/!(*.ts|tsconfig.json)')
.on('change', (file) => fs.copy(file, toDist(file)))
.on('add', (file) => fs.copy(file, toDist(file)))
.on('unlink', (file) => fs.remove(toDist(file)))
watch('src/client/**/!(*.ts|tsconfig.json)')
.on('change', (file) => copy(file, toDist(file)))
.on('add', (file) => copy(file, toDist(file)))
.on('unlink', (file) => remove(toDist(file)))

@ -1,11 +1,13 @@
import { ref, onMounted, defineComponent } from 'vue'
import { defineComponent, ref, onMounted } from 'vue'
export const ClientOnly = defineComponent({
setup(_, { slots }) {
const show = ref(false)
onMounted(() => {
show.value = true
})
return () => (show.value && slots.default ? slots.default() : null)
}
})

@ -1,5 +1,5 @@
import { watchEffect, Ref } from 'vue'
import { HeadConfig, SiteData } from '../../shared'
import { HeadConfig, SiteData, createTitle } from '../../shared'
import { Route } from '../router'
export function useUpdateHead(route: Route, siteDataByRouteRef: Ref<SiteData>) {
@ -56,12 +56,12 @@ export function useUpdateHead(route: Route, siteDataByRouteRef: Ref<SiteData>) {
watchEffect(() => {
const pageData = route.data
const siteData = siteDataByRouteRef.value
const pageTitle = pageData && pageData.title
const pageDescription = pageData && pageData.description
const frontmatterHead = pageData && pageData.frontmatter.head
// update title and description
document.title = (pageTitle ? pageTitle + ` | ` : ``) + siteData.title
document.title = createTitle(siteData, pageData)
document
.querySelector(`meta[name=description]`)!
.setAttribute('content', pageDescription || siteData.description)

@ -1,7 +1,12 @@
import { InjectionKey, Ref, shallowRef, readonly, computed, inject } from 'vue'
import { Route } from './router'
import serializedSiteData from '@siteData'
import { resolveSiteDataByRoute, PageData, SiteData } from '../shared'
import {
PageData,
SiteData,
resolveSiteDataByRoute,
createTitle
} from '../shared'
import { withBase } from './utils'
export const dataSymbol: InjectionKey<VitePressData> = Symbol()
@ -54,9 +59,7 @@ export function initData(route: Route): VitePressData {
return withBase(path || '/')
}),
title: computed(() => {
return route.data.title
? route.data.title + ' | ' + site.value.title
: site.value.title
return createTitle(site.value, route.data)
}),
description: computed(() => {
return route.data.description || site.value.description

@ -7,11 +7,11 @@ import {
onMounted,
watch
} from 'vue'
import Theme from '/@theme/index'
import { inBrowser, pathToFile } from './utils'
import { Router, RouterSymbol, createRouter } from './router'
import { siteDataRef, useData } from './data'
import { useUpdateHead } from './composables/head'
import Theme from '/@theme/index'
import { usePrefetch } from './composables/preFetch'
import { dataSymbol, initData } from './data'
import { Content } from './components/Content'
@ -107,16 +107,11 @@ function newRouter(): Router {
pageFilePath = pageFilePath.replace(/\.js$/, '.lean.js')
}
// in browser: native dynamic import
if (inBrowser) {
isInitialPageLoad = false
return import(/*@vite-ignore*/ pageFilePath)
}
// SSR: sync require
// @ts-ignore
return require(pageFilePath)
return import(/*@vite-ignore*/ pageFilePath)
}, NotFound)
}

@ -1,7 +1,7 @@
import { reactive, inject, markRaw, nextTick, readonly } from 'vue'
import type { Component, InjectionKey } from 'vue'
import { PageData } from '../shared'
import { inBrowser } from './utils'
import { inBrowser, withBase } from './utils'
import { siteDataRef } from './data'
export interface Route {
@ -82,7 +82,7 @@ export function createRouter(
throw new Error(`Invalid route component: ${comp}`)
}
route.path = pendingPath
route.path = inBrowser ? pendingPath : withBase(pendingPath)
route.component = markRaw(comp)
route.data = import.meta.env.PROD
? markRaw(JSON.parse(__pageData))
@ -127,7 +127,7 @@ export function createRouter(
if (latestPendingPath === pendingPath) {
latestPendingPath = null
route.path = pendingPath
route.path = inBrowser ? pendingPath : withBase(pendingPath)
route.component = fallbackComponent ? markRaw(fallbackComponent) : null
route.data = notFoundPageData
}

@ -25,7 +25,5 @@ export { inBrowser, withBase } from './app/utils'
// components
export { Content } from './app/components/Content'
import { ComponentOptions } from 'vue'
import _Debug from './app/components/Debug.vue'
const Debug = _Debug as ComponentOptions
export { Debug }
export const Debug = _Debug as import('vue').ComponentOptions

@ -1,7 +1,7 @@
declare const __VP_HASH_MAP__: Record<string, string>
declare const __CARBON__: boolean
declare const __BSA__: boolean
declare const __ALGOLIA__: boolean
declare const __CARBON__: boolean
declare module '*.vue' {
import { ComponentOptions } from 'vue'
const comp: ComponentOptions
@ -13,12 +13,16 @@ declare module '@siteData' {
export default data
}
// this module's typing is broken
// this module's typing is broken.
declare module '@docsearch/js' {
function docsearch<T = any>(props: T): void
export default docsearch
}
declare module '@docsearch/react/dist/esm/types' {
export type DocSearchHit = any
}
declare module '@docsearch/css' {
const css: string
export default css

@ -1,189 +1,55 @@
<script setup lang="ts">
import { ref, computed, watch, defineAsyncComponent } from 'vue'
import { useRoute, useData } from 'vitepress'
import { isSideBarEmpty, getSideBarConfig } from './support/sideBar'
// components
import Home from './components/Home.vue'
import NavBar from './components/NavBar.vue'
import SideBar from './components/SideBar.vue'
import Page from './components/Page.vue'
const NoopComponent = () => null
const CarbonAds = __CARBON__
? defineAsyncComponent(() => import('./components/CarbonAds.vue'))
: NoopComponent
const BuySellAds = __BSA__
? defineAsyncComponent(() => import('./components/BuySellAds.vue'))
: NoopComponent
const AlgoliaSearchBox = __ALGOLIA__
? defineAsyncComponent(() => import('./components/AlgoliaSearchBox.vue'))
: NoopComponent
// generic state
const route = useRoute()
const { site, page, theme, frontmatter } = useData()
// custom layout
const isCustomLayout = computed(() => !!frontmatter.value.customLayout)
// home
const enableHome = computed(() => !!frontmatter.value.home)
// automatic multilang check for AlgoliaSearchBox
const isMultiLang = computed(() => Object.keys(site.value.langs).length > 1)
// navbar
const showNavbar = computed(() => {
const themeConfig = theme.value
if (frontmatter.value.navbar === false || themeConfig.navbar === false) {
return false
}
return (
site.value.title || themeConfig.logo || themeConfig.repo || themeConfig.nav
)
})
// sidebar
const openSideBar = ref(false)
const showSidebar = computed(() => {
if (frontmatter.value.home || frontmatter.value.sidebar === false) {
return false
}
return !isSideBarEmpty(
getSideBarConfig(theme.value.sidebar, route.data.relativePath)
)
})
const toggleSidebar = (to?: boolean) => {
openSideBar.value = typeof to === 'boolean' ? to : !openSideBar.value
}
const hideSidebar = toggleSidebar.bind(null, false)
// close the sidebar when navigating to a different location
watch(route, hideSidebar)
// TODO: route only changes when the pathname changes
// listening to hashchange does nothing because it's prevented in router
// page classes
const pageClasses = computed(() => {
return [
{
'no-navbar': !showNavbar.value,
'sidebar-open': openSideBar.value,
'no-sidebar': !showSidebar.value
}
]
})
import { provide } from 'vue'
import { useSidebar, useCloseSidebarOnEscape } from './composables/sidebar'
import VPSkipLink from './components/VPSkipLink.vue'
import VPBackdrop from './components/VPBackdrop.vue'
import VPNav from './components/VPNav.vue'
import VPLocalNav from './components/VPLocalNav.vue'
import VPSidebar from './components/VPSidebar.vue'
import VPContent from './components/VPContent.vue'
import VPFooter from './components/VPFooter.vue'
const {
isOpen: isSidebarOpen,
open: openSidebar,
close: closeSidebar
} = useSidebar()
useCloseSidebarOnEscape(isSidebarOpen, closeSidebar)
provide('close-sidebar', closeSidebar)
</script>
<template>
<div class="theme" :class="pageClasses">
<NavBar v-if="showNavbar" @toggle="toggleSidebar">
<template #search>
<slot name="navbar-search">
<AlgoliaSearchBox
v-if="theme.algolia"
:options="theme.algolia"
:multilang="isMultiLang"
/>
</slot>
</template>
</NavBar>
<SideBar :open="openSideBar">
<template #sidebar-top>
<slot name="sidebar-top" />
</template>
<template #sidebar-bottom>
<slot name="sidebar-bottom" />
</template>
</SideBar>
<!-- TODO: make this button accessible -->
<div class="sidebar-mask" @click="toggleSidebar(false)" />
<Content v-if="isCustomLayout" />
<template v-else-if="enableHome">
<!-- A slot for customizing the entire homepage easily -->
<slot name="home">
<Home>
<template #hero>
<slot name="home-hero" />
</template>
<template #features>
<slot name="home-features" />
</template>
<template #footer>
<slot name="home-footer" />
</template>
</Home>
</slot>
</template>
<Page v-else>
<template #top>
<slot name="page-top-ads">
<div
id="ads-container"
v-if="theme.carbonAds && theme.carbonAds.carbon"
>
<CarbonAds
:key="'carbon' + page.relativePath"
:code="theme.carbonAds.carbon"
:placement="theme.carbonAds.placement"
/>
</div>
</slot>
<slot name="page-top" />
</template>
<template #bottom>
<slot name="page-bottom" />
<slot name="page-bottom-ads">
<BuySellAds
v-if="theme.carbonAds && theme.carbonAds.custom"
:key="'custom' + page.relativePath"
:code="theme.carbonAds.custom"
:placement="theme.carbonAds.placement"
/>
</slot>
</template>
</Page>
<div class="Layout">
<VPSkipLink />
<VPBackdrop class="backdrop" :show="isSidebarOpen" @click="closeSidebar" />
<VPNav />
<VPLocalNav :open="isSidebarOpen" @open-menu="openSidebar" />
<VPSidebar :open="isSidebarOpen" />
<VPContent>
<template #home-hero-before><slot name="home-hero-before" /></template>
<template #home-hero-after><slot name="home-hero-after" /></template>
<template #home-features-before><slot name="home-features-before" /></template>
<template #home-features-after><slot name="home-features-after" /></template>
<template #aside-top><slot name="aside-top" /></template>
<template #aside-bottom><slot name="aside-bottom" /></template>
<template #aside-outline-before><slot name="aside-outline-before" /></template>
<template #aside-outline-after><slot name="aside-outline-after" /></template>
<template #aside-ads-before><slot name="aside-ads-before" /></template>
<template #aside-ads-after><slot name="aside-ads-after" /></template>
</VPContent>
<VPFooter />
</div>
<Debug />
</template>
<style>
#ads-container {
margin: 0 auto;
}
@media (min-width: 420px) {
#ads-container {
position: relative;
right: 0;
float: right;
margin: -8px -8px 24px 24px;
width: 146px;
}
}
@media (max-width: 420px) {
#ads-container {
/* Avoid layout shift */
height: 105px;
margin: 1.75rem 0;
}
}
@media (min-width: 1400px) {
#ads-container {
position: fixed;
right: 8px;
bottom: 8px;
}
<style scoped>
.Layout {
display: flex;
flex-direction: column;
min-height: 100vh;
}
</style>

@ -1,23 +1,84 @@
<script setup lang="ts">
import { useData } from 'vitepress'
const { site } = useData()
</script>
<template>
<div class="theme">
<h1>404</h1>
<blockquote>{{ getMsg() }}</blockquote>
<a :href="site.base" aria-label="go to home">Take me home.</a>
<div class="NotFound">
<p class="code">404</p>
<h1 class="title">PAGE NOT FOUND</h1>
<div class="divider" />
<blockquote class="quote">
But if you don't change your direction, and if you keep looking, you may end up where you are heading.
</blockquote>
<div class="action">
<a class="link" :href="site.base" aria-label="go to home">
Take me home
</a>
</div>
</div>
</template>
<script setup lang="ts">
import { useData } from 'vitepress'
<style scoped>
.NotFound {
padding: 64px 24px 96px;
text-align: center;
}
const { site } = useData()
const msgs = [
`There's nothing here.`,
`How did we get here?`,
`That's a Four-Oh-Four.`,
`Looks like we've got some broken links.`
]
@media (min-width: 768px) {
.NotFound {
padding: 96px 32px 168px;
}
}
function getMsg() {
return msgs[Math.floor(Math.random() * msgs.length)]
.code {
line-height: 64px;
font-size: 64px;
font-weight: 600;
}
</script>
.title {
padding-top: 12px;
letter-spacing: 2px;
line-height: 20px;
font-size: 20px;
font-weight: 700;
}
.divider {
margin: 24px auto 18px;
width: 64px;
height: 1px;
background-color: var(--vp-c-divider)
}
.quote {
margin: 0 auto;
max-width: 256px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-text-2);
}
.action {
padding-top: 20px;
}
.link {
display: inline-block;
border: 1px solid var(--vp-c-brand);
border-radius: 16px;
padding: 3px 16px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-brand);
transition: border-color 0.25s, color .25s;
}
.link:hover {
border-color: var(--vp-c-brand-dark);
color: var(--vp-c-brand-dark);
}
</style>

@ -1,188 +0,0 @@
<script setup lang="ts">
import '@docsearch/css'
import docsearch from '@docsearch/js'
import { useRoute, useRouter, useData } from 'vitepress'
import { getCurrentInstance, onMounted, watch } from 'vue'
import type { DefaultTheme } from '../config'
import type { DocSearchHit } from '@docsearch/react/dist/esm/types'
const props = defineProps<{
options: DefaultTheme.AlgoliaSearchOptions
multilang?: boolean
}>()
const vm = getCurrentInstance()
const route = useRoute()
const router = useRouter()
watch(
() => props.options,
(value) => {
update(value)
}
)
onMounted(() => {
initialize(props.options)
})
function isSpecialClick(event: MouseEvent) {
return (
event.button === 1 ||
event.altKey ||
event.ctrlKey ||
event.metaKey ||
event.shiftKey
)
}
function getRelativePath(absoluteUrl: string) {
const { pathname, hash } = new URL(absoluteUrl)
return pathname + hash
}
function update(options: any) {
if (vm && vm.vnode.el) {
vm.vnode.el.innerHTML =
'<div class="algolia-search-box" id="docsearch"></div>'
initialize(options)
}
}
const { lang } = useData()
// if the user has multiple locales, the search results should be filtered
// based on the language
const facetFilters: string[] = props.multilang ? ['lang:' + lang.value] : []
if (props.options.searchParameters?.facetFilters) {
facetFilters.push(...props.options.searchParameters.facetFilters)
}
watch(lang, (newLang, oldLang) => {
const index = facetFilters.findIndex((filter) => filter === 'lang:' + oldLang)
if (index > -1) {
facetFilters.splice(index, 1, 'lang:' + newLang)
}
})
function initialize(userOptions: any) {
docsearch(
Object.assign({}, userOptions, {
container: '#docsearch',
searchParameters: Object.assign({}, userOptions.searchParameters, {
// pass a custom lang facetFilter to allow multiple language search
// https://github.com/algolia/docsearch-configs/pull/3942
facetFilters
}),
navigator: {
navigate: ({ itemUrl }: { itemUrl: string }) => {
const { pathname: hitPathname } = new URL(
window.location.origin + itemUrl
)
// Router doesn't handle same-page navigation so we use the native
// browser location API for anchor navigation
if (route.path === hitPathname) {
window.location.assign(window.location.origin + itemUrl)
} else {
router.go(itemUrl)
}
}
},
transformItems: (items: DocSearchHit[]) => {
return items.map((item) => {
return Object.assign({}, item, {
url: getRelativePath(item.url)
})
})
},
hitComponent: ({
hit,
children
}: {
hit: DocSearchHit
children: any
}) => {
const relativeHit = hit.url.startsWith('http')
? getRelativePath(hit.url as string)
: hit.url
return {
type: 'a',
ref: undefined,
constructor: undefined,
key: undefined,
props: {
href: hit.url,
onClick: (event: MouseEvent) => {
if (isSpecialClick(event)) {
return
}
// we rely on the native link scrolling when user is already on
// the right anchor because Router doesn't support duplicated
// history entries
if (route.path === relativeHit) {
return
}
// if the hits goes to another page, we prevent the native link
// behavior to leverage the Router loading feature
if (route.path !== relativeHit) {
event.preventDefault()
}
router.go(relativeHit)
},
children
},
__v: null
}
}
})
)
}
</script>
<template>
<div class="algolia-search-box" id="docsearch" />
</template>
<style>
.algolia-search-box {
padding-top: 1px;
}
@media (min-width: 720px) {
.algolia-search-box {
padding-left: 8px;
}
}
@media (min-width: 751px) {
.algolia-search-box {
min-width: 176.3px; /* avoid layout shift */
}
.algolia-search-box .DocSearch-Button-Placeholder {
padding-left: 8px;
font-size: 0.9rem;
font-weight: 500;
}
}
.DocSearch {
--docsearch-primary-color: var(--c-brand);
--docsearch-highlight-color: var(--docsearch-primary-color);
--docsearch-searchbox-shadow: inset 0 0 0 2px var(--docsearch-primary-color);
--docsearch-text-color: var(--c-text-light);
--docsearch-muted-color: var(--c-text-lighter);
--docsearch-searchbox-background: #f2f2f2;
}
</style>

@ -1,152 +0,0 @@
<script setup lang="ts">
import { onMounted } from 'vue'
// global _bsa
const ID = 'bsa-cpc-script'
declare global {
var _bsa: BSA | undefined
interface BSA {
init(
name: string,
code: string,
placement: string,
options: {
target: string
align: string
disable_css?: 'true' | 'false'
}
): void
}
}
const { code, placement } = defineProps<{
code: string
placement: string
}>()
onMounted(() => {
if (!document.getElementById(ID)) {
const s = document.createElement('script')
s.id = ID
s.src = '//m.servedby-buysellads.com/monetization.js'
document.head.appendChild(s)
s.onload = () => {
load()
}
} else {
load()
}
})
function load() {
if (typeof _bsa !== 'undefined' && _bsa) {
const parent = document.querySelector('.bsa-cpc')!
// cleanup any existing ad to avoid them stacking
parent.innerHTML = ''
_bsa.init('default', code, `placement:${placement}`, {
target: '.bsa-cpc',
align: 'horizontal',
disable_css: 'true'
})
}
}
</script>
<template>
<div class="buy-sell-ads">
<div class="bsa-cpc" />
</div>
</template>
<style scoped>
.buy-sell-ads {
margin: 0 auto;
padding-top: 2rem;
font-size: 0.85rem;
}
.bsa-cpc {
border-radius: 6px;
background-color: var(--c-bg-accent);
}
.bsa-cpc ::v-deep(a._default_) {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
margin-bottom: 20px;
padding: 12px;
text-decoration: none;
line-height: 1.4;
font-weight: 400;
color: var(--c-text-light);
}
@media (min-width: 512px) {
.bsa-cpc ::v-deep(a._default_) {
flex-wrap: nowrap;
}
}
.bsa-cpc ::v-deep(.default-ad) {
display: none;
}
.bsa-cpc ::v-deep(a._default_ .default-image) {
flex-shrink: 0;
margin-right: 12px;
width: 24px;
}
.bsa-cpc ::v-deep(a._default_ .default-image img) {
border-radius: 4px;
height: 24px;
vertical-align: middle;
}
.bsa-cpc ::v-deep(._default_::after) {
border: 1px solid #1c90f3;
border-radius: 4px;
margin-top: 8px;
margin-left: 36px;
padding: 0 8px;
line-height: 22px;
font-size: 0.85em;
font-weight: 500;
color: #1c90f3;
content: 'Sponsored';
}
@media (min-width: 512px) {
.bsa-cpc ::v-deep(._default_::after) {
margin-top: 0px;
margin-left: 12px;
}
}
.bsa-cpc ::v-deep(.default-text) {
flex-grow: 1;
align-self: center;
width: calc(100% - 36px);
}
@media (min-width: 512px) {
.bsa-cpc ::v-deep(.default-text) {
width: auto;
}
}
.bsa-cpc ::v-deep(.default-title) {
font-weight: 600;
}
.bsa-cpc ::v-deep(.default-description) {
padding-left: 8px;
}
</style>

@ -1,99 +0,0 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const { code, placement } = defineProps<{
code: string
placement: string
}>()
const el = ref()
onMounted(() => {
const s = document.createElement('script')
s.id = '_carbonads_js'
s.src = `//cdn.carbonads.com/carbon.js?serve=${code}&placement=${placement}`
el.value.appendChild(s)
})
</script>
<template>
<div class="carbon-ads" ref="el" />
</template>
<style scoped>
.carbon-ads {
border-radius: 4px;
margin: 0 auto;
padding: 8px;
max-width: 280px;
font-size: 0.75rem;
background-color: var(--c-bg-accent);
min-height: 105.38px; /* avoid layout shift on mobile */
}
.carbon-ads::after {
clear: both;
display: block;
content: '';
}
@media (min-width: 420px) {
.carbon-ads {
z-index: 1;
float: right;
margin: -8px -8px 24px 24px;
width: 146px;
max-width: 100%;
min-height: 200px;
}
}
@media (min-width: 1400px) {
.carbon-ads {
right: 8px;
float: none;
margin: 0;
}
}
.carbon-ads :deep(.carbon-img) {
float: left;
margin-right: 0.75rem;
max-width: 110px;
border: 1px solid var(--c-divider);
}
@media (min-width: 420px) {
.carbon-ads :deep(.carbon-img) {
float: none;
display: block;
margin-right: 0;
max-width: 130px;
}
}
.carbon-ads :deep(.carbon-img img) {
display: block;
width: 100%;
height: auto;
}
@media (min-width: 420px) {
.carbon-ads :deep(.carbon-text) {
padding-top: 8px;
}
}
.carbon-ads :deep(.carbon-text) {
display: block;
font-weight: 400;
color: var(--c-text-light);
}
.carbon-ads :deep(.carbon-poweredby) {
display: block;
padding-top: 2px;
font-weight: 400;
color: var(--c-text-lighter);
}
</style>

@ -1,38 +0,0 @@
<script setup lang="ts">
import { useEditLink } from '../composables/editLink'
import OutboundLink from './icons/OutboundLink.vue'
const { url, text } = useEditLink()
</script>
<template>
<div class="edit-link">
<a
v-if="url"
class="link"
:href="url"
target="_blank"
rel="noopener noreferrer"
>
{{ text }} <OutboundLink class="icon" />
</a>
</div>
</template>
<style scoped>
.link {
display: inline-block;
font-size: 1rem;
font-weight: 500;
color: var(--c-text-light);
}
.link:hover {
text-decoration: none;
color: var(--c-brand);
}
.icon {
margin-left: 4px;
}
</style>

@ -1,31 +0,0 @@
<script setup lang="ts">
import HomeHero from './HomeHero.vue'
import HomeFeatures from './HomeFeatures.vue'
import HomeFooter from './HomeFooter.vue'
</script>
<template>
<main class="home" aria-labelledby="main-title">
<HomeHero />
<slot name="hero" />
<HomeFeatures />
<div class="home-content">
<Content />
</div>
<slot name="features" />
<HomeFooter />
<slot name="footer" />
</main>
</template>
<style scoped>
.home {
padding-top: var(--header-height);
}
.home-content {
max-width: 960px;
margin: 0px auto;
padding: 0 1.5rem;
}
</style>

@ -1,143 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useData } from 'vitepress'
const { frontmatter } = useData()
const hasFeatures = computed(() => {
return frontmatter.value.features && frontmatter.value.features.length > 0
})
interface Feature {
title?: string
details?: string
}
const features = computed<Feature[]>(() => {
return frontmatter.value.features ? frontmatter.value.features : []
})
</script>
<template>
<div v-if="hasFeatures" class="home-features">
<div class="wrapper">
<div class="container">
<div class="features">
<section
v-for="(feature, index) in features"
:key="index"
class="feature"
>
<h2 class="title" v-if="feature.title">{{ feature.title }}</h2>
<p class="details" v-if="feature.details">{{ feature.details }}</p>
</section>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.home-features {
margin: 0 auto;
padding: 2.5rem 0 2.75rem;
max-width: 960px;
}
.home-hero + .home-features {
padding-top: 0;
}
@media (min-width: 420px) {
.home-features {
padding: 3.25rem 0 3.5rem;
}
.home-hero + .home-features {
padding-top: 0;
}
}
@media (min-width: 720px) {
.home-features {
padding-right: 1.5rem;
padding-left: 1.5rem;
}
}
.wrapper {
padding: 0 1.5rem;
}
.home-hero + .home-features .wrapper {
border-top: 1px solid var(--c-divider);
padding-top: 2.5rem;
}
@media (min-width: 420px) {
.home-hero + .home-features .wrapper {
padding-top: 3.25rem;
}
}
@media (min-width: 720px) {
.wrapper {
padding-right: 0;
padding-left: 0;
}
}
.container {
margin: 0 auto;
max-width: 392px;
}
@media (min-width: 720px) {
.container {
max-width: 960px;
}
}
.features {
display: flex;
flex-wrap: wrap;
margin: -20px -24px;
}
.feature {
flex-shrink: 0;
padding: 20px 24px;
width: 100%;
}
@media (min-width: 720px) {
.feature {
width: calc(100% / 3);
}
}
.title {
margin: 0;
border-bottom: 0;
line-height: 1.4;
font-size: 1.25rem;
font-weight: 500;
}
@media (min-width: 420px) {
.title {
font-size: 1.4rem;
}
}
.details {
margin: 0;
line-height: 1.6;
font-size: 1rem;
color: var(--c-text-light);
}
.title + .details {
padding-top: 0.25rem;
}
</style>

@ -1,50 +0,0 @@
<script setup lang="ts">
import { useData } from 'vitepress'
const { frontmatter } = useData()
</script>
<template>
<footer v-if="frontmatter.footer" class="footer">
<div class="container">
<p class="text">{{ frontmatter.footer }}</p>
</div>
</footer>
</template>
<style scoped>
.footer {
margin: 0 auto;
max-width: 960px;
}
@media (min-width: 720px) {
.footer {
padding: 0 1.5rem;
}
}
.container {
padding: 2rem 1.5rem 2.25rem;
}
.home-hero + .footer .container,
.home-features + .footer .container,
.home-content + .footer .container {
border-top: 1px solid var(--c-divider);
}
@media (min-width: 420px) {
.container {
padding: 3rem 1.5rem 3.25rem;
}
}
.text {
margin: 0;
text-align: center;
line-height: 1.4;
font-size: 0.9rem;
color: var(--c-text-light);
}
</style>

@ -1,161 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useData, withBase } from 'vitepress'
import NavLink from './NavLink.vue'
const { site, frontmatter } = useData()
const showHero = computed(() => {
const { heroImage, heroText, tagline, actionLink, actionText } =
frontmatter.value
return heroImage || heroText || tagline || (actionLink && actionText)
})
const heroText = computed(() => frontmatter.value.heroText || site.value.title)
const tagline = computed(
() => frontmatter.value.tagline || site.value.description
)
</script>
<template>
<header v-if="showHero" class="home-hero">
<figure v-if="frontmatter.heroImage" class="figure">
<img
class="image"
:src="withBase(frontmatter.heroImage)"
:alt="frontmatter.heroAlt"
/>
</figure>
<h1 v-if="heroText" id="main-title" class="title">{{ heroText }}</h1>
<p v-if="tagline" class="tagline">{{ tagline }}</p>
<NavLink
v-if="frontmatter.actionLink && frontmatter.actionText"
:item="{ link: frontmatter.actionLink, text: frontmatter.actionText }"
class="action"
/>
<NavLink
v-if="frontmatter.altActionLink && frontmatter.altActionText"
:item="{
link: frontmatter.altActionLink,
text: frontmatter.altActionText
}"
class="action alt"
/>
</header>
</template>
<style scoped>
.home-hero {
margin: 2.5rem 0 2.75rem;
padding: 0 1.5rem;
text-align: center;
}
@media (min-width: 420px) {
.home-hero {
margin: 3.5rem 0;
}
}
@media (min-width: 720px) {
.home-hero {
margin: 4rem 0 4.25rem;
}
}
.figure {
padding: 0 1.5rem;
}
.image {
display: block;
margin: 0 auto;
width: auto;
max-width: 100%;
max-height: 280px;
}
.title {
margin-top: 1.5rem;
font-size: 2rem;
}
@media (min-width: 420px) {
.title {
font-size: 3rem;
}
}
@media (min-width: 720px) {
.title {
margin-top: 2rem;
}
}
.tagline {
margin: 0;
margin-top: 0.25rem;
line-height: 1.3;
font-size: 1.2rem;
color: var(--c-text-light);
}
@media (min-width: 420px) {
.tagline {
line-height: 1.2;
font-size: 1.6rem;
}
}
.action {
margin-top: 1.5rem;
display: inline-block;
}
.action.alt {
margin-left: 1.5rem;
}
@media (min-width: 420px) {
.action {
margin-top: 2rem;
display: inline-block;
}
}
.action :deep(.item) {
display: inline-block;
border-radius: 6px;
padding: 0 20px;
line-height: 44px;
font-size: 1rem;
font-weight: 500;
color: var(--c-bg);
background-color: var(--c-brand);
border: 2px solid var(--c-brand);
transition: background-color 0.1s ease;
}
.action.alt :deep(.item) {
background-color: var(--c-bg);
color: var(--c-brand);
}
.action :deep(.item:hover) {
text-decoration: none;
color: var(--c-bg);
background-color: var(--c-brand-light);
}
@media (min-width: 420px) {
.action :deep(.item) {
padding: 0 24px;
line-height: 52px;
font-size: 1.2rem;
font-weight: 500;
}
}
</style>

@ -1,60 +0,0 @@
<script setup lang="ts">
import { ref, computed, onMounted, watchEffect } from 'vue'
import { useData } from 'vitepress'
const { theme, page } = useData()
const hasLastUpdated = computed(() => {
const lu = theme.value.lastUpdated
return lu !== undefined && lu !== false && page.value.lastUpdated !== 0
})
const prefix = computed(() => {
const p = theme.value.lastUpdated
return p === true ? 'Last Updated' : p
})
const datetime = ref('')
onMounted(() => {
watchEffect(() => {
// locale string might be different based on end user
// and will lead to potential hydration mismatch if calculated at build time
datetime.value = new Date(page.value.lastUpdated!).toLocaleString('en-US')
})
})
</script>
<template>
<p v-if="hasLastUpdated" class="last-updated">
<span class="prefix">{{ prefix }}:</span>
<span class="datetime">{{ datetime }}</span>
</p>
</template>
<style scoped>
.last-updated {
display: inline-block;
margin: 0;
line-height: 1.4;
font-size: 0.9rem;
color: var(--c-text-light);
}
@media (min-width: 960px) {
.last-updated {
font-size: 1rem;
}
}
.prefix {
display: inline-block;
font-weight: 500;
}
.datetime {
display: inline-block;
margin-left: 6px;
font-weight: 400;
}
</style>

@ -1,60 +0,0 @@
<script setup lang="ts">
import NavBarTitle from './NavBarTitle.vue'
import NavLinks from './NavLinks.vue'
import ToggleSideBarButton from './ToggleSideBarButton.vue'
defineEmits(['toggle'])
</script>
<template>
<header class="nav-bar">
<ToggleSideBarButton @toggle="$emit('toggle')" />
<NavBarTitle />
<div class="flex-grow" />
<div class="nav">
<NavLinks />
</div>
<slot name="search" />
</header>
</template>
<style scoped>
.nav-bar {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: var(--z-index-navbar);
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--c-divider);
padding: 0.7rem 1.5rem 0.7rem 4rem;
height: var(--header-height);
background-color: var(--c-bg);
}
@media (min-width: 720px) {
.nav-bar {
padding: 0.7rem 1.5rem;
}
}
.flex-grow {
flex-grow: 1;
}
.nav {
display: none;
}
@media (min-width: 720px) {
.nav {
display: block;
}
}
</style>

@ -1,41 +0,0 @@
<script setup lang="ts">
import { withBase, useData } from 'vitepress'
const { site, theme, localePath } = useData()
</script>
<template>
<a
class="nav-bar-title"
:href="localePath"
:aria-label="`${site.title}, back to home`"
>
<img
v-if="theme.logo"
class="logo"
:src="withBase(theme.logo)"
alt="Logo"
/>
{{ site.title }}
</a>
</template>
<style scoped>
.nav-bar-title {
font-size: 1.3rem;
font-weight: 600;
color: var(--c-text);
display: flex;
justify-content: center;
align-items: center;
}
.nav-bar-title:hover {
text-decoration: none;
}
.logo {
margin-right: 0.75rem;
height: 1.3rem;
vertical-align: bottom;
}
</style>

@ -1,135 +0,0 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useRoute } from 'vitepress'
import type { DefaultTheme } from '../config'
import NavDropdownLinkItem from './NavDropdownLinkItem.vue'
defineProps<{
item: DefaultTheme.NavItemWithChildren
}>()
const route = useRoute()
const open = ref(false)
watch(
() => route.path,
() => {
open.value = false
}
)
function toggle() {
open.value = !open.value
}
</script>
<template>
<div class="nav-dropdown-link" :class="{ open }">
<button class="button" :aria-label="item.ariaLabel" @click="toggle">
<span class="button-text">{{ item.text }}</span>
<span class="button-arrow" :class="open ? 'down' : 'right'" />
</button>
<ul class="dialog">
<li v-for="item in item.items" :key="item.text" class="dialog-item">
<NavDropdownLinkItem :item="item" />
</li>
</ul>
</div>
</template>
<style scoped>
.nav-dropdown-link {
position: relative;
height: 36px;
overflow: hidden;
cursor: pointer;
}
@media (min-width: 720px) {
.nav-dropdown-link {
height: auto;
overflow: visible;
}
.nav-dropdown-link:hover .dialog {
display: block;
}
}
.nav-dropdown-link.open {
height: auto;
}
.button {
display: block;
border: 0;
padding: 0 1.5rem;
width: 100%;
text-align: left;
line-height: 36px;
font-family: var(--font-family-base);
font-size: 1rem;
font-weight: 600;
color: var(--c-text);
white-space: nowrap;
background-color: transparent;
cursor: pointer;
}
.button:focus {
outline: 0;
}
@media (min-width: 720px) {
.button {
border-bottom: 2px solid transparent;
padding: 0;
line-height: 24px;
font-size: 0.9rem;
font-weight: 500;
}
}
.button-arrow {
display: inline-block;
margin-top: -1px;
margin-left: 8px;
border-top: 6px solid #ccc;
border-right: 4px solid transparent;
border-bottom: 0;
border-left: 4px solid transparent;
vertical-align: middle;
}
.button-arrow.right {
transform: rotate(-90deg);
}
@media (min-width: 720px) {
.button-arrow.right {
transform: rotate(0);
}
}
.dialog {
margin: 0;
padding: 0;
list-style: none;
}
@media (min-width: 720px) {
.dialog {
display: none;
position: absolute;
top: 26px;
right: -8px;
border-radius: 6px;
padding: 12px 0;
min-width: 128px;
background-color: var(--c-bg);
box-shadow: var(--shadow-3);
}
}
</style>

@ -1,76 +0,0 @@
<script setup lang="ts">
import { toRefs } from 'vue'
import type { DefaultTheme } from '../config'
import { useNavLink } from '../composables/navLink'
import OutboundLink from './icons/OutboundLink.vue'
const props = defineProps<{
item: DefaultTheme.NavItemWithLink
}>()
const propsRefs = toRefs(props)
const { props: linkProps, isExternal } = useNavLink(propsRefs.item)
</script>
<template>
<div class="nav-dropdown-link-item">
<a class="item" v-bind="linkProps">
<span class="arrow" />
<span class="text">{{ item.text }}</span>
<span class="icon"><OutboundLink v-if="isExternal" /></span>
</a>
</div>
</template>
<style scoped>
.item {
display: block;
padding: 0 1.5rem 0 2.5rem;
line-height: 32px;
font-size: 0.9rem;
font-weight: 500;
color: var(--c-text);
white-space: nowrap;
}
@media (min-width: 720px) {
.item {
padding: 0 24px 0 12px;
line-height: 32px;
font-size: 0.85rem;
font-weight: 500;
color: var(--c-text);
white-space: nowrap;
}
.item.active .arrow {
opacity: 1;
}
}
.item:hover,
.item.active {
text-decoration: none;
color: var(--c-brand);
}
.item.external:hover {
border-bottom-color: transparent;
color: var(--c-text);
}
@media (min-width: 720px) {
.arrow {
display: inline-block;
margin-right: 8px;
border-top: 6px solid #ccc;
border-right: 4px solid transparent;
border-bottom: 0;
border-left: 4px solid transparent;
vertical-align: middle;
opacity: 0;
transform: translateY(-2px) rotate(-90deg);
}
}
</style>

@ -1,61 +0,0 @@
<script setup lang="ts">
import { toRefs } from 'vue'
import type { DefaultTheme } from '../config'
import { useNavLink } from '../composables/navLink'
import OutboundLink from './icons/OutboundLink.vue'
const props = defineProps<{
item: DefaultTheme.NavItemWithLink
}>()
const propsRefs = toRefs(props)
const { props: linkProps, isExternal } = useNavLink(propsRefs.item)
</script>
<template>
<div class="nav-link">
<a class="item" v-bind="linkProps">
{{ item.text }} <OutboundLink v-if="isExternal" />
</a>
</div>
</template>
<style scoped>
.item {
display: block;
padding: 0 1.5rem;
line-height: 36px;
font-size: 1rem;
font-weight: 600;
color: var(--c-text);
white-space: nowrap;
}
.item:hover,
.item.active {
text-decoration: none;
color: var(--c-brand);
}
.item.external:hover {
border-bottom-color: transparent;
color: var(--c-text);
}
@media (min-width: 720px) {
.item {
border-bottom: 2px solid transparent;
padding: 0;
line-height: 24px;
font-size: 0.9rem;
font-weight: 500;
}
.item:hover,
.item.active {
border-bottom-color: var(--c-brand);
color: var(--c-text);
}
}
</style>

@ -1,52 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useData } from 'vitepress'
import { useLanguageLinks } from '../composables/nav'
import { useRepo } from '../composables/repo'
import NavLink from './NavLink.vue'
import NavDropdownLink from './NavDropdownLink.vue'
const { theme } = useData()
const localeLinks = useLanguageLinks()
const repo = useRepo()
const show = computed(() => theme.value.nav || repo.value || localeLinks.value)
</script>
<template>
<nav v-if="show" class="nav-links">
<template v-if="theme.nav">
<div v-for="item in theme.nav" :key="item.text" class="item">
<NavDropdownLink v-if="item.items" :item="item" />
<NavLink v-else :item="item" />
</div>
</template>
<div v-if="localeLinks" class="item">
<NavDropdownLink :item="localeLinks" />
</div>
<div v-if="repo" class="item">
<NavLink :item="repo" />
</div>
</nav>
</template>
<style scoped>
.nav-links {
padding: 0.75rem 0;
border-bottom: 1px solid var(--c-divider);
}
@media (min-width: 720px) {
.nav-links {
display: flex;
padding: 6px 0 0;
align-items: center;
border-bottom: 0;
}
.item + .item {
padding-left: 24px;
}
}
</style>

@ -1,88 +0,0 @@
<script setup lang="ts">
import { withBase } from 'vitepress'
import { useNextAndPrevLinks } from '../composables/nextAndPrevLinks'
import ArrowLeft from './icons/ArrowLeft.vue'
import ArrowRight from './icons/ArrowRight.vue'
const { hasLinks, prev, next } = useNextAndPrevLinks()
</script>
<template>
<div v-if="hasLinks" class="next-and-prev-link">
<div class="container">
<div class="prev">
<a v-if="prev" class="link" :href="withBase(prev.link)">
<ArrowLeft class="icon icon-prev" />
<span class="text">{{ prev.text }}</span>
</a>
</div>
<div class="next">
<a v-if="next" class="link" :href="withBase(next.link)">
<span class="text">{{ next.text }}</span>
<ArrowRight class="icon icon-next" />
</a>
</div>
</div>
</div>
</template>
<style scoped>
.next-and-prev-link {
padding-top: 1rem;
}
.container {
display: flex;
justify-content: space-between;
border-top: 1px solid var(--c-divider);
padding-top: 1rem;
}
.prev,
.next {
display: flex;
flex-shrink: 0;
width: 50%;
}
.prev {
justify-content: flex-start;
padding-right: 12px;
}
.next {
justify-content: flex-end;
padding-left: 12px;
}
.link {
display: inline-flex;
align-items: center;
max-width: 100%;
font-size: 1rem;
font-weight: 500;
}
.text {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.icon {
display: block;
flex-shrink: 0;
width: 16px;
height: 16px;
fill: var(--c-text);
transform: translateY(1px);
}
.icon-prev {
margin-right: 8px;
}
.icon-next {
margin-left: 8px;
}
</style>

@ -1,53 +0,0 @@
<script setup lang="ts">
import PageFooter from './PageFooter.vue'
import NextAndPrevLinks from './NextAndPrevLinks.vue'
</script>
<template>
<main class="page">
<div class="container">
<slot name="top" />
<Content class="content" />
<PageFooter />
<NextAndPrevLinks />
<slot name="bottom" />
</div>
</main>
</template>
<style scoped>
.page {
padding-top: var(--header-height);
}
@media (min-width: 720px) {
.page {
margin-left: 16.4rem;
}
}
@media (min-width: 960px) {
.page {
margin-left: 20rem;
}
}
.container {
margin: 0 auto;
padding: 0 1.5rem 4rem;
max-width: 48rem;
}
.content {
padding-bottom: 1.5rem;
}
@media (max-width: 420px) {
.content {
/* fix carbon ads display */
clear: both;
}
}
</style>

@ -1,44 +0,0 @@
<script setup lang="ts">
import EditLink from './EditLink.vue'
import LastUpdated from './LastUpdated.vue'
import { useData } from 'vitepress'
const { page } = useData()
</script>
<template>
<footer class="page-footer">
<div class="edit">
<EditLink />
</div>
<div class="updated">
<LastUpdated v-if="page.lastUpdated" />
</div>
</footer>
</template>
<style scoped>
.page-footer {
padding-top: 1rem;
padding-bottom: 1rem;
overflow: auto;
}
@media (min-width: 960px) {
.page-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
}
.updated {
padding-top: 4px;
}
@media (min-width: 960px) {
.updated {
padding-top: 0;
}
}
</style>

@ -1,60 +0,0 @@
<script setup lang="ts">
import NavLinks from './NavLinks.vue'
import SideBarLinks from './SideBarLinks.vue'
defineProps<{ open: boolean }>()
</script>
<template>
<aside class="sidebar" :class="{ open }">
<NavLinks class="nav" />
<slot name="sidebar-top" />
<SideBarLinks />
<slot name="sidebar-bottom" />
</aside>
</template>
<style scoped>
.sidebar {
position: fixed;
top: var(--header-height);
bottom: 0;
left: 0;
z-index: var(--z-index-sidebar);
border-right: 1px solid var(--c-divider);
width: 16.4rem;
background-color: var(--c-bg);
overflow-y: auto;
transform: translateX(-100%);
transition: transform 0.25s ease;
}
@media (min-width: 720px) {
.sidebar {
transform: translateX(0);
}
}
@media (min-width: 960px) {
.sidebar {
width: 20rem;
}
}
.sidebar.open {
transform: translateX(0);
}
.nav {
display: block;
}
@media (min-width: 720px) {
.nav {
display: none;
}
}
</style>

@ -1,100 +0,0 @@
import { FunctionalComponent, h, VNode } from 'vue'
import { useRoute, useData } from 'vitepress'
import { Header } from '../../shared'
import { DefaultTheme } from '../config'
import { joinUrl, isActive } from '../utils'
interface HeaderWithChildren extends Header {
children?: Header[]
}
export const SideBarLink: FunctionalComponent<{
item: DefaultTheme.SideBarItem
depth?: number
}> = (props) => {
const route = useRoute()
const { site, frontmatter } = useData()
const depth = props.depth || 1
const maxDepth = frontmatter.value.sidebarDepth || Infinity
const headers = route.data.headers
const text = props.item.text
const link = resolveLink(site.value.base, props.item.link)
const children = (props.item as DefaultTheme.SideBarGroup).children
const active = isActive(route, props.item.link)
const childItems =
depth < maxDepth
? createChildren(active, children, headers, depth + 1)
: null
return h('li', { class: 'sidebar-link' }, [
h(
link ? 'a' : 'p',
{
class: { 'sidebar-link-item': true, active },
href: link
},
text
),
childItems
])
}
function resolveLink(base: string, path?: string): string | undefined {
if (path === undefined) {
return path
}
// keep relative hash to the same page
if (path.startsWith('#')) {
return path
}
return joinUrl(base, path)
}
function createChildren(
active: boolean,
children?: DefaultTheme.SideBarItem[],
headers?: Header[],
depth = 1
): VNode | null {
if (children && children.length > 0) {
return h(
'ul',
{ class: 'sidebar-links' },
children.map((c) => {
return h(SideBarLink, { item: c, depth })
})
)
}
return active && headers
? createChildren(false, resolveHeaders(headers), undefined, depth)
: null
}
function resolveHeaders(headers: Header[]): DefaultTheme.SideBarItem[] {
return mapHeaders(groupHeaders(headers))
}
function groupHeaders(headers: Header[]): HeaderWithChildren[] {
headers = headers.map((h) => Object.assign({}, h))
let lastH2: HeaderWithChildren
headers.forEach((h) => {
if (h.level === 2) {
lastH2 = h
} else if (lastH2) {
;(lastH2.children || (lastH2.children = [])).push(h)
}
})
return headers.filter((h) => h.level === 2)
}
function mapHeaders(headers: HeaderWithChildren[]): DefaultTheme.SideBarItem[] {
return headers.map((header) => ({
text: header.title,
link: `#${header.slug}`,
children: header.children ? mapHeaders(header.children) : undefined
}))
}

@ -1,12 +0,0 @@
<script setup lang="ts">
import { useSideBar } from '../composables/sideBar'
import { SideBarLink } from './SideBarLink'
const items = useSideBar()
</script>
<template>
<ul v-if="items.length > 0" class="sidebar-links">
<SideBarLink v-for="item of items" :item="item" />
</ul>
</template>

@ -1,46 +0,0 @@
<script lang="ts">
export default {
emits: ['toggle']
}
</script>
<template>
<div class="sidebar-button" @click="$emit('toggle')">
<svg
class="icon"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
role="img"
viewBox="0 0 448 512"
>
<path
fill="currentColor"
d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"
class
/>
</svg>
</div>
</template>
<style>
.sidebar-button {
position: absolute;
top: 0.6rem;
left: 1rem;
display: none;
padding: 0.6rem;
cursor: pointer;
}
.sidebar-button .icon {
display: block;
width: 1.25rem;
height: 1.25rem;
}
@media screen and (max-width: 719px) {
.sidebar-button {
display: block;
}
}
</style>

@ -0,0 +1,127 @@
<script setup lang="ts">
import docsearch from '@docsearch/js'
import { DocSearchHit } from '@docsearch/react/dist/esm/types'
import { onMounted } from 'vue'
import { useRouter, useRoute, useData } from 'vitepress'
import { DefaultTheme } from '../config'
const router = useRouter()
const route = useRoute()
const { theme } = useData()
onMounted(() => {
initialize(theme.value.algolia)
setTimeout(poll, 16)
})
function poll() {
// programmatically open the search box after initialize
const e = new Event('keydown') as any
e.key = 'k'
e.metaKey = true
window.dispatchEvent(e)
setTimeout(() => {
if (!document.querySelector('.DocSearch-Modal')) {
poll()
}
}, 16)
}
function initialize(userOptions: DefaultTheme.AlgoliaSearchOptions) {
// note: multi-lang search support is removed since the theme
// doesn't support multiple locales as of now.
const options = Object.assign({}, userOptions, {
container: '#docsearch',
navigator: {
navigate({ itemUrl }: { itemUrl: string }) {
const { pathname: hitPathname } = new URL(
window.location.origin + itemUrl
)
// router doesn't handle same-page navigation so we use the native
// browser location API for anchor navigation
if (route.path === hitPathname) {
window.location.assign(window.location.origin + itemUrl)
} else {
router.go(itemUrl)
}
}
},
transformItems(items: DocSearchHit[]) {
return items.map((item) => {
return Object.assign({}, item, {
url: getRelativePath(item.url)
})
})
},
hitComponent({ hit, children }: { hit: DocSearchHit, children: any }) {
const relativeHit = hit.url.startsWith('http')
? getRelativePath(hit.url as string)
: hit.url
return {
__v: null,
type: 'a',
ref: undefined,
constructor: undefined,
key: undefined,
props: {
href: hit.url,
onClick(event: MouseEvent) {
if (isSpecialClick(event)) {
return
}
// we rely on the native link scrolling when user is already on
// the right anchor because Router doesn't support duplicated
// history entries.
if (route.path === relativeHit) {
return
}
// if the hits goes to another page, we prevent the native link
// behavior to leverage the Router loading feature.
if (route.path !== relativeHit) {
event.preventDefault()
}
router.go(relativeHit)
},
children
}
}
}
})
docsearch(options)
}
function isSpecialClick(event: MouseEvent) {
return (
event.button === 1 ||
event.altKey ||
event.ctrlKey ||
event.metaKey ||
event.shiftKey
)
}
function getRelativePath(absoluteUrl: string) {
const { pathname, hash } = new URL(absoluteUrl)
return pathname + hash
}
</script>
<template>
<div id="docsearch" />
</template>

@ -0,0 +1,39 @@
<script lang="ts" setup>
defineProps<{
show: boolean
}>()
</script>
<template>
<transition name="fade">
<div v-if="show" class="VPBackdrop" />
</transition>
</template>
<style scoped>
.VPBackdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: var(--vp-z-index-backdrop);
background: rgba(0, 0, 0, .6);
transition: opacity 0.5s;
}
.VPBackdrop.fade-enter-from,
.VPBackdrop.fade-leave-to {
opacity: 0;
}
.VPBackdrop.fade-leave-active {
transition-duration: .25s;
}
@media (min-width: 1280px) {
.VPBackdrop {
display: none;
}
}
</style>

@ -0,0 +1,55 @@
<script setup lang="ts">
defineProps<{
icon?: string
title: string
details: string
}>()
</script>
<template>
<article class="VPBox">
<div v-if="icon" class="icon">{{ icon }}</div>
<h1 class="title">{{ title }}</h1>
<p class="details">{{ details }}</p>
</article>
</template>
<style scoped>
.VPBox {
border: 1px solid var(--vp-c-divider-light);
border-radius: 12px;
padding: 24px;
height: 100%;
background-color: var(--vp-c-bg-soft);
}
.icon {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
border-radius: 6px;
background-color: var(--vp-c-gray-light-4);
width: 48px;
height: 48px;
font-size: 24px;
}
.dark .icon {
background-color: var(--vp-c-bg);
}
.title {
line-height: 24px;
font-size: 16px;
font-weight: 600;
}
.details {
padding-top: 8px;
line-height: 24px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-text-2);
}
</style>

@ -0,0 +1,122 @@
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{
tag?: string
size?: 'medium' | 'big'
theme?: 'brand' | 'alt' | 'sponsor'
text: string
href?: string
}>()
const classes = computed(() => [
props.size ?? 'medium',
props.theme ?? 'brand'
])
const isExternal = computed(() => props.href && /^[a-z]+:/i.test(props.href))
const component = computed(() => {
if (props.tag) {
return props.tag
}
return props.href ? 'a' : 'button'
})
</script>
<template>
<component
:is="component"
class="VPButton"
:class="classes"
:href="href"
:target="isExternal ? '_blank' : undefined"
:rel="isExternal ? 'noopener noreferrer' : undefined"
>
{{ text }}
</component>
</template>
<style scoped>
.VPButton {
display: inline-block;
border: 1px solid transparent;
text-align: center;
font-weight: 500;
white-space: nowrap;
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
}
.VPButton:active {
transition: color 0.1s, border-color 0.1s, background-color 0.1s;
}
.VPButton.medium {
border-radius: 20px;
padding: 0 20px;
line-height: 38px;
font-size: 14px;
}
.VPButton.big {
border-radius: 24px;
padding: 0 24px;
line-height: 46px;
font-size: 16px;
}
.VPButton.brand {
border-color: var(--vp-button-brand-border);
color: var(--vp-button-brand-text);
background-color: var(--vp-button-brand-bg);
}
.VPButton.brand:hover {
border-color: var(--vp-button-brand-hover-border);
color: var(--vp-button-brand-hover-text);
background-color: var(--vp-button-brand-hover-bg);
}
.VPButton.brand:active {
border-color: var(--vp-button-brand-active-border);
color: var(--vp-button-brand-active-text);
background-color: var(--vp-button-brand-active-bg);
}
.VPButton.alt {
border-color: var(--vp-button-alt-border);
color: var(--vp-button-alt-text);
background-color: var(--vp-button-alt-bg);
}
.VPButton.alt:hover {
border-color: var(--vp-button-alt-hover-border);
color: var(--vp-button-alt-hover-text);
background-color: var(--vp-button-alt-hover-bg);
}
.VPButton.alt:active {
border-color: var(--vp-button-alt-active-border);
color: var(--vp-button-alt-active-text);
background-color: var(--vp-button-alt-active-bg);
}
.VPButton.sponsor {
border-color: var(--vp-button-sponsor-border);
color: var(--vp-button-sponsor-text);
background-color: var(--vp-button-sponsor-bg);
}
.VPButton.sponsor:hover {
border-color: var(--vp-button-sponsor-hover-border);
color: var(--vp-button-sponsor-hover-text);
background-color: var(--vp-button-sponsor-hover-bg);
}
.VPButton.sponsor:active {
border-color: var(--vp-button-sponsor-active-border);
color: var(--vp-button-sponsor-active-text);
background-color: var(--vp-button-sponsor-active-bg);
}
</style>

@ -0,0 +1,90 @@
<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'
import { useData } from 'vitepress'
import { useAside } from '../composables/aside'
const { theme } = useData()
const carbonOptions = theme.value.carbonAds
const { isAsideEnabled } = useAside()
const container = ref()
let hasInitalized = false
function init() {
if (!hasInitalized) {
hasInitalized = true
const s = document.createElement('script')
s.id = '_carbonads_js'
s.src = `//cdn.carbonads.com/carbon.js?serve=${carbonOptions.code}&placement=${carbonOptions.placement}`
s.async = true
container.value.appendChild(s)
}
}
// no need to account for option changes during dev, we can just
// refresh the page
if (carbonOptions) {
onMounted(() => {
// if the page is loaded when aside is active, load carbon directly.
// otherwise, only load it if the page resizes to wide enough. this avoids
// loading carbon at all on mobile where it's never shown
if (isAsideEnabled.value) {
init()
} else {
watch(isAsideEnabled, (wide) => wide && init())
}
})
}
</script>
<template>
<div class="VPCarbonAds" ref="container" />
</template>
<style>
.VPCarbonAds {
display: flex;
justify-content: center;
align-items: center;
padding: 24px;
border-radius: 12px;
min-height: 240px;
text-align: center;
line-height: 18px;
font-size: 12px;
font-weight: 500;
background-color: var(--vp-c-bg-soft);
transition: color 0.5s, background-color 0.5s;
}
.VPCarbonAds img {
margin: 0 auto;
border-radius: 6px;
}
.VPCarbonAds .carbon-text {
display: block;
margin: 0 auto;
padding-top: 12px;
color: var(--vp-c-text-1);
transition: color 0.25s;
}
.VPCarbonAds .carbon-text:hover {
color: var(--vp-c-brand);
}
.VPCarbonAds .carbon-poweredby {
display: block;
padding-top: 6px;
font-size: 11px;
font-weight: 500;
color: var(--vp-c-text-2);
text-transform: uppercase;
transition: color 0.25s;
}
.VPCarbonAds .carbon-poweredby:hover {
color: var(--vp-c-text-1);
}
</style>

@ -0,0 +1,78 @@
<script setup lang="ts">
import { useRoute, useData } from 'vitepress'
import { useCopyCode } from '../composables/copy-code'
import { useSidebar } from '../composables/sidebar'
import NotFound from '../NotFound.vue'
import VPPage from './VPPage.vue'
import VPHome from './VPHome.vue'
import VPDoc from './VPDoc.vue'
const route = useRoute()
const { frontmatter } = useData()
const { hasSidebar } = useSidebar()
useCopyCode()
</script>
<template>
<div
class="VPContent"
id="VPContent"
:class="{
'has-sidebar': hasSidebar,
'is-home': frontmatter.layout === 'home'
}"
>
<NotFound v-if="route.component === NotFound" />
<VPPage v-else-if="frontmatter.layout === 'page'" />
<VPHome v-else-if="frontmatter.layout === 'home'">
<template #home-hero-before><slot name="home-hero-before" /></template>
<template #home-hero-after><slot name="home-hero-after" /></template>
<template #home-features-before><slot name="home-features-before" /></template>
<template #home-features-after><slot name="home-features-after" /></template>
</VPHome>
<VPDoc v-else>
<template #aside-top><slot name="aside-top" /></template>
<template #aside-outline-before><slot name="aside-outline-before" /></template>
<template #aside-outline-after><slot name="aside-outline-after" /></template>
<template #aside-ads-before><slot name="aside-ads-before" /></template>
<template #aside-ads-after><slot name="aside-ads-after" /></template>
<template #aside-bottom><slot name="aside-bottom" /></template>
</VPDoc>
</div>
</template>
<style scoped>
.VPContent {
flex-grow: 1;
flex-shrink: 0;
margin: 0 auto;
width: 100%;
}
.VPContent.is-home {
width: 100%;
max-width: 100%;
}
@media (min-width: 960px) {
.VPContent {
padding-top: var(--vp-nav-height);
}
.VPContent.has-sidebar {
margin: 0;
padding-left: var(--vp-sidebar-width);
}
}
@media (min-width: 1440px) {
.VPContent.has-sidebar {
padding-right: calc((100vw - var(--vp-layout-max-width)) / 2);
padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
}
}
</style>

@ -0,0 +1,191 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useData } from 'vitepress'
import { useSidebar } from '../composables/sidebar'
import VPDocAside from './VPDocAside.vue'
import VPDocFooter from './VPDocFooter.vue'
const { page } = useData()
const { hasSidebar } = useSidebar()
const pageName = computed(() => {
return page.value.relativePath.slice(0, page.value.relativePath.indexOf('/'))
})
</script>
<template>
<div class="VPDoc" :class="{ 'has-sidebar': hasSidebar }">
<div class="container">
<div class="aside">
<div class="aside-curtain" />
<div class="aside-container">
<div class="aside-content">
<VPDocAside>
<template #aside-top><slot name="aside-top" /></template>
<template #aside-bottom><slot name="aside-bottom" /></template>
<template #aside-outline-before><slot name="aside-outline-before" /></template>
<template #aside-outline-after><slot name="aside-outline-after" /></template>
<template #aside-ads-before><slot name="aside-ads-before" /></template>
<template #aside-ads-after><slot name="aside-ads-after" /></template>
</VPDocAside>
</div>
</div>
</div>
<div class="content">
<div class="content-container">
<main class="main">
<Content class="vp-doc" :class="pageName" />
</main>
<VPDocFooter />
</div>
</div>
</div>
</div>
</template>
<style scoped>
.VPDoc {
padding: 32px 24px 96px;
width: 100%;
}
@media (min-width: 768px) {
.VPDoc {
padding: 48px 32px 128px;
}
}
@media (min-width: 960px) {
.VPDoc {
padding: 32px 32px 0;
}
.VPDoc:not(.has-sidebar) .container {
display: flex;
justify-content: center;
max-width: 992px;
}
.VPDoc:not(.has-sidebar) .aside {
display: block;
}
.VPDoc:not(.has-sidebar) .content {
max-width: 752px;
}
}
@media (min-width: 1280px) {
.VPDoc .container {
display: flex;
justify-content: center;
}
.VPDoc .aside {
display: block;
}
}
@media (min-width: 1440px) {
.VPDoc:not(.has-sidebar) .content {
max-width: 784px;
}
.VPDoc:not(.has-sidebar) .container {
max-width: 1104px;
}
}
.container {
margin: 0 auto;
width: 100%;
}
.aside {
position: relative;
display: none;
order: 2;
flex-grow: 1;
padding-left: 32px;
width: 100%;
max-width: 256px;
}
.aside-container {
position: sticky;
top: 0;
margin-top: calc(var(--vp-nav-height-desktop) * -1 - 32px);
padding-top: calc(var(--vp-nav-height-desktop) + 32px);
height: 100vh;
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: none;
}
.aside-container::-webkit-scrollbar {
display: none;
}
.aside-curtain {
position: fixed;
bottom: 0;
z-index: 10;
width: 224px;
height: 32px;
background: linear-gradient(transparent, var(--vp-c-bg) 70%);
}
.aside-content {
display: flex;
flex-direction: column;
min-height: calc(100vh - (var(--vp-nav-height-desktop) + 32px));
padding-bottom: 32px;
}
.content {
position: relative;
margin: 0 auto;
width: 100%;
}
@media (min-width: 960px) {
.content {
padding: 0 32px 128px;
}
}
@media (min-width: 1280px) {
.content {
order: 1;
margin: 0;
min-width: 640px;
}
}
.content-container {
margin: 0 auto;
max-width: 688px;
}
.edit-link {
margin: 0 0 32px;
}
.edit-link .vt-link {
font-size: 14px;
color: var(--vt-c-brand);
font-weight: 500;
}
.vt-icon {
width: 18px;
height: 18px;
color: var(--vt-c-brand);
display: inline-block;
margin-right: 8px;
position: relative;
top: -1px;
}
</style>

@ -0,0 +1,46 @@
<script setup lang="ts">
import { useData } from 'vitepress'
import VPDocAsideOutline from './VPDocAsideOutline.vue'
import VPDocAsideCarbonAds from './VPDocAsideCarbonAds.vue'
const { page, theme } = useData()
</script>
<template>
<div class="VPDocAside">
<slot name="aside-top" />
<slot name="aside-outline-before" />
<VPDocAsideOutline v-if="page.headers.length" />
<slot name="aside-outline-after" />
<div class="spacer" />
<slot name="aside-ads-before" />
<VPDocAsideCarbonAds v-if="theme.carbonAds" />
<slot name="aside-ads-after" />
<slot name="aside-bottom" />
</div>
</template>
<style scoped>
.VPDocAside {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.spacer {
flex-grow: 1;
}
.VPDocAside :deep(.spacer + .VPDocAsideSponsors),
.VPDocAside :deep(.spacer + .VPDocAsideCarbonAds) {
margin-top: 24px;
}
.VPDocAside :deep(.VPDocAsideSponsors + .VPDocAsideCarbonAds) {
margin-top: 16px;
}
</style>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save