Merge branch 'main' into zh

pull/1593/head
Xavi Lee 4 years ago
commit 80c588e24a

@ -1,3 +1,3 @@
github: [yyx990803, kiaking, posva, pikax]
github: [yyx990803, kiaking, brc-dd, posva]
open_collective: vuejs
patreon: evanyou

@ -1,3 +1,39 @@
# [1.0.0-alpha.48](https://github.com/vuejs/vitepress/compare/v1.0.0-alpha.47...v1.0.0-alpha.48) (2023-02-26)
### Bug Fixes
* **compat:** remove use of array.at ([fd99590](https://github.com/vuejs/vitepress/commit/fd995906f61e5181ca8e1116dcd93eec65075056))
* **theme:** add height constraints to hero image ([#1983](https://github.com/vuejs/vitepress/issues/1983)) ([803d5b6](https://github.com/vuejs/vitepress/commit/803d5b6d663b5293c70672ca5526a33f454e4a17))
* **theme:** allow empty details in home feature ([#1936](https://github.com/vuejs/vitepress/issues/1936)) ([#1963](https://github.com/vuejs/vitepress/issues/1963)) ([b56351c](https://github.com/vuejs/vitepress/commit/b56351c7785b7a3a3413dcf24d7ac63c1f40fd2b))
* **theme:** show external link icon in navbar ([#1881](https://github.com/vuejs/vitepress/issues/1881)) ([8e6e8d9](https://github.com/vuejs/vitepress/commit/8e6e8d9af534e124cb16552686b460e19d0f894f)), closes [#1948](https://github.com/vuejs/vitepress/issues/1948)
* **theme:** show external link icon on same line ([#1880](https://github.com/vuejs/vitepress/issues/1880)) ([6218b10](https://github.com/vuejs/vitepress/commit/6218b108bc78aed0ec1afd3d1cf4182e611eed90))
### Features
* **build:** add support for custom languages ([#1837](https://github.com/vuejs/vitepress/issues/1837)) ([5a6d384](https://github.com/vuejs/vitepress/commit/5a6d3849527ee1dfd9f4299f5350cfa7641effb7))
* **theme:** make prev/next links changeable ([#1972](https://github.com/vuejs/vitepress/issues/1972)) ([b8a5e8e](https://github.com/vuejs/vitepress/commit/b8a5e8e5b24b025c9a5e4850b72296f726ae71e5))
* **theme:** support custom target and rel in navbar links ([#1993](https://github.com/vuejs/vitepress/issues/1993)) ([e2d4edf](https://github.com/vuejs/vitepress/commit/e2d4edf45b5ec890c088d3b0517b21a7b3eab9df))
# [1.0.0-alpha.47](https://github.com/vuejs/vitepress/compare/v1.0.0-alpha.46...v1.0.0-alpha.47) (2023-02-20)
### Bug Fixes
* **build:** show error stack in logs ([#1960](https://github.com/vuejs/vitepress/issues/1960)) ([c4d8d72](https://github.com/vuejs/vitepress/commit/c4d8d7225c2d8dd75f1640730e8d1425097e3aa3))
* custom titles of code snippets inside code groups ([#1834](https://github.com/vuejs/vitepress/issues/1834)) ([bcb8cbf](https://github.com/vuejs/vitepress/commit/bcb8cbf3c839dc17c1eaee7e39edb3ecca236a27))
* **types:** augment vite user config ([#1946](https://github.com/vuejs/vitepress/issues/1946)) ([5c9b75e](https://github.com/vuejs/vitepress/commit/5c9b75e325c27f63373c969e16035a9df5292cc9))
### Reverts
* "docs: add linkage for `code-groups` in `getting-started`" ([#1943](https://github.com/vuejs/vitepress/issues/1943)) ([ed90724](https://github.com/vuejs/vitepress/commit/ed90724022359358de582a3c00e86f381d57eeba)), closes [#1906](https://github.com/vuejs/vitepress/issues/1906)
# [1.0.0-alpha.46](https://github.com/vuejs/vitepress/compare/v1.0.0-alpha.45...v1.0.0-alpha.46) (2023-02-12)

@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`render correct content > main content 1`] = `
[

@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`static data file support in vite 3 > render correct content 1`] = `
[

@ -15,7 +15,8 @@ export default defineConfig({
sidebar: {
'/guide/': sidebarGuide(),
'/config/': sidebarConfig()
'/config/': sidebarConfig(),
'/api/': sidebarGuide()
},
editLink: {
@ -37,7 +38,16 @@ export default defineConfig({
function nav() {
return [
{ text: 'Guide', link: '/guide/what-is-vitepress', activeMatch: '/guide/' },
{ text: 'Configs', link: '/config/introduction', activeMatch: '/config/' },
{
text: 'Config Reference',
link: '/config/introduction',
activeMatch: '/config/'
},
{
text: 'Runtime API',
link: '/api/',
activeMatch: '/api/'
},
{
text: pkg.version,
items: [
@ -58,49 +68,46 @@ function sidebarGuide() {
return [
{
text: 'Introduction',
collapsible: true,
collapsed: false,
items: [
{ text: 'What is VitePress?', link: '/guide/what-is-vitepress' },
{ text: 'Getting Started', link: '/guide/getting-started' },
{ text: 'Configuration', link: '/guide/configuration' },
{ text: 'Routing', link: '/guide/routing' },
{ text: 'Deploying', link: '/guide/deploying' },
{ text: 'Internationalization', link: '/guide/i18n' }
{ text: 'Deploying', link: '/guide/deploying' }
]
},
{
text: 'Writing',
collapsible: true,
collapsed: false,
items: [
{ text: 'Markdown', link: '/guide/markdown' },
{ text: 'Markdown Extensions', link: '/guide/markdown' },
{ text: 'Asset Handling', link: '/guide/asset-handling' },
{ text: 'Frontmatter', link: '/guide/frontmatter' },
{ text: 'Using Vue in Markdown', link: '/guide/using-vue' },
{ text: 'API Reference', link: '/guide/api' }
{ text: 'Internationalization', link: '/guide/i18n' }
]
},
{
text: 'Theme',
collapsible: true,
text: 'Customization',
collapsed: false,
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: 'Home Page', link: '/guide/theme-home-page' },
{ text: 'Team Page', link: '/guide/theme-team-page' },
{ text: 'Badge', link: '/guide/theme-badge' },
{ text: 'Footer', link: '/guide/theme-footer' },
{ text: 'Search', link: '/guide/theme-search' },
{ text: 'Carbon Ads', link: '/guide/theme-carbon-ads' }
{ text: 'Default Theme', link: '/guide/default-theme' },
{
text: 'Extending the Default Theme',
link: '/guide/customization-extending-default-theme'
},
{ text: 'Building a Custom Theme', link: '/guide/customization-intro' },
{
text: 'Runtime API',
link: '/api/'
},
{ text: 'Build-Time Data Loading', link: '/guide/data-loading' }
]
},
{
text: 'Migrations',
collapsible: true,
collapsed: false,
items: [
{
text: 'Migration from VuePress',
@ -118,12 +125,12 @@ function sidebarGuide() {
function sidebarConfig() {
return [
{
text: 'Config',
text: 'Config Reference',
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' }
{ text: 'App Config', link: '/config/app-config' },
{ text: 'Default Theme Config', link: '/config/theme-config' },
{ text: 'Frontmatter Config', link: '/config/frontmatter-config' }
]
}
]

@ -16,8 +16,9 @@ export default defineConfig({
lastUpdatedText: '最后更新于',
sidebar: {
'/zh/guide/': sidebarGuide(),
'/zh/config/': sidebarConfig()
'/guide/': sidebarGuide(),
'/config/': sidebarConfig(),
'/api/': sidebarGuide()
},
editLink: {
@ -44,7 +45,12 @@ function nav() {
activeMatch: '/zh/guide/'
},
{
text: '配置',
text: '运行时 API',
link: '/api/',
activeMatch: '/api/'
},
{
text: '配置参考',
link: '/zh/config/introduction',
activeMatch: '/zh/config/'
},
@ -68,7 +74,7 @@ function sidebarGuide() {
return [
{
text: '简介',
collapsible: true,
collapsed: true,
items: [
{ text: 'VitePress 是什么', link: '/zh/guide/what-is-vitepress' },
{ text: '快速开始', link: '/zh/guide/getting-started' },
@ -80,9 +86,9 @@ function sidebarGuide() {
},
{
text: '写作',
collapsible: true,
collapsed: true,
items: [
{ text: 'Markdown', link: '/zh/guide/markdown' },
{ text: 'Markdown 扩展', link: '/zh/guide/markdown' },
{ text: '资源处理', link: '/zh/guide/asset-handling' },
{ text: 'Frontmatter', link: '/zh/guide/frontmatter' },
{ text: '在 Markdown 中使用 Vue', link: '/zh/guide/using-vue' },
@ -91,7 +97,7 @@ function sidebarGuide() {
},
{
text: '主题',
collapsible: true,
collapsed: true,
items: [
{ text: '简介', link: '/zh/guide/theme-introduction' },
{ text: '导航', link: '/zh/guide/theme-nav' },
@ -110,7 +116,7 @@ function sidebarGuide() {
},
{
text: '迁移',
collapsible: true,
collapsed: true,
items: [
{
text: '从 VuePress 迁移',
@ -121,6 +127,23 @@ function sidebarGuide() {
link: '/zh/guide/migration-from-vitepress-0'
}
]
},
{
text: '自定义',
collapsed: false,
items: [
{ text: '默认主题', link: '/guide/default-theme' },
{
text: '扩展默认主题',
link: '/guide/customization-extending-default-theme'
},
{ text: '构建自定义主题', link: '/guide/customization-intro' },
{
text: '运行时 API',
link: '/api/'
},
{ text: 'Build-Time Data Loading', link: '/guide/data-loading' }
]
}
]
}
@ -128,12 +151,12 @@ function sidebarGuide() {
function sidebarConfig() {
return [
{
text: '配置',
text: '配置参考',
items: [
{ text: '简介', link: '/zh/config/introduction' },
{ text: '应用全局配置', link: '/zh/config/app-configs' },
{ text: '主题配置', link: '/zh/config/theme-configs' },
{ text: 'Frontmatter 配置', link: '/zh/config/frontmatter-configs' }
{ text: '应用全局配置', link: '/zh/config/app-config' },
{ text: '主题配置', link: '/zh/config/theme-config' },
{ text: 'Frontmatter 配置', link: '/zh/config/frontmatter-config' }
]
}
]

@ -1,4 +1,4 @@
# API Reference
# Runtime API Reference
VitePress offers several built-in APIs to let you access app data. VitePress also comes with a few built-in components that can be used globally.
@ -66,11 +66,11 @@ interface Router {
- **Type**: `(path: string) => string`
Appends the configured [`base`](../config/app-configs#base) to a given URL path. Also see [Base URL](./asset-handling#base-url).
Appends the configured [`base`](/config/app-config#base) to a given URL path. Also see [Base URL](/guide/asset-handling#base-url).
## `<Content />`
The `<Content />` component displays the rendered markdown contents. Useful [when creating your own theme](./theme-introduction).
The `<Content />` component displays the rendered markdown contents. Useful [when creating your own theme](/guide/customization-intro).
```vue
<template>

@ -1,10 +1,10 @@
# App Configs
# App Config
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.
App config is where you can define the global settings of the site. App config options define settings that apply to every VitePress site, regardless of what theme it is using. For example, the base directory or the title of the site.
```ts
export default {
// These are app level configs.
// app level config options
lang: 'en-US',
title: 'VitePress',
description: 'Vite & Vue powered static site generator.',
@ -115,7 +115,7 @@ export default {
- 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.
Use git commit to get the timestamp. This option enables the default theme to display the page's last updated time. You can customize the text via [`themeConfig.lastUpdatedText`](theme-config#lastupdatedtext) option.
```ts
export default {
@ -154,6 +154,10 @@ interface MarkdownOptions extends MarkdownIt.Options {
// Enable line numbers in code block.
lineNumbers?: boolean
// Add support for your own languages.
// https://github.com/shikijs/shiki/blob/main/docs/languages.md#supporting-your-own-languages-with-shiki
languages?: Shiki.ILanguageRegistration
// markdown-it-anchor plugin options.
// See: https://github.com/valeriangalliat/markdown-it-anchor#usage
anchor?: anchorPlugin.AnchorOptions

@ -1,6 +1,6 @@
# Frontmatter Configs
# Frontmatter Config
Frontmatter enables page based configuration. On every markdown, you're free to add Any settings to override any global app or theme configs. Also, there are configs which you can only define in Frontmatter.
Frontmatter enables page based configuration. In every markdown file, you can use frontmatter config to override app-level or theme config options. Also, there are config options which you can only define in frontmatter.
```yaml
---
@ -9,7 +9,7 @@ editLink: true
---
```
You may access frontmatter by `$frontmatter` helper inside any markdown file.
You can access frontmatter by `$frontmatter` helper inside any markdown file.
```md
{{ $frontmatter.title }}
@ -19,7 +19,7 @@ You may access frontmatter by `$frontmatter` helper inside any markdown file.
- Type: `string`
Title for the page. It's same as [config.title](../config/app-configs#title), and it overrides the app config.
Title for the page. It's same as [config.title](../config/app-config#title), and it overrides the app config.
```yaml
---
@ -31,7 +31,7 @@ title: VitePress
- Type: `string | boolean`
The suffix for the title. It's same as [config.titleTemplate](../config/app-configs#titletemplate), and it overrides the app config.
The suffix for the title. It's same as [config.titleTemplate](../config/app-config#titletemplate), and it overrides the app config.
```yaml
---
@ -44,7 +44,7 @@ titleTemplate: Vite & Vue powered static site generator
- Type: `string`
Description for the page. It's same as [config.description](../config/app-configs#description), and it overrides the app config.
Description for the page. It's same as [config.description](../config/app-config#description), and it overrides the app config.
```yaml
---
@ -189,4 +189,4 @@ aside: false
- Type: `number | [number, number] | 'deep' | false`
- Default: `2`
The levels of header in the outline to display for the page. It's same as [config.themeConfig.outline](../config/theme-configs#outline), and it overrides the theme config.
The levels of header in the outline to display for the page. It's same as [config.themeConfig.outline](../config/theme-config#outline), and it overrides the theme config.

@ -15,11 +15,11 @@ Place your configuration file at `.vitepress/config.js`. This is where all ViteP
You can also use any of `.ts`, `.cjs`, `.mjs`, `.cts`, `.mts` as the config file extension.
:::
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.
VitePress comes with 2 types of configuration. One is the [App Config](./app-config) which configures the site's fundamental features such as setting title of the site, or customize how markdown parser works. Second is the [Theme Config](./theme-config) which configures the theme of the site, for example, adding a sidebar, or add features such as "Edit this page on GitHub" link.
There's also another configuration you may do in [Frontmatter](./frontmatter-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.
There's also another configuration you may do in [Frontmatter](./frontmatter-config). Frontmatter config can override global config defined in App Config or Theme Config for that specific page. However, there're several options that are only available at frontmatter as well.
Please refer to the corresponding configs page to learn more.
Please refer to the corresponding config page to learn more.
## Config Intellisense

@ -1,6 +1,6 @@
# Theme Configs
# Default Theme Config
Theme configs let you customize your theme. You can define theme configs by adding `themeConfig` key to the config file.
Theme config lets you customize your theme. You can define theme config via the `themeConfig` option in the config file:
```ts
export default {
@ -17,7 +17,7 @@ export default {
}
```
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.
**The options documented on this page only apply to the default theme.** Different themes expect different theme config. When using a custom theme, the theme config object will be passed to the theme so the theme can define conditional behavior based on it.
## i18nRouting
@ -87,15 +87,22 @@ export default {
```ts
type NavItem = NavItemWithLink | NavItemWithChildren
type NavItemWithLink = {
interface NavItemWithLink {
text: string
link: string
activeMatch?: string
target?: string
rel?: string
}
interface NavItemWithChildren {
interface NavItemChildren {
text?: string
items: NavItemWithLink[]
}
interface NavItemWithChildren {
text?: string
items: (NavItemChildren | NavItemWithLink)[]
activeMatch?: string
}
```

@ -45,7 +45,7 @@ However, if you are authoring a theme component that links to assets dynamically
<img :src="theme.logoPath" />
```
In this case it is recommended to wrap the path with the [`withBase` helper](./api#withbase) provided by VitePress:
In this case it is recommended to wrap the path with the [`withBase` helper](/api/#withbase) provided by VitePress:
```vue
<script setup>

@ -24,4 +24,4 @@ In the above example, the site will have the title of `VitePress`, and `Just pla
Learn everything about VitePress features at [Theme: Introduction](./theme-introduction) to find how to configure specific features within this config file.
You may also find all configuration references at [Configs](../config/introduction).
You may also find all configuration references at [Config Reference](../config/introduction).

@ -0,0 +1,49 @@
## Basic Usage
```js
export default {
load() {
return {
data: 'hello'
}
}
}
```
```js
export default {
async load() {
return (await fetch('...')).json()
}
}
```
## Generating Data Based On Local Files
```js
import { readDirSync } from 'node:fs'
export default {
watch: ['*.md'],
async load() {
//
}
}
```
## Typed Data
```ts
export interface Data {
// data type
}
declare const data: Data
export { data }
export default {
async load(): Promise<Data> {
// ...
}
}
```

@ -17,7 +17,7 @@ The following guides are based on some shared assumptions:
::: tip
If your site is to be served at a subdirectory (`https://example.com/subdir/`), then you have to set `'/subdir/'` as the [`base`](../config/app-configs#base) in your `docs/.vitepress/config.js`.
If your site is to be served at a subdirectory (`https://example.com/subdir/`), then you have to set `'/subdir/'` as the [`base`](../config/app-config#base) in your `docs/.vitepress/config.js`.
**Example:** If you're using Github (or GitLab) Pages and deploying to `user.github.io/repo/`, then set your `base` to `/repo/`.
@ -28,13 +28,13 @@ If your site is to be served at a subdirectory (`https://example.com/subdir/`),
- You may run this command to build the docs:
```sh
$ yarn docs:build
$ npm run docs:build
```
- Once you've built the docs, you can test them locally by running:
```sh
$ yarn docs:preview
$ npm run docs:preview
```
The `preview` command will boot up a local static web server that will serve the files from `.vitepress/dist` at `http://localhost:4173`. It's an easy way to check if the production build looks fine in your local environment.
@ -55,7 +55,7 @@ If your site is to be served at a subdirectory (`https://example.com/subdir/`),
Set up a new project and change these settings using your dashboard:
- **Build Command:** `yarn docs:build`
- **Build Command:** `npm run docs:build`
- **Output Directory:** `docs/.vitepress/dist`
- **Node Version:** `14` (or above, by default it usually will be 14 or 16, but on Cloudflare Pages the default is still 12, so you may need to [change that](https://developers.cloudflare.com/pages/platform/build-configuration/))
@ -82,8 +82,8 @@ Don't enable options like _Auto Minify_ for HTML code. It will remove comments f
deploy:
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
@ -94,10 +94,10 @@ Don't enable options like _Auto Minify_ for HTML code. It will remove comments f
- uses: actions/setup-node@v3
with:
node-version: 16
cache: yarn
- run: yarn install --frozen-lockfile
cache: npm
- run: npm install --frozen-lockfile
- name: Build
run: yarn docs:build
run: npm run docs:build
- uses: actions/configure-pages@v2
- uses: actions/upload-pages-artifact@v1
with:
@ -111,11 +111,13 @@ Don't enable options like _Auto Minify_ for HTML code. It will remove comments f
Please replace the corresponding branch name. For example, if the branch you want to build is `master`, then you should replace `main` with `master` in the above file.
:::
3. Now commit your code and push it to the `main` branch.
3. In your repository's Settings under Pages menu item, select `GitHub Actions` in Build and deployment's Source.
4. Wait for actions to complete.
4. Now commit your code and push it to the `main` branch.
5. In your repository's Settings under Pages menu item, select `gh-pages` branch as GitHub Pages source. Now your docs will automatically deploy each time you push.
5. Wait for actions to complete.
6. In your repository's Settings under Pages menu item, click `Visit site`, then you can see your site. Your docs will automatically deploy each time you push.
## GitLab Pages
@ -134,8 +136,8 @@ Don't enable options like _Auto Minify_ for HTML code. It will remove comments f
paths:
- node_modules/
script:
- yarn install
- yarn docs:build
- npm install
- npm run docs:build
artifacts:
paths:
- public
@ -153,8 +155,8 @@ Don't enable options like _Auto Minify_ for HTML code. It will remove comments f
before_script:
- apk add git
script:
- yarn install
- yarn docs:build
- npm install
- npm run docs:build
artifacts:
paths:
- public
@ -170,7 +172,7 @@ Don't enable options like _Auto Minify_ for HTML code. It will remove comments f
- **`app_location`**: `/`
- **`output_location`**: `docs/.vitepress/dist`
- **`app_build_command`**: `yarn docs:build`
- **`app_build_command`**: `npm run docs:build`
## Firebase
@ -197,7 +199,7 @@ Don't enable options like _Auto Minify_ for HTML code. It will remove comments f
}
```
2. After running `yarn docs:build`, run this command to deploy:
2. After running `npm run docs:build`, run this command to deploy:
```sh
firebase deploy
@ -205,7 +207,7 @@ Don't enable options like _Auto Minify_ for HTML code. It will remove comments f
## Surge
1. After running `yarn docs:build`, run this command to deploy:
1. After running `npm run docs:build`, run this command to deploy:
```sh
npx surge docs/.vitepress/dist

@ -9,7 +9,7 @@ editLink: true
---
```
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.
Between the triple-dashed lines, you can set [predefined variables](../config/frontmatter-config), or even create custom ones of your own. These variables can be used via the special <code>$frontmatter</code> variable.
Here's an example of how you could use it in your Markdown file:

@ -215,7 +215,7 @@ Wraps in a <div class="vp-raw">
- Install required deps with your preferred package manager:
```sh
$ yarn add -D postcss postcss-prefix-selector
$ npm install -D postcss postcss-prefix-selector
```
- Create a file named `docs/.postcssrc.cjs` and add this to it:
@ -281,7 +281,7 @@ export default {
A [list of valid languages](https://github.com/shikijs/shiki/blob/main/docs/languages.md) is available on Shiki's repository.
You may also customize syntax highlight theme in app config. Please see [`markdown` options](../config/app-configs#markdown) for more details.
You may also customize syntax highlight theme in app config. Please see [`markdown` options](../config/app-config#markdown) for more details.
## Line Highlighting in Code Blocks
@ -493,7 +493,7 @@ export default {
}
```
Please see [`markdown` options](../config/app-configs#markdown) for more details.
Please see [`markdown` options](../config/app-config#markdown) for more details.
You can add `:line-numbers` / `:no-line-numbers` mark in your fenced code blocks to override the value set in config.
@ -748,4 +748,4 @@ module.exports = {
}
```
See full list of configurable properties in [Configs: App Configs](../config/app-configs#markdown).
See full list of configurable properties in [Config Reference: App Config](../config/app-config#markdown).

@ -20,4 +20,4 @@ If you're coming from VitePress 0.x version, there're several breaking changes d
## Frontmatter Config
- `home: true` option has changed to `layout: home`. Also, many Homepage related settings have been modified to provide additional features. See [Home Page guide](./theme-home-page) for details.
- `footer` option is moved to [`themeConfig.footer`](../config/theme-configs#footer).
- `footer` option is moved to [`themeConfig.footer`](../config/theme-config#footer).

@ -49,7 +49,7 @@ docs/index.md -> /
docs/getting-started.md -> /getting-started.html
```
You may also customize the root directory in config file via [`srcDir`](/config/app-configs#srcdir) option too. Running `vitepress dev` with the following setting acts same as running `vitepress dev docs` command.
You may also customize the root directory in config file via [`srcDir`](/config/app-config#srcdir) option too. Running `vitepress dev` with the following setting acts same as running `vitepress dev docs` command.
```ts
export default {
@ -88,7 +88,7 @@ By default, VitePress generates the final static page files by adding `.html` ex
└─ index.md
```
However, you may also generate a clean URL by setting up [`cleanUrls`](/config/app-configs#cleanurls) option.
However, you may also generate a clean URL by setting up [`cleanUrls`](/config/app-config#cleanurls) option.
```ts
export default {
@ -120,7 +120,7 @@ packages/pkg-a/src/pkg-a-code.md -> /pkg-a/pkg-a-code.md
packages/pkg-b/src/pkg-b-code.md -> /pkg-b/pkg-b-code.md
```
You may configure the mapping via [`rewrites`](/config/app-configs#rewrites) option like this.
You may configure the mapping via [`rewrites`](/config/app-config#rewrites) option like this.
```ts
export default {

@ -4,7 +4,7 @@ The Nav is the navigation bar displayed on top of the page. It contains the site
## Site Title and Logo
By default, nav shows the title of the site referencing [`config.title`](../config/app-configs#title) value. If you would like to change what's displayed on nav, you may define custom text in `themeConfig.siteTitle` option.
By default, nav shows the title of the site referencing [`config.title`](../config/app-config#title) value. If you would like to change what's displayed on nav, you may define custom text in `themeConfig.siteTitle` option.
```js
export default {
@ -35,7 +35,7 @@ export default {
}
```
You can also pass an object as logo if you want to add `alt` attribute or customize it based on dark/light mode. Refer [`themeConfig.logo`](../config/theme-configs#logo) for details.
You can also pass an object as logo if you want to add `alt` attribute or customize it based on dark/light mode. Refer [`themeConfig.logo`](../config/theme-config#logo) for details.
## Navigation Links
@ -46,7 +46,7 @@ export default {
themeConfig: {
nav: [
{ text: 'Guide', link: '/guide' },
{ text: 'Configs', link: '/configs' },
{ text: 'Config', link: '/config' },
{ text: 'Changelog', link: 'https://github.com/...' }
]
}
@ -138,6 +138,25 @@ export default {
`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.
:::
### Customize link's "target" and "rel" attributes
By default, VitePress automatically determines `target` and `rel` attributes based on whether the link is an external link. But if you want, you can customize them too.
```js
export default {
themeConfig: {
nav: [
{
text: 'Merchandise',
link: 'https://www.thegithubshop.com/',
target: '_self',
rel: 'sponsored'
}
]
}
}
```
## Social Links
Refer [`socialLinks`](../config/theme-configs#sociallinks).
Refer [`socialLinks`](../config/theme-config#sociallinks).

@ -1,29 +1,43 @@
# Prev Next Link
You can customize the text of previous and next links. This is helpful if you want to show different text on previous/next links than what you have on your sidebar.
You can customize the text and link for the previous and next pages (shown at doc footer). This is helpful if you want a different text there than what you have on your sidebar. Additionally, you may find it useful to disable the footer or link to a page that is not included in your sidebar.
## prev
- Type: `string`
- Type: `string | false | { text?: string; link?: string }`
- Details:
Specify the text to show on the link to the previous page.
Specifies the text/link to show on the link to the previous page. If you don't set this in frontmatter, the text/link will be inferred from the sidebar config.
If you don't set this in frontmatter, the text will be inferred from the sidebar config.
- Examples:
- Example:
- To customize only the text:
```yaml
---
prev: 'Get Started | Markdown'
---
```
```yaml
---
prev: 'Get Started | Markdown'
---
```
## next
- To customize both text and link:
- Type: `string`
```yaml
---
prev:
text: 'Markdown'
link: '/guide/markdown'
---
```
- Details:
- To hide previous page:
```yaml
---
prev: false
---
```
## next
Same as `prev` but for the next page.
Same as `prev` but for the next page.

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

@ -207,7 +207,7 @@ interface TeamMember {
// Social links. e.g. GitHub, Twitter, etc. You may pass in
// the Social Links object here.
// See: https://vitepress.vuejs.org/config/theme-configs.html#sociallinks
// See: https://vitepress.vuejs.org/config/theme-config.html#sociallinks
links?: SocialLink[]
// URL for the sponsor page for the member.

@ -36,7 +36,7 @@ Directives also work:
### Access to Site & Page Data
You can use the [`useData` helper](./api#usedata) in a `<script>` block and expose the data to the page.
You can use the [`useData` helper](/api/#usedata) in a `<script>` block and expose the data to the page.
**Input**
@ -104,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](./theme-introduction) 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 [Customization Guide](./customization-intro) 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.
@ -198,7 +198,7 @@ export default {
## Built-In Components
VitePress provides Built-In Vue Components like `ClientOnly`, check out the [Global Component Guide](./api) for more information.
VitePress provides Built-In Vue Components like `ClientOnly`, check out the [Global Component Guide](/api/) for more information.
**Also see:**
@ -263,7 +263,7 @@ export default {
## Using Teleports
Vitepress currently has SSG support for teleports to body only. For other targets, you can wrap them inside the built-in `<ClientOnly>` component or inject the teleport markup into the correct location in your final page HTML through [`postRender` hook](../config/app-configs#postrender).
Vitepress currently has SSG support for teleports to body only. For other targets, you can wrap them inside the built-in `<ClientOnly>` component or inject the teleport markup into the correct location in your final page HTML through [`postRender` hook](../config/app-config#postrender).
<ModalDemo />

@ -1,53 +1,45 @@
# What is VitePress?
VitePress is [VuePress](https://vuepress.vuejs.org/)' little brother, built on top of [Vite](https://vitejs.dev/).
VitePress is a [Static Site Generator](https://en.wikipedia.org/wiki/Static_site_generator) (SSG). It is designed for building performant content-centric websites, such as this documentation you are reading right now. It also powers the documentation for [Vue.js](https://vuejs.org/), [Vite](https://vitejs.dev/), and many more<!-- TODO: showcase page? -->. In a nutshell, VitePress takes your source content written in [Markdown](https://en.wikipedia.org/wiki/Markdown), applies a theme to it, and generates a directory of static HTML pages (and necessary asset files) that can be easily deployed anywhere.
::: 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.
:::
## Developer Experience
## Motivation
VitePress aims to provide a great Developer Experience (DX) when working with Markdown content.
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!
- **[Vite-Powered:](https://vitejs.dev/)** instant server start, with edits always instantly reflected (<100ms) without page reload.
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!
- **[Built-in Markdown Extensions:](/guide/markdown)** Frontmatter, tables, syntax highlighting... you name it. Specifically, VitePress provides many advanced features for working with code blocks, making it ideal for highly technical documentation.
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.
- **[Vue-Enhanced Markdown](/guide/using-vue):** each Markdown page is also a Vue [Single-File Component](https://vuejs.org/guide/scaling-up/sfc.html), thanks to Vue template's 100% syntax compatibility with HTML. You can embed interactivity in your static content using Vue templating features or imported Vue components.
Now, with Vite and Vue 3, it is time to rethink what a "Vue-powered static site generator" can really be.
## Performance
## Improvements over VuePress v1
Unlike many traditional SSGs, a website generated by VitePress is in fact a [Single Page Application](https://en.wikipedia.org/wiki/Single-page_application) (SPA).
There're couple of things that are improved from VuePress v1....
- **Fast Initial Load**
### It uses Vue 3
The initial visit to any page will be served the static, pre-rendered HTML for maximum loading speed, together with a JavaScript bundle that turns the page into a Vue SPA ("hydration"). The hydration process is extremely fast: on [PageSpeed Insights](https://pagespeed.web.dev/), typical VitePress sites achieve near-perfect performance scores even on low-end mobile devices with a slow network.
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.
- **Fast Post-load Navigation**
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.
More importantly, the SPA model leads to better user experience **after** the initial load. Subsequent navigation within the site will no longer cause a full page reload. Instead, the incoming page's content will be fetched and dynamically updated. VitePress also automatically pre-fetches page chunks for links that are within viewport. In most cases, post-load navigation will feel instant.
### It uses Vite under the hood
- **Interactivity Without Penalty**
- Faster dev server start
- Faster hot updates
- Faster build (uses Rollup internally)
To be able to hydrate the dynamic Vue parts embedded inside static Markdown, each Markdown page is processed as a Vue component and compiled into JavaScript. This may sound inefficient, but the Vue compiler is smart enough to separate the static and dynamic parts, minimizing both the hydration cost and payload size. For the initial page load, the static parts are automatically eliminated from the JavaScript payload and skipped during hydration.
### Lighter page weight
## Theming & Extensibility
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.
VitePress ships with a feature-rich default theme designed for documentation purposes. It allows you to spin up a beautiful documentation site like this one with minimal effort, and doesn't require any Vue-specific knowledge.
### Other differences
VitePress also supports fully customized themes with the developer experience of a standard Vite + Vue application. Being built on Vite also means you can directly leverage Vite plugins from its rich ecosystem. This makes VitePress an ideal choice for building sites that is content-centric but also requires non-trivial interactivity. The [Vue.js documentation](https://github.com/vuejs/docs) is a good example of such customization.
VitePress is more opinionated and less configurable: VitePress aims to scale back the complexity in the current VuePress and restart from its minimalist roots.
And of course, you can use it to build a blog! The [official Vue.js blog](https://github.com/vuejs/blog) is also built with VitePress.
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.
## What About VuePress?
## Will this become the next vuepress in the future?
VitePress is the spiritual successor of VuePress. The original VuePress was based on Vue 2 and webpack. With Vue 3 and Vite under the hood, VitePress provides significantly better DX, better production performance, a more polished default theme, and a more flexible customization API.
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.
The API difference between VitePress and VuePress mostly lies in theming and customization. If you are using VuePress 1 with the default theme, it should be relatively straightforward to migrate to VitePress.
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 discussion](https://github.com/vuejs/vitepress/discussions/548) about this topic. If you're curious, please leave your thoughts!
There has also been effort invested into VuePress 2, which also supports Vue 3 and Vite with more compatibility with VuePress 1. However, maintaining two SSGs in parallel isn't sustainable, so the Vue team has decided to focus on VitePress as the main recommended SSG in the long run.

@ -11,7 +11,7 @@ hero:
actions:
- theme: brand
text: Get Started
link: /guide/getting-started
link: /guide/what-is-vitepress
- theme: alt
text: View on GitHub
link: https://github.com/vuejs/vitepress

@ -1,9 +1,9 @@
{
"name": "vitepress",
"version": "1.0.0-alpha.46",
"version": "1.0.0-alpha.48",
"description": "Vite & Vue powered static site generator",
"type": "module",
"packageManager": "pnpm@7.27.0",
"packageManager": "pnpm@7.28.0",
"main": "dist/node/index.js",
"types": "types/index.d.ts",
"exports": {
@ -84,10 +84,10 @@
"@docsearch/js": "^3.3.3",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/devtools-api": "^6.5.0",
"@vueuse/core": "^9.12.0",
"@vueuse/core": "^9.13.0",
"body-scroll-lock": "4.0.0-beta.0",
"shiki": "^0.14.1",
"vite": "^4.1.1",
"vite": "^4.1.4",
"vue": "^3.2.47"
},
"devDependencies": {
@ -117,7 +117,7 @@
"@types/markdown-it-emoji": "^2.0.2",
"@types/micromatch": "^4.0.2",
"@types/minimist": "^1.2.2",
"@types/node": "^18.13.0",
"@types/node": "^18.14.1",
"@types/prompts": "^2.4.2",
"chokidar": "^3.5.3",
"compression": "^1.7.4",
@ -125,16 +125,16 @@
"cross-spawn": "^7.0.3",
"debug": "^4.3.4",
"enquirer": "^2.3.6",
"esbuild": "^0.17.7",
"esbuild": "^0.17.10",
"escape-html": "^1.0.3",
"execa": "^7.0.0",
"fast-glob": "^3.2.12",
"fs-extra": "^11.1.0",
"get-port": "^6.1.2",
"lint-staged": "^13.1.1",
"lru-cache": "^7.14.1",
"lint-staged": "^13.1.2",
"lru-cache": "^7.17.0",
"markdown-it": "^13.0.1",
"markdown-it-anchor": "^8.6.6",
"markdown-it-anchor": "^8.6.7",
"markdown-it-attrs": "^4.1.6",
"markdown-it-container": "^3.0.0",
"markdown-it-emoji": "^2.0.2",
@ -146,14 +146,14 @@
"path-to-regexp": "^6.2.1",
"picocolors": "^1.0.0",
"pkg-dir": "5.0.0",
"playwright-chromium": "^1.30.0",
"playwright-chromium": "^1.31.1",
"polka": "1.0.0-next.22",
"prettier": "^2.8.4",
"prompts": "^2.4.2",
"punycode": "^2.3.0",
"rimraf": "^4.1.2",
"rollup": "^3.15.0",
"rollup-plugin-dts": "^5.1.1",
"rollup": "^3.17.3",
"rollup-plugin-dts": "^5.2.0",
"rollup-plugin-esbuild": "^5.0.0",
"semver": "^7.3.8",
"shiki-processor": "^0.1.3",
@ -161,8 +161,8 @@
"sirv": "^2.0.2",
"supports-color": "^9.3.1",
"typescript": "~4.9.5",
"vitest": "^0.28.4",
"vue-tsc": "^1.0.24",
"vitest": "^0.29.1",
"vue-tsc": "^1.2.0",
"wait-on": "^7.0.1"
},
"pnpm": {

File diff suppressed because it is too large Load Diff

@ -39,15 +39,15 @@ const showFooter = computed(() => {
</div>
</div>
<div v-if="control.prev || control.next" class="prev-next">
<div v-if="control.prev?.link || control.next?.link" class="prev-next">
<div class="pager">
<a v-if="control.prev" class="pager-link prev" :href="normalizeLink(control.prev.link)">
<a v-if="control.prev?.link" class="pager-link prev" :href="normalizeLink(control.prev.link)">
<span class="desc" v-html="theme.docFooter?.prev || 'Previous page'"></span>
<span class="title" v-html="control.prev.text"></span>
</a>
</div>
<div class="pager" :class="{ 'has-prev': control.prev }">
<a v-if="control.next" class="pager-link next" :href="normalizeLink(control.next.link)">
<div class="pager" :class="{ 'has-prev': control.prev?.link }">
<a v-if="control.next?.link" class="pager-link next" :href="normalizeLink(control.next.link)">
<span class="desc" v-html="theme.docFooter?.next || 'Next page'"></span>
<span class="title" v-html="control.next.text"></span>
</a>

@ -7,7 +7,7 @@ import VPIconArrowRight from './icons/VPIconArrowRight.vue'
defineProps<{
icon?: DefaultTheme.FeatureIcon
title: string
details: string
details?: string
link?: string
linkText?: string
}>()
@ -25,7 +25,7 @@ defineProps<{
/>
<div v-else-if="icon" class="icon">{{ icon }}</div>
<h2 class="title" v-html="title"></h2>
<p class="details" v-html="details"></p>
<p v-if="details" class="details" v-html="details"></p>
<div v-if="linkText" class="link-text">
<p class="link-text-value">

@ -306,6 +306,7 @@ const heroImageSlotExists = inject('hero-image-slot-exists') as Ref<boolean>
/*rtl:ignore*/
left: 50%;
max-width: 192px;
max-height: 192px;
/*rtl:ignore*/
transform: translate(-50%, -50%);
}
@ -313,12 +314,14 @@ const heroImageSlotExists = inject('hero-image-slot-exists') as Ref<boolean>
@media (min-width: 640px) {
:deep(.image-src) {
max-width: 256px;
max-height: 256px;
}
}
@media (min-width: 960px) {
:deep(.image-src) {
max-width: 320px;
max-height: 320px;
}
}
</style>

@ -8,6 +8,8 @@ const props = defineProps<{
tag?: string
href?: string
noIcon?: boolean
target?: string
rel?: string
}>()
const tag = computed(() => props.tag ?? props.href ? 'a' : 'span')
@ -20,8 +22,8 @@ const isExternal = computed(() => props.href && EXTERNAL_URL_RE.test(props.href)
class="VPLink"
:class="{ link: href }"
:href="href ? normalizeLink(href) : undefined"
:target="isExternal ? '_blank' : undefined"
:rel="isExternal ? 'noreferrer' : undefined"
:target="target || (isExternal ? '_blank' : undefined)"
:rel="rel || (isExternal ? 'noreferrer' : undefined)"
>
<slot />
<VPIconExternalLink v-if="isExternal && !noIcon" class="icon" />
@ -37,5 +39,6 @@ const isExternal = computed(() => props.href && EXTERNAL_URL_RE.test(props.href)
height: 11px;
fill: var(--vp-c-text-3);
transition: fill 0.25s;
flex-shrink: 0;
}
</style>

@ -1,10 +1,11 @@
<script lang="ts" setup>
import type { DefaultTheme } from 'vitepress/theme'
import { useData } from '../composables/data.js'
import { isActive } from '../support/utils.js'
import VPLink from './VPLink.vue'
defineProps<{
item: any
item: DefaultTheme.NavItemWithLink
}>()
const { page } = useData()
@ -15,6 +16,8 @@ const { page } = useData()
<VPLink
:class="{ active: isActive(page.relativePath, item.activeMatch || item.link, !!item.activeMatch) }"
:href="item.link"
:target="item.target"
:rel="item.rel"
>
{{ item.text }}
</VPLink>

@ -22,7 +22,8 @@ const { page } = useData()
)
}"
:href="item.link"
:noIcon="true"
:target="item.target"
:rel="item.rel"
>
{{ item.text }}
</VPLink>

@ -113,7 +113,8 @@ function onCaretClick() {
}
.link {
display: block;
display: flex;
align-items: center;
flex-grow: 1;
}

@ -15,12 +15,39 @@ export function usePrevNext() {
})
return {
prev: frontmatter.value.prev
? { ...candidates[index - 1], text: frontmatter.value.prev }
: candidates[index - 1],
next: frontmatter.value.next
? { ...candidates[index + 1], text: frontmatter.value.next }
: candidates[index + 1]
prev:
frontmatter.value.prev === false
? undefined
: {
text:
(typeof frontmatter.value.prev === 'string'
? frontmatter.value.prev
: typeof frontmatter.value.prev === 'object'
? frontmatter.value.prev.text
: undefined) ?? candidates[index - 1]?.text,
link:
(typeof frontmatter.value.prev === 'object'
? frontmatter.value.prev.link
: undefined) ?? candidates[index - 1]?.link
},
next:
frontmatter.value.next === false
? undefined
: {
text:
(typeof frontmatter.value.next === 'string'
? frontmatter.value.next
: typeof frontmatter.value.next === 'object'
? frontmatter.value.next.text
: undefined) ?? candidates[index + 1]?.text,
link:
(typeof frontmatter.value.next === 'object'
? frontmatter.value.next.link
: undefined) ?? candidates[index + 1]?.link
}
} as {
prev?: { text?: string; link?: string }
next?: { text?: string; link?: string }
}
})
}

@ -58,7 +58,7 @@
}
.custom-block-title {
font-weight: 700;
font-weight: 600;
}
.custom-block p + p {

@ -310,7 +310,7 @@
position: relative;
z-index: 1;
margin: 0;
padding: 16px 0;
padding: 20px 0;
background: transparent;
overflow-x: auto;
}
@ -419,24 +419,25 @@
/*rtl:ignore*/
direction: ltr;
position: absolute;
top: 8px;
top: 12px;
/*rtl:ignore*/
right: 8px;
right: 12px;
z-index: 3;
display: block;
justify-content: center;
align-items: center;
border: 1px solid var(--vp-code-copy-code-border-color);
border-radius: 4px;
width: 40px;
height: 40px;
background-color: var(--vp-code-block-bg);
background-color: var(--vp-code-copy-code-bg);
opacity: 0;
cursor: pointer;
background-image: var(--vp-icon-copy);
background-position: 50%;
background-size: 20px;
background-repeat: no-repeat;
transition: opacity 0.4s;
transition: border-color 0.25s, background-color 0.25s, opacity 0.25s;
}
.vp-doc [class*='language-']:hover > button.copy,
@ -444,7 +445,9 @@
opacity: 1;
}
.vp-doc [class*='language-'] > button.copy:hover {
.vp-doc [class*='language-'] > button.copy:hover,
.vp-doc [class*='language-'] > button.copy.copied {
border-color: var(--vp-code-copy-code-hover-border-color);
background-color: var(--vp-code-copy-code-hover-bg);
}
@ -459,12 +462,15 @@
.vp-doc [class*='language-'] > button.copy.copied::before,
.vp-doc [class*='language-'] > button.copy:hover.copied::before {
position: relative;
top: -1px;
/*rtl:ignore*/
left: -65px;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--vp-code-copy-code-hover-border-color);
/*rtl:ignore*/
border-right: 0;
border-radius: 4px 0 0 4px;
width: 64px;
height: 40px;
@ -479,9 +485,9 @@
.vp-doc [class*='language-'] > span.lang {
position: absolute;
top: 6px;
top: 2px;
/*rtl:ignore*/
right: 12px;
right: 8px;
z-index: 2;
font-size: 12px;
font-weight: 500;

@ -29,11 +29,11 @@
--vp-c-green-dimm-2: rgba(16, 185, 129, 0.2);
--vp-c-green-dimm-3: rgba(16, 185, 129, 0.5);
--vp-c-yellow: #eab308;
--vp-c-yellow-light: #facc15;
--vp-c-yellow-lighter: #fde047;
--vp-c-yellow-dark: #ca8a04;
--vp-c-yellow-darker: #a16207;
--vp-c-yellow: #d97706;
--vp-c-yellow-light: #f59e0b;
--vp-c-yellow-lighter: #fbbf24;
--vp-c-yellow-dark: #b45309;
--vp-c-yellow-darker: #92400e;
--vp-c-yellow-dimm-1: rgba(234, 179, 8, 0.05);
--vp-c-yellow-dimm-2: rgba(234, 179, 8, 0.2);
--vp-c-yellow-dimm-3: rgba(234, 179, 8, 0.5);
@ -203,6 +203,7 @@
--vp-code-block-color: var(--vp-c-text-dark-1);
--vp-code-block-bg: #292b30;
--vp-code-block-bg-light: #1e1e20;
--vp-code-block-divider-color: #000000;
--vp-code-line-highlight-color: rgba(0, 0, 0, 0.5);
@ -217,7 +218,10 @@
--vp-code-line-warning-color: var(--vp-c-yellow-dimm-2);
--vp-code-line-error-color: var(--vp-c-red-dimm-2);
--vp-code-copy-code-hover-bg: rgba(255, 255, 255, 0.05);
--vp-code-copy-code-border-color: transparent;
--vp-code-copy-code-bg: var(--vp-code-block-bg-light);
--vp-code-copy-code-hover-border-color: var(--vp-c-divider);
--vp-code-copy-code-hover-bg: var(--vp-code-block-bg-light);
--vp-code-copy-code-active-text: var(--vp-c-text-dark-2);
--vp-code-tab-divider: var(--vp-code-block-divider-color);
@ -284,22 +288,22 @@
--vp-custom-block-info-border: var(--vp-c-border);
--vp-custom-block-info-text: var(--vp-c-text-2);
--vp-custom-block-info-bg: var(--vp-c-bg-soft);
--vp-custom-block-info-code-bg: var(--vp-c-mute);
--vp-custom-block-info-code-bg: var(--vp-c-bg-soft-down);
--vp-custom-block-tip-border: var(--vp-c-green-dimm-3);
--vp-custom-block-tip-border: var(--vp-c-green);
--vp-custom-block-tip-text: var(--vp-c-green);
--vp-custom-block-tip-bg: var(--vp-c-green-dimm-1);
--vp-custom-block-tip-code-bg: var(--vp-custom-block-tip-bg);
--vp-custom-block-tip-bg: var(--vp-c-bg-soft);
--vp-custom-block-tip-code-bg: var(--vp-c-bg-soft-down);
--vp-custom-block-warning-border: var(--vp-c-yellow-dimm-3);
--vp-custom-block-warning-border: var(--vp-c-yellow);
--vp-custom-block-warning-text: var(--vp-c-yellow);
--vp-custom-block-warning-bg: var(--vp-c-yellow-dimm-1);
--vp-custom-block-warning-code-bg: var(--vp-custom-block-warning-bg);
--vp-custom-block-warning-bg: var(--vp-c-bg-soft);
--vp-custom-block-warning-code-bg: var(--vp-c-bg-soft-down);
--vp-custom-block-danger-border: var(--vp-c-red-dimm-3);
--vp-custom-block-danger-border: var(--vp-c-red);
--vp-custom-block-danger-text: var(--vp-c-red);
--vp-custom-block-danger-bg: var(--vp-c-red-dimm-1);
--vp-custom-block-danger-code-bg: var(--vp-custom-block-danger-bg);
--vp-custom-block-danger-bg: var(--vp-c-bg-soft);
--vp-custom-block-danger-code-bg: var(--vp-c-bg-soft-down);
--vp-custom-block-details-border: var(--vp-custom-block-info-border);
--vp-custom-block-details-text: var(--vp-custom-block-info-text);

@ -64,23 +64,21 @@ export async function build(
// as JS object literal.
const hashMapString = JSON.stringify(JSON.stringify(pageToHashMap))
const pages = ['404.md', ...siteConfig.pages].map(
(page) => siteConfig.rewrites.map[page] || page
)
await Promise.all(
pages.map((page) =>
renderPage(
render,
siteConfig,
page,
clientResult,
appChunk,
cssChunk,
pageToHashMap,
hashMapString
['404.md', ...siteConfig.pages]
.map((page) => siteConfig.rewrites.map[page] || page)
.map((page) =>
renderPage(
render,
siteConfig,
page,
clientResult,
appChunk,
cssChunk,
pageToHashMap,
hashMapString
)
)
)
)
} catch (e) {
spinner.stopAndPersist({

@ -197,9 +197,13 @@ function resolvePageImports(
page = config.rewrites.inv[page] || page
// find the page's js chunk and inject script tags for its imports so that
// they start fetching as early as possible
const srcPath = normalizePath(
fs.realpathSync(path.resolve(config.srcDir, page))
)
let srcPath = page
try {
srcPath = normalizePath(fs.realpathSync(path.resolve(config.srcDir, page)))
} catch (e) {
// if the page is a virtual page generated by a dynamic route this would
// fail, which is expected
}
const pageChunk = result.output.find(
(chunk) => chunk.type === 'chunk' && chunk.facadeModuleId === srcPath
) as OutputChunk

@ -29,19 +29,23 @@ if (!command || command === 'dev') {
server.printUrls()
}
createDevServer().catch((err) => {
createLogger().error(c.red(`failed to start server. error:\n`), err)
createLogger().error(
`${c.red(`failed to start server. error:`)}\n${err.stack}`
)
process.exit(1)
})
} else {
logVersion()
if (command === 'build') {
build(root, argv).catch((err) => {
createLogger().error(c.red(`build error:\n`), err)
createLogger().error(`${c.red(`build error:`)}\n${err.stack}`)
process.exit(1)
})
} else if (command === 'serve' || command === 'preview') {
serve(argv).catch((err) => {
createLogger().error(c.red(`failed to start server. error:\n`), err)
createLogger().error(
`${c.red(`failed to start server. error:`)}\n${err.stack}`
)
process.exit(1)
})
} else {

@ -3,7 +3,6 @@ import _debug from 'debug'
import fg from 'fast-glob'
import fs from 'fs-extra'
import path from 'path'
import { compile, match } from 'path-to-regexp'
import c from 'picocolors'
import {
createLogger,
@ -15,6 +14,12 @@ import {
} from 'vite'
import { DEFAULT_THEME_PATH } from './alias'
import type { MarkdownOptions } from './markdown/markdown'
import {
dynamicRouteRE,
resolveDynamicRoutes,
type ResolvedRouteConfig
} from './plugins/dynamicRoutesPlugin'
import { resolveRewrites } from './plugins/rewritesPlugin'
import {
APPEARANCE_KEY,
type Awaitable,
@ -178,11 +183,16 @@ export interface SiteConfig<ThemeConfig = any>
cacheDir: string
tempDir: string
pages: string[]
dynamicRoutes: {
routes: ResolvedRouteConfig[]
fileToModulesMap: Record<string, Set<string>>
}
rewrites: {
map: Record<string, string | undefined>
inv: Record<string, string | undefined>
}
logger: Logger
userConfig: UserConfig
}
const resolve = (root: string, file: string) =>
@ -236,33 +246,10 @@ export async function resolveConfig(
? userThemeDir
: DEFAULT_THEME_PATH
// Important: fast-glob doesn't guarantee order of the returned files.
// We must sort the pages so the input list to rollup is stable across
// builds - otherwise different input order could result in different exports
// order in shared chunks which in turns invalidates the hash of every chunk!
// JavaScript built-in sort() is mandated to be stable as of ES2019 and
// supported in Node 12+, which is required by Vite.
const pages = (
await fg(['**.md'], {
cwd: srcDir,
ignore: ['**/node_modules', ...(userConfig.srcExclude || [])]
})
).sort()
const rewriteEntries = Object.entries(userConfig.rewrites || {})
const rewrites = rewriteEntries.length
? Object.fromEntries(
pages
.map((src) => {
for (const [from, to] of rewriteEntries) {
const dest = rewrite(src, from, to)
if (dest) return [src, dest]
}
})
.filter((e) => e != null) as [string, string][]
)
: {}
const { pages, dynamicRoutes, rewrites } = await resolvePages(
srcDir,
userConfig
)
const config: SiteConfig = {
root,
@ -270,6 +257,7 @@ export async function resolveConfig(
site,
themeDir,
pages,
dynamicRoutes,
configPath,
configDeps,
outDir,
@ -292,10 +280,8 @@ export async function resolveConfig(
transformHead: userConfig.transformHead,
transformHtml: userConfig.transformHtml,
transformPageData: userConfig.transformPageData,
rewrites: {
map: rewrites,
inv: Object.fromEntries(Object.entries(rewrites).map((a) => a.reverse()))
}
rewrites,
userConfig
}
return config
@ -431,10 +417,32 @@ function resolveSiteDataHead(userConfig?: UserConfig): HeadConfig[] {
return head
}
function rewrite(src: string, from: string, to: string) {
const urlMatch = match(from)
const res = urlMatch(src)
if (!res) return false
const toPath = compile(to)
return toPath(res.params)
export async function resolvePages(srcDir: string, userConfig: UserConfig) {
// Important: fast-glob doesn't guarantee order of the returned files.
// We must sort the pages so the input list to rollup is stable across
// builds - otherwise different input order could result in different exports
// order in shared chunks which in turns invalidates the hash of every chunk!
// JavaScript built-in sort() is mandated to be stable as of ES2019 and
// supported in Node 12+, which is required by Vite.
const allMarkdownFiles = (
await fg(['**.md'], {
cwd: srcDir,
ignore: ['**/node_modules', ...(userConfig.srcExclude || [])]
})
).sort()
const pages = allMarkdownFiles.filter((p) => !dynamicRouteRE.test(p))
const dynamicRouteFiles = allMarkdownFiles.filter((p) =>
dynamicRouteRE.test(p)
)
const dynamicRoutes = await resolveDynamicRoutes(dynamicRouteFiles)
pages.push(...dynamicRoutes.routes.map((r) => r.path))
const rewrites = resolveRewrites(pages, userConfig.rewrites)
return {
pages,
dynamicRoutes,
rewrites
}
}

@ -15,7 +15,7 @@ import MarkdownIt from 'markdown-it'
import anchorPlugin from 'markdown-it-anchor'
import attrsPlugin from 'markdown-it-attrs'
import emojiPlugin from 'markdown-it-emoji'
import type { IThemeRegistration } from 'shiki'
import type { ILanguageRegistration, IThemeRegistration } from 'shiki'
import type { Logger } from 'vite'
import { containerPlugin } from './plugins/containers'
import { highlight } from './plugins/highlight'
@ -47,6 +47,7 @@ export interface MarkdownOptions extends MarkdownIt.Options {
headers?: HeadersPluginOptions
sfc?: SfcPluginOptions
theme?: ThemeOptions
languages?: ILanguageRegistration[]
toc?: TocPluginOptions
externalLinks?: Record<string, string>
}
@ -64,10 +65,17 @@ export const createMarkdownRenderer = async (
linkify: true,
highlight:
options.highlight ||
(await highlight(options.theme, options.defaultHighlightLang, logger)),
(await highlight(
options.theme,
options.languages,
options.defaultHighlightLang,
logger
)),
...options
}) as MarkdownRenderer
md.linkify.set({ fuzzyLink: false })
// custom plugins
md.use(componentPlugin)
.use(highlightLinePlugin)

@ -1,6 +1,11 @@
import { customAlphabet } from 'nanoid'
import c from 'picocolors'
import type { HtmlRendererOptions, IThemeRegistration } from 'shiki'
import {
BUNDLED_LANGUAGES,
type HtmlRendererOptions,
type ILanguageRegistration,
type IThemeRegistration
} from 'shiki'
import {
addClass,
createDiffProcessor,
@ -58,6 +63,7 @@ const errorLevelProcessor = defineProcessor({
export async function highlight(
theme: ThemeOptions = 'material-theme-palenight',
languages: ILanguageRegistration[] = [],
defaultLang: string = '',
logger: Pick<Logger, 'warn'> = console
): Promise<(str: string, lang: string, attrs: string) => string> {
@ -74,6 +80,7 @@ export async function highlight(
const highlighter = await getHighlighter({
themes: hasSingleTheme ? [theme] : [theme.dark, theme.light],
langs: [...BUNDLED_LANGUAGES, ...languages],
processors
})

@ -27,7 +27,8 @@ export const lineNumberPlugin = (md: MarkdownIt, enable = false) => {
const lineNumbersCode = [
...Array(
lines.length - (lines.at(-1) === `<span class="line"></span>` ? 1 : 0)
lines.length -
(lines[lines.length - 1] === `<span class="line"></span>` ? 1 : 0)
)
]
.map((_, index) => `<span class="line-number">${index + 1}</span><br>`)

@ -115,7 +115,7 @@ export const snippetPlugin = (md: MarkdownIt, srcDir: string) => {
rawTitle = ''
] = (rawPathRegexp.exec(rawPath) || []).slice(1)
const title = rawTitle || filename.split('/').at(-1) || ''
const title = rawTitle || filename.split('/').pop() || ''
state.line = startLine + 1

@ -55,7 +55,9 @@ export async function createMarkdownToVueRenderFn(
file: string,
publicDir: string
): Promise<MarkdownCompileResult> => {
const alias = siteConfig?.rewrites.map[file.slice(srcDir.length + 1)]
const alias =
siteConfig?.rewrites.map[file] || // virtual dynamic path file
siteConfig?.rewrites.map[file.slice(srcDir.length + 1)]
file = alias ? path.join(srcDir, alias) : file
const relativePath = slash(path.relative(srcDir, file))
const dir = path.dirname(file)
@ -69,6 +71,16 @@ export async function createMarkdownToVueRenderFn(
const start = Date.now()
// resolve params for dynamic routes
let params
src = src.replace(
/^__VP_PARAMS_START([^]+?)__VP_PARAMS_END__/,
(_, paramsString) => {
params = JSON.parse(paramsString)
return ''
}
)
// resolve includes
let includes: string[] = []
src = src.replace(includesRE, (m, m1) => {
@ -151,6 +163,7 @@ export async function createMarkdownToVueRenderFn(
description: inferDescription(frontmatter),
frontmatter,
headers,
params,
relativePath
}

@ -1,5 +1,6 @@
import path from 'path'
import c from 'picocolors'
import { slash } from './utils/slash'
import type { OutputAsset, OutputChunk } from 'rollup'
import {
defineConfig,
@ -14,12 +15,13 @@ import {
resolveAliases,
SITE_DATA_REQUEST_PATH
} from './alias'
import type { SiteConfig } from './config'
import { resolvePages, type SiteConfig } from './config'
import { clearCache, createMarkdownToVueRenderFn } from './markdownToVue'
import type { PageDataPayload } from './shared'
import { staticDataPlugin } from './staticDataPlugin'
import { slash } from './utils/slash'
import { webFontsPlugin } from './webFontsPlugin'
import { staticDataPlugin } from './plugins/staticDataPlugin'
import { webFontsPlugin } from './plugins/webFontsPlugin'
import { dynamicRoutesPlugin } from './plugins/dynamicRoutesPlugin'
import { rewritesPlugin } from './plugins/rewritesPlugin'
declare module 'vite' {
interface UserConfig {
@ -69,8 +71,7 @@ export async function createVitePressPlugin(
pages,
ignoreDeadLinks,
lastUpdated,
cleanUrls,
rewrites
cleanUrls
} = siteConfig
let markdownToVue: Awaited<ReturnType<typeof createMarkdownToVueRenderFn>>
@ -197,13 +198,16 @@ export async function createVitePressPlugin(
configDeps.forEach((file) => server.watcher.add(file))
}
server.middlewares.use((req, res, next) => {
if (req.url) {
const page = req.url.replace(/[?#].*$/, '').slice(site.base.length)
req.url = req.url.replace(page, rewrites.inv[page] || page)
// update pages, dynamicRoutes and rewrites on md file add / deletion
const onFileAddDelete = async (file: string) => {
if (file.endsWith('.md')) {
Object.assign(
siteConfig,
await resolvePages(siteConfig.srcDir, siteConfig.userConfig)
)
}
next()
})
}
server.watcher.on('add', onFileAddDelete).on('unlink', onFileAddDelete)
// serve our index.html after vite history fallback
return () => {
@ -307,9 +311,9 @@ export async function createVitePressPlugin(
try {
clearCache()
await recreateServer?.()
} catch (err) {
} catch (err: any) {
siteConfig.logger.error(
c.red(`\nfailed to restart server. error:\n${err}`)
`\n${c.red(`failed to restart server. error:`)}\n${err.stack}`
)
}
return
@ -344,9 +348,11 @@ export async function createVitePressPlugin(
return [
vitePressPlugin,
rewritesPlugin(siteConfig),
vuePlugin,
webFontsPlugin(siteConfig.useWebFonts),
...(userViteConfig?.plugins || []),
staticDataPlugin
staticDataPlugin,
await dynamicRoutesPlugin(siteConfig)
]
}

@ -0,0 +1,178 @@
import {
loadConfigFromFile,
normalizePath,
type Plugin,
type ViteDevServer
} from 'vite'
import fs from 'fs-extra'
import c from 'picocolors'
import path from 'path'
import { resolvePages, type SiteConfig } from '../config'
export const dynamicRouteRE = /\[(\w+?)\]/g
interface UserRouteConfig {
params: Record<string, string>
content?: string
}
interface RouteModule {
path: string
config: {
paths:
| UserRouteConfig[]
| (() => UserRouteConfig[] | Promise<UserRouteConfig[]>)
}
dependencies: string[]
}
export type ResolvedRouteConfig = UserRouteConfig & {
/**
* the raw route, e.g. foo/[bar].md
*/
route: string
/**
* the actual path with params resolved, e.g. foo/1.md
*/
path: string
}
export const dynamicRoutesPlugin = async (
config: SiteConfig
): Promise<Plugin> => {
let server: ViteDevServer
return {
name: 'vitepress:dynamic-routes',
configureServer(_server) {
server = _server
},
resolveId(id) {
if (!id.endsWith('.md')) return
const normalizedId = id.startsWith(config.root)
? normalizePath(path.relative(config.root, id))
: id.replace(/^\//, '')
const matched = config.dynamicRoutes.routes.find(
(r) => r.path === normalizedId
)
if (matched) {
return normalizedId
}
},
load(id) {
const matched = config.dynamicRoutes.routes.find((r) => r.path === id)
if (matched) {
const { route, params, content } = matched
const routeFile = normalizePath(path.resolve(config.root, route))
config.dynamicRoutes.fileToModulesMap[routeFile].add(id)
let baseContent = fs.readFileSync(routeFile, 'utf-8')
// inject raw content
// this is intended for integration with CMS
// we use a speical injection syntax so the content is rendered as
// static local content instead of included as runtime data.
if (content) {
baseContent = baseContent.replace(/<!--\s*@content\s*-->/, content)
}
// params are injected with special markers and extracted as part of
// __pageData in ../markdownTovue.ts
return `__VP_PARAMS_START${JSON.stringify(
params
)}__VP_PARAMS_END__${baseContent}`
}
},
async handleHotUpdate(ctx) {
const mods = config.dynamicRoutes.fileToModulesMap[ctx.file]
if (mods) {
// path loader module or deps updated, reset loaded routes
if (!/\.md$/.test(ctx.file)) {
Object.assign(
config,
await resolvePages(config.srcDir, config.userConfig)
)
}
for (const id of mods) {
ctx.modules.push(server.moduleGraph.getModuleById(id)!)
}
}
}
}
}
export async function resolveDynamicRoutes(
routes: string[]
): Promise<SiteConfig['dynamicRoutes']> {
const pendingResolveRoutes: Promise<ResolvedRouteConfig[]>[] = []
const routeFileToModulesMap: Record<string, Set<string>> = {}
for (const route of routes) {
// locate corresponding route paths file
const jsPathsFile = route.replace(/\.md$/, '.paths.js')
let pathsFile = jsPathsFile
if (!fs.existsSync(jsPathsFile)) {
pathsFile = route.replace(/\.md$/, '.paths.ts')
if (!fs.existsSync(pathsFile)) {
console.warn(
c.yellow(
`missing paths file for dynamic route ${route}: ` +
`a corresponding ${jsPathsFile} or ${pathsFile} is needed.`
)
)
continue
}
}
// load the paths loader module
let mod: RouteModule
try {
mod = (await loadConfigFromFile(
{} as any,
path.resolve(pathsFile)
)) as RouteModule
} catch (e) {
console.warn(`invalid paths file export in ${pathsFile}.`)
continue
}
if (mod) {
// this array represents the virtual modules affected by this route
const matchedModuleIds = (routeFileToModulesMap[
normalizePath(path.resolve(route))
] = new Set())
// each dependency (including the loader module itself) also point to the
// same array
for (const dep of mod.dependencies) {
routeFileToModulesMap[normalizePath(path.resolve(dep))] =
matchedModuleIds
}
const resolveRoute = async (): Promise<ResolvedRouteConfig[]> => {
const loader = mod.config.paths
const paths = await (typeof loader === 'function' ? loader() : loader)
return paths.map((userConfig) => {
return {
path: route.replace(
dynamicRouteRE,
(_, key) => userConfig.params[key]
),
route,
...userConfig
}
})
}
pendingResolveRoutes.push(resolveRoute())
}
}
return {
routes: (await Promise.all(pendingResolveRoutes)).flat(),
fileToModulesMap: routeFileToModulesMap
}
}

@ -0,0 +1,54 @@
import type { Plugin } from 'vite'
import { compile, match } from 'path-to-regexp'
import type { SiteConfig, UserConfig } from '../config'
export function resolveRewrites(
pages: string[],
userRewrites: UserConfig['rewrites']
) {
const rewriteEntries = Object.entries(userRewrites || {})
const rewrites = rewriteEntries.length
? Object.fromEntries(
pages
.map((src) => {
for (const [from, to] of rewriteEntries) {
const dest = rewrite(src, from, to)
if (dest) return [src, dest]
}
})
.filter((e) => e != null) as [string, string][]
)
: {}
return {
map: rewrites,
inv: Object.fromEntries(Object.entries(rewrites).map((a) => a.reverse()))
}
}
function rewrite(src: string, from: string, to: string) {
const urlMatch = match(from)
const res = urlMatch(src)
if (!res) return false
const toPath = compile(to)
return toPath(res.params)
}
export const rewritesPlugin = (config: SiteConfig): Plugin => {
return {
name: 'vitepress:rewrites',
configureServer(server) {
// dev rewrite
server.middlewares.use((req, _res, next) => {
if (req.url) {
const page = req.url
.replace(/[?#].*$/, '')
.slice(config.site.base.length)
if (config.rewrites.inv[page]) {
req.url = req.url.replace(page, config.rewrites.inv[page]!)
}
}
next()
})
}
}
}

@ -4,7 +4,7 @@ import {
loadConfigFromFile,
normalizePath
} from 'vite'
import { dirname, resolve } from 'path'
import path, { dirname, resolve } from 'path'
import { isMatch } from 'micromatch'
const loaderMatch = /\.data\.(j|t)s$/
@ -24,6 +24,8 @@ interface CachedLoaderModule {
const idToLoaderModulesMap: Record<string, CachedLoaderModule | undefined> =
Object.create(null)
const depToLoaderModuleIdMap: Record<string, string> = Object.create(null)
// During build, the load hook will be called on the same file twice
// once for client and once for server build. Not only is this wasteful, it
// also leads to a race condition in loadConfigFromFile() that results in an
@ -66,8 +68,16 @@ export const staticDataPlugin: Plugin = {
} else {
// use vite's load config util as a away to load Node.js file with
// TS & native ESM support
const loaderModule = (await loadConfigFromFile({} as any, id))
?.config as LoaderModule
const res = await loadConfigFromFile({} as any, id)
// record deps for hmr
if (server && res) {
for (const dep of res.dependencies) {
depToLoaderModuleIdMap[normalizePath(path.resolve(dep))] = id
}
}
const loaderModule = res?.config as LoaderModule
pattern =
typeof loaderModule.watch === 'string'
? [loaderModule.watch]
@ -103,20 +113,27 @@ export const staticDataPlugin: Plugin = {
if (server && loaderMatch.test(id)) {
// register this module as a glob importer
const { pattern } = idToLoaderModulesMap[id]!
;(server as any)._importGlobMap.set(id, [pattern])
if (pattern) {
;(server as any)._importGlobMap.set(id, [pattern])
}
}
return null
},
handleHotUpdate(ctx) {
const file = ctx.file
// dependency of data loader changed
// (note the dep array includes the loader file itself)
if (file in depToLoaderModuleIdMap) {
const id = depToLoaderModuleIdMap[file]!
delete idToLoaderModulesMap[id]
ctx.modules.push(server.moduleGraph.getModuleById(id)!)
}
for (const id in idToLoaderModulesMap) {
const { pattern } = idToLoaderModulesMap[id]!
const isLoaderFile = normalizePath(ctx.file) === id
if (isLoaderFile) {
// invalidate loader file
delete idToLoaderModulesMap[id]
}
if (isLoaderFile || (pattern && isMatch(ctx.file, pattern))) {
if (pattern && isMatch(file, pattern)) {
ctx.modules.push(server.moduleGraph.getModuleById(id)!)
}
}

@ -99,7 +99,7 @@ export function resolveSiteDataByRoute(
}
/**
* Create the page title string based on configs.
* Create the page title string based on config.
*/
export function createTitle(siteData: SiteData, pageData: PageData): string {
const title = pageData.title || siteData.title

@ -120,7 +120,7 @@ export namespace DefaultTheme {
export type NavItem = NavItemWithLink | NavItemWithChildren
export type NavItemWithLink = {
export interface NavItemWithLink {
text: string
link: string
@ -129,9 +129,11 @@ export namespace DefaultTheme {
* RegExp object here because it isn't serializable
*/
activeMatch?: string
target?: string
rel?: string
}
export type NavItemChildren = {
export interface NavItemChildren {
text?: string
items: NavItemWithLink[]
}

1
types/shared.d.ts vendored

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

Loading…
Cancel
Save