diff --git a/CHANGELOG.md b/CHANGELOG.md index da9f58d4..cee645f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +# [1.0.0-rc.35](https://github.com/vuejs/vitepress/compare/v1.0.0-rc.34...v1.0.0-rc.35) (2024-1-3) + +### Bug Fixes + +- **client:** add computed dir and lang to html root ([c2b4c66](https://github.com/vuejs/vitepress/commit/c2b4c66e79fde7479f5f43841e1921a5c220c9a5)), closes [#3353](https://github.com//github.com/vuejs/vitepress/pull/3353/issues/issuecomment-1874753809) +- fill all empty code lines ([563020b](https://github.com/vuejs/vitepress/commit/563020ba61abda254af9a124ddafd12de644cd4e)), closes [#3305](https://github.com/vuejs/vitepress/issues/3305) +- fix theme chunking logic causing out-of-order styles ([#3403](https://github.com/vuejs/vitepress/issues/3403)) ([a6cd891](https://github.com/vuejs/vitepress/commit/a6cd891d95454b3130aaf08f499659d2585acc63)) +- invalidate module cache for subsequent builds ([#3398](https://github.com/vuejs/vitepress/issues/3398)) ([27f60e0](https://github.com/vuejs/vitepress/commit/27f60e0b7784603c6fb300bd8dce64515eb98962)) + +### Features + +- allow passing options to emoji plugin ([09e48db](https://github.com/vuejs/vitepress/commit/09e48db355f530c7a138437004659b61239f4b75)), closes [#3174](https://github.com/vuejs/vitepress/issues/3174) +- **theme:** allow specifying rel and target in logoLink ([6c89943](https://github.com/vuejs/vitepress/commit/6c899437c15b126b488e73c99cdaad77fc7e5611)), closes [#3264](https://github.com/vuejs/vitepress/issues/3264) [#3271](https://github.com/vuejs/vitepress/issues/3271) + +### Performance Improvements + +- **localSearch:** add concurrency pooling, cleanup logic, improve performance ([#3374](https://github.com/vuejs/vitepress/issues/3374)) ([ac5881e](https://github.com/vuejs/vitepress/commit/ac5881eeac3f042a8fbf034edb99e5f2b45eaa2a)) + # [1.0.0-rc.34](https://github.com/vuejs/vitepress/compare/v1.0.0-rc.33...v1.0.0-rc.34) (2023-12-30) ### Bug Fixes diff --git a/README.md b/README.md index a8ccec37..2e70a766 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ -# VitePress (RC: release candidate) 📝💨 +# VitePress 📝💨 -[![Test](https://github.com/vuejs/vitepress/workflows/Test/badge.svg)](https://github.com/vuejs/vitepress/actions) +[![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) [![chat](https://img.shields.io/badge/chat-discord-blue?logo=discord)](https://chat.vuejs.org) --- -VitePress is [VuePress](https://vuepress.vuejs.org)' spiritual successor, built on top of [vite](https://github.com/vitejs/vite). - -Currently, it is in the `release candidate` stage. It is already suitable for out-of-the-box documentation use. We do not plan to introduce any breaking changes from here on until the stable release. +VitePress is a Vue-powered static site generator and a spiritual successor to [VuePress](https://vuepress.vuejs.org), built on top of [Vite](https://github.com/vitejs/vite). ## Documentation diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config/en.ts similarity index 69% rename from docs/.vitepress/config.ts rename to docs/.vitepress/config/en.ts index 500c583e..1f619347 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config/en.ts @@ -4,48 +4,11 @@ import { defineConfig, type DefaultTheme } from 'vitepress' const require = createRequire(import.meta.url) const pkg = require('vitepress/package.json') -export default defineConfig({ +export const en = defineConfig({ lang: 'en-US', - title: 'VitePress', description: 'Vite & Vue powered static site generator.', - lastUpdated: true, - cleanUrls: true, - - markdown: { - math: true, - codeTransformers: [ - // We use `[!!code` in demo to prevent transformation, here we revert it back. - { - postprocess(code) { - return code.replace(/\[\!\!code/g, '[!code') - } - } - ] - }, - - sitemap: { - hostname: 'https://vitepress.dev', - transformItems(items) { - return items.filter((item) => !item.url.includes('migration')) - } - }, - - /* prettier-ignore */ - head: [ - ['link', { rel: 'icon', type: 'image/svg+xml', href: '/vitepress-logo-mini.svg' }], - ['link', { rel: 'icon', type: 'image/png', href: '/vitepress-logo-mini.png' }], - ['meta', { name: 'theme-color', content: '#5f67ee' }], - ['meta', { name: 'og:type', content: 'website' }], - ['meta', { name: 'og:locale', content: 'en' }], - ['meta', { name: 'og:site_name', content: 'VitePress' }], - ['meta', { name: 'og:image', content: 'https://vitepress.dev/vitepress-og.jpg' }], - ['script', { src: 'https://cdn.usefathom.com/script.js', 'data-site': 'AZBRSFGG', 'data-spa': 'auto', defer: '' }] - ], - themeConfig: { - logo: { src: '/vitepress-logo-mini.svg', width: 24, height: 24 }, - nav: nav(), sidebar: { @@ -58,27 +21,9 @@ export default defineConfig({ 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' - }, - - search: { - provider: 'algolia', - options: { - appId: '8J64VVRP8K', - apiKey: 'a18e2f4cc5665f6602c5631fd868adfd', - indexName: 'vitepress' - } - }, - - carbonAds: { - code: 'CEBDT27Y', - placement: 'vuejsorg' } } }) @@ -111,7 +56,6 @@ function nav(): DefaultTheme.NavItem[] { ] } -/* prettier-ignore */ function sidebarGuide(): DefaultTheme.SidebarItem[] { return [ { @@ -140,7 +84,10 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] { collapsed: false, items: [ { text: 'Using a Custom Theme', link: 'custom-theme' }, - { text: 'Extending the Default Theme', link: 'extending-default-theme' }, + { + text: 'Extending the Default Theme', + link: 'extending-default-theme' + }, { text: 'Build-Time Data Loading', link: 'data-loading' }, { text: 'SSR Compatibility', link: 'ssr-compat' }, { text: 'Connecting to a CMS', link: 'cms' } diff --git a/docs/.vitepress/config/index.ts b/docs/.vitepress/config/index.ts new file mode 100644 index 00000000..50f024d1 --- /dev/null +++ b/docs/.vitepress/config/index.ts @@ -0,0 +1,66 @@ +import { defineConfig } from 'vitepress' +import { en } from './en' +import { zh, search as zhSearch } from './zh' + +export default defineConfig({ + title: 'VitePress', + + lastUpdated: true, + cleanUrls: true, + + markdown: { + math: true, + codeTransformers: [ + // We use `[!!code` in demo to prevent transformation, here we revert it back. + { + postprocess(code) { + return code.replace(/\[\!\!code/g, '[!code') + } + } + ] + }, + + sitemap: { + hostname: 'https://vitepress.dev', + transformItems(items) { + return items.filter((item) => !item.url.includes('migration')) + } + }, + + /* prettier-ignore */ + head: [ + ['link', { rel: 'icon', type: 'image/svg+xml', href: '/vitepress-logo-mini.svg' }], + ['link', { rel: 'icon', type: 'image/png', href: '/vitepress-logo-mini.png' }], + ['meta', { name: 'theme-color', content: '#5f67ee' }], + ['meta', { name: 'og:type', content: 'website' }], + ['meta', { name: 'og:locale', content: 'en' }], + ['meta', { name: 'og:site_name', content: 'VitePress' }], + ['meta', { name: 'og:image', content: 'https://vitepress.dev/vitepress-og.jpg' }], + ['script', { src: 'https://cdn.usefathom.com/script.js', 'data-site': 'AZBRSFGG', 'data-spa': 'auto', defer: '' }] + ], + + themeConfig: { + logo: { src: '/vitepress-logo-mini.svg', width: 24, height: 24 }, + + socialLinks: [ + { icon: 'github', link: 'https://github.com/vuejs/vitepress' } + ], + + search: { + provider: 'algolia', + options: { + appId: '8J64VVRP8K', + apiKey: 'a18e2f4cc5665f6602c5631fd868adfd', + indexName: 'vitepress', + locales: { ...zhSearch } + } + }, + + carbonAds: { code: 'CEBDT27Y', placement: 'vuejsorg' } + }, + + locales: { + root: { label: 'English', ...en }, + zh: { label: '简体中文', ...zh } + } +}) diff --git a/docs/.vitepress/config/zh.ts b/docs/.vitepress/config/zh.ts new file mode 100644 index 00000000..e5119e6b --- /dev/null +++ b/docs/.vitepress/config/zh.ts @@ -0,0 +1,204 @@ +import { createRequire } from 'module' +import { defineConfig, type DefaultTheme } from 'vitepress' + +const require = createRequire(import.meta.url) +const pkg = require('vitepress/package.json') + +export const zh = defineConfig({ + lang: 'zh-Hans', + description: '由 Vite 和 Vue 驱动的静态站点生成器', + + themeConfig: { + nav: nav(), + + sidebar: { + '/zh/guide/': { base: '/zh/guide/', items: sidebarGuide() }, + '/zh/reference/': { base: '/zh/reference/', items: sidebarReference() } + }, + + editLink: { + pattern: 'https://github.com/vuejs/vitepress/edit/main/docs/:path', + text: '在 GitHub 上编辑此页面' + }, + + footer: { + message: '根据 MIT 许可发布。', + copyright: '版权所有 © 2019 至今 尤雨溪' + }, + + docFooter: { + prev: '上一页', + next: '下一页' + }, + + outline: { + label: '页面导航' + }, + + lastUpdated: { + text: '最后更新于', + formatOptions: { + dateStyle: 'short', + timeStyle: 'medium' + } + }, + + langMenuLabel: '多语言', + returnToTopLabel: '回到顶部', + sidebarMenuLabel: '菜单', + darkModeSwitchLabel: '主题', + lightModeSwitchTitle: '切换到浅色模式', + darkModeSwitchTitle: '切换到深色模式' + } +}) + +function nav(): DefaultTheme.NavItem[] { + return [ + { + text: '指南', + link: '/zh/guide/what-is-vitepress', + activeMatch: '/zh/guide/' + }, + { + text: '参考', + link: '/zh/reference/site-config', + activeMatch: '/zh/reference/' + }, + { + text: pkg.version, + items: [ + { + text: '更新日志', + link: 'https://github.com/vuejs/vitepress/blob/main/CHANGELOG.md' + }, + { + text: '参与贡献', + link: 'https://github.com/vuejs/vitepress/blob/main/.github/contributing.md' + } + ] + } + ] +} + +function sidebarGuide(): DefaultTheme.SidebarItem[] { + return [ + { + text: '简介', + collapsed: false, + items: [ + { text: '什么是 VitePress?', link: 'what-is-vitepress' }, + { text: '快速开始', link: 'getting-started' }, + { text: '路由', link: 'routing' }, + { text: '部署', link: 'deploy' } + ] + }, + { + text: '写作', + collapsed: false, + items: [ + { text: 'Markdown 扩展', link: 'markdown' }, + { text: '资源处理', link: 'asset-handling' }, + { text: 'frontmatter', link: 'frontmatter' }, + { text: '在 Markdown 使用 Vue', link: 'using-vue' }, + { text: '国际化', link: 'i18n' } + ] + }, + { + text: '自定义', + collapsed: false, + items: [ + { text: '自定义主题', link: 'custom-theme' }, + { text: '扩展默认主题', link: 'extending-default-theme' }, + { text: '构建时数据加载', link: 'data-loading' }, + { text: 'SSR 兼容性', link: 'ssr-compat' }, + { text: '连接 CMS', link: 'cms' } + ] + }, + { + text: '实验性功能', + collapsed: false, + items: [ + { text: 'MPA 模式', link: 'mpa-mode' }, + { text: 'sitemap 生成', link: 'sitemap-generation' } + ] + }, + { text: '配置和 API 参考', base: '/zh/reference/', link: 'site-config' } + ] +} + +function sidebarReference(): DefaultTheme.SidebarItem[] { + return [ + { + text: '参考', + items: [ + { text: '站点配置', link: 'site-config' }, + { text: 'frontmatter 配置', link: 'frontmatter-config' }, + { text: '运行时 API', link: 'runtime-api' }, + { text: 'CLI', link: 'cli' }, + { + text: '默认主题', + base: '/zh/reference/default-theme-', + items: [ + { text: '概览', link: 'config' }, + { text: '导航栏', link: 'nav' }, + { text: '侧边栏', link: 'sidebar' }, + { text: '主页', link: 'home-page' }, + { text: '页脚', link: 'footer' }, + { text: '布局', link: 'layout' }, + { text: '徽章', link: 'badge' }, + { text: '团队页', link: 'team-page' }, + { text: '上下页链接', link: 'prev-next-links' }, + { text: '编辑链接', link: 'edit-link' }, + { text: '最后更新时间戳', link: 'last-updated' }, + { text: '搜索', link: 'search' }, + { text: 'Carbon Ads', link: 'carbon-ads' } + ] + } + ] + } + ] +} + +export const search: DefaultTheme.AlgoliaSearchOptions['locales'] = { + zh: { + placeholder: '搜索文档', + translations: { + button: { + buttonText: '搜索文档', + buttonAriaLabel: '搜索文档' + }, + modal: { + searchBox: { + resetButtonTitle: '清除查询条件', + resetButtonAriaLabel: '清除查询条件', + cancelButtonText: '取消', + cancelButtonAriaLabel: '取消' + }, + startScreen: { + recentSearchesTitle: '搜索历史', + noRecentSearchesText: '没有搜索历史', + saveRecentSearchButtonTitle: '保存至搜索历史', + removeRecentSearchButtonTitle: '从搜索历史中移除', + favoriteSearchesTitle: '收藏', + removeFavoriteSearchButtonTitle: '从收藏中移除' + }, + errorScreen: { + titleText: '无法获取结果', + helpText: '你可能需要检查你的网络连接' + }, + footer: { + selectText: '选择', + navigateText: '切换', + closeText: '关闭', + searchByText: '搜索提供者' + }, + noResultsScreen: { + noResultsText: '无法找到相关结果', + suggestedQueryText: '你可以尝试查询', + reportMissingResultsText: '你认为该查询应该有结果?', + reportMissingResultsLinkText: '点击反馈' + } + } + } + } +} diff --git a/docs/guide/i18n.md b/docs/guide/i18n.md index 0d256c73..b055c160 100644 --- a/docs/guide/i18n.md +++ b/docs/guide/i18n.md @@ -75,25 +75,39 @@ However, VitePress won't redirect `/` to `/en/` by default. You'll need to confi /* /en/:splat 302 ``` -**Pro tip:** If using the above approach, you can use `nf_lang` cookie to persist user's language choice. A very basic way to do this is register a watcher inside the [setup](./custom-theme#using-a-custom-theme) function of custom theme: +**Pro tip:** If using the above approach, you can use `nf_lang` cookie to persist user's language choice: ```ts // docs/.vitepress/theme/index.ts import DefaultTheme from 'vitepress/theme' +import Layout from './Layout.vue' export default { extends: DefaultTheme, - setup() { - const { lang } = useData() - watchEffect(() => { - if (inBrowser) { - document.cookie = `nf_lang=${lang.value}; expires=Mon, 1 Jan 2024 00:00:00 UTC; path=/` - } - }) - } + Layout } ``` +```vue + + + + +``` + ## RTL Support (Experimental) For RTL support, specify `dir: 'rtl'` in config and use some RTLCSS PostCSS plugin like , or . You'll need to configure your PostCSS plugin to use `:where([dir="ltr"])` and `:where([dir="rtl"])` as prefixes to prevent CSS specificity issues. diff --git a/docs/zh/guide/asset-handling.md b/docs/zh/guide/asset-handling.md new file mode 100644 index 00000000..019e6b0d --- /dev/null +++ b/docs/zh/guide/asset-handling.md @@ -0,0 +1,59 @@ +# 资源处理 {#asset-handling} + +## 引用静态资源 {#referencing-static-assets} + +所有的 Markdown 文件都会被编译成 Vue 组件,并由 [Vite](https://vitejs.dev/guide/assets.html) 处理。可以,**并且应该**使用相对路径来引用资源: + +```md +![An image](./image.png) +``` + +可以在 Markdown 文件、主题中的 `*.vue` 组件、样式和普通的 `.css` 文件中引用静态资源,通过使用绝对路径 (基于项目根目录) 或者相对路径 (基于文件系统)。后者类似于 Vite、Vue CLI 或者 webpack 的 `file-loader` 的行为。 + +常见的图像,媒体和字体文件会被自动检测并视作资源。 + +所有引用的资源,包括那些使用绝对路径的,都会在生产构建过程中被复制到输出目录,并使用哈希文件名。从未使用过的资源将不会被复制。小于 4kb 的图像资源将会采用 base64 内联——这可以通过 [`vite`](../reference/site-config#vite) 配置选项进行配置。 + +所有**静态**路径引用,包括绝对路径,都应基于你的工作目录的结构。 + +## public 目录 {#the-public-directory} + +有时可能需要一些静态资源,但这些资源没有直接被 Markdown 或主题组件直接引用,或者你可能想以原始文件名提供某些文件,像 `robots.txt`,favicons 和 PWA 图标这样的文件。 + +可以将这些文件放置在[源目录](./routing#source-directory)的 `public` 目录中。例如,如果项目根目录是 `./docs`,并且使用默认源目录位置,那么 public 目录将是 `./docs/public`。 + +放置在 `public` 中的资源将按原样复制到输出目录的根目录中。 + +请注意,应使用根绝对路径来引用放置在 `public` 中的文件——例如,`public/icon.png` 应始终在源代码中作为 `/icon.png` 引用。 + +## 根 URL {#base-url} + +如果站点没有部署在根 URL 上,则需要在 `.vitepress/config.js` 中设置 `base` 选项。例如,如果计划将站点部署到 `https://foo.github.io/bar/`,则 `base` 应设置为 `'/bar/'`(它应始终以斜杠开头和结尾)。 + +所有静态资源路径都会被自动处理,来适应不同的 `base` 配置值。例如,如果 markdown 中有一个对 `public` 中的资源的绝对引用: + +```md +![An image](/image-inside-public.png) +``` + +在这种情况下,更改 `base` 配置值时,**无需**更新该引用。 + +但是如果你正在编写一个主题组件,它动态地链接到资源,例如一个图片,它的 `src` 基于主题配置值: + +```vue + +``` + +在这种情况下,建议使用 VitePress 提供的 [`withBase` helper](../reference/runtime-api#withbase) 来包装路径: + +```vue + + + +``` diff --git a/docs/zh/guide/cms.md b/docs/zh/guide/cms.md new file mode 100644 index 00000000..734f4f22 --- /dev/null +++ b/docs/zh/guide/cms.md @@ -0,0 +1,56 @@ +--- +outline: deep +--- + +# 连接到 CMS {#connecting-to-a-cms} + +## 一般的工作流 {#general-workflow} + +将 VitePress 连接到 CMS 主要围绕[动态路由](./routing#dynamic-routes)展开。在继续阅读之前,请确保了解它的工作原理。 + +由于每个 CMS 的工作方式都不同,因此我们只能提供一个通用的工作流,你需要根据具体情况进行调整。 + +1. 如果你的 CMS 需要身份验证,请创建一个 `.env` 文件来存储你的 API token: + + ```js + // posts/[id].paths.js + import { loadEnv } from 'vitepress' + + const env = loadEnv('', process.cwd()) + ``` + +2. 从 CMS 获取必要的数据并将其格式调整为合适的路径数据: + + ```js + export default { + async paths() { + // 如有需要,使用相应的 CMS 客户端库 + const data = await (await fetch('https://my-cms-api', { + headers: { + // 如有必要,可使用 token + } + })).json() + + return data.map(entry => { + return { + params: { id: entry.id, /* title, authors, date 等 */ }, + content: entry.content + } + }) + } + } + ``` + +3. 在页面中渲染内容: + + ```md + # {{ $params.title }} + + - by {{ $params.author }} on {{ $params.date }} + + + ``` + +## 整合指南 {#integration-guides} + +如果你已经写了一篇关于如何将 VitePress 与特定 CMS 集成的指南,请点击下面的“在 GitHub 上编辑此页面”链接将它提交到这里! diff --git a/docs/zh/guide/custom-theme.md b/docs/zh/guide/custom-theme.md new file mode 100644 index 00000000..bee26870 --- /dev/null +++ b/docs/zh/guide/custom-theme.md @@ -0,0 +1,222 @@ +# 自定义主题 {#using-a-custom-theme} + +## 解析主题 {#theme-resolving} + +可以通过创建一个 `.vitepress/theme/index.js` 或 `.vitepress/theme/index.ts` 文件 (即“主题入口文件”) 来启用自定义主题: + +``` +. +├─ docs # 项目根目录 +│ ├─ .vitepress +│ │ ├─ theme +│ │ │ └─ index.js # 主题入口 +│ │ └─ config.js # 配置文件 +│ └─ index.md +└─ package.json +``` + +当检测到存在主题入口文件时,VitePress 总会使用自定义主题而不是默认主题。但你可以[拓展默认主题](./extending-default-theme)来在其基础上实现更高级的自定义。 + +## 主题接口 {#theme-interface} + +VitePress 自定义主题是一个对象,该对象具有如下接口: + +```ts +interface Theme { + /** + * 每个页面的根布局组件 + * @required + */ + Layout: Component + /** + * 增强 Vue 应用实例 + * @optional + */ + enhanceApp?: (ctx: EnhanceAppContext) => Awaitable + /** + * 扩展另一个主题,在我们的主题之前调用它的 `enhanceApp` + * @optional + */ + extends?: Theme +} + +interface EnhanceAppContext { + app: App // Vue 应用实例 + router: Router // VitePress 路由实例 + siteData: Ref // 站点级元数据 +} +``` + +主题入口文件需要将主题作为默认导出来导出: + +```js +// .vitepress/theme/index.js + +// 可以直接在主题入口导入 Vue 文件 +// VitePress 已预先配置 @vitejs/plugin-vue +import Layout from './Layout.vue' + +export default { + Layout, + enhanceApp({ app, router, siteData }) { + // ... + } +} +``` + +默认导出是自定义主题的唯一方式,并且只有 `Layout` 属性是必须的。所以从技术上讲,一个 VitePress 主题可以是一个单独的 Vue 组件。 + +在组件内部,它的工作方式就像是一个普通的 Vite + Vue 3 应用。请注意,主题还需要保证 [SSR 兼容](./ssr-compat)。 + +## 构建布局 {#building-a-layout} + +最基本的布局组件需要包含一个 [``](../reference/runtime-api#content) 组件: + +```vue + + +``` + +上面的布局只是将每个页面的 markdown 渲染为 HTML。我们添加的第一个改进是处理 404 错误: + +```vue{1-4,9-12} + + + +``` + +[`useData()`](../reference/runtime-api#usedata) 为我们提供了所有的运行时数据,以便我们根据不同条件渲染不同的布局。我们可以访问的另一个数据是当前页面的 frontmatter。通过利用这个数据,我们允许用户控制每个页面的布局。例如,用户可以指定一个页面是否使用特殊的主页布局: + +```md +--- +layout: home +--- +``` + +并且我们可以调整我们的主题进行处理: + +```vue{3,12-14} + + + +``` + +当然你可以将布局切分为不同的组件: + +```vue{3-5,12-15} + + + +``` + +请查看[运行时 API 参考](../reference/runtime-api)获取主题组件中所有可用内容。此外,可以利用[构建时数据加载](./data-loading)来生成数据驱动布局——例如,一个列出当前项目中所有博客文章的页面。 + +## 分发自定义主题 {#distributing-a-custom-theme} + +分发自定义主题最简单的方式是通过将其作为 [GitHub 模版仓库](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-template-repository)。 + +如果你希望将主题作为 npm 包来分发,请按照下面的步骤操作: + +1. 在包入口将主题对象作为默认导出来导出。 + +2. 如果合适的话,将主题配置类型定义作为 `ThemeConfig` 导出。 + +3. 如果主题需要调整 VitePress 配置,请在包的子路径下 (例如 `my-theme/config`) 下导出该配置,以便用户拓展。 + +4. 记录主题配置选项 (通过配置文件和 frontmatter)。 + +5. 提供清晰的说明,告诉用户如何使用主题 (见下文)。 + +## 使用自定义主题 {#consuming-a-custom-theme} + +要使用外部主题,请导入它并重新导出: + +```js +// .vitepress/theme/index.js +import Theme from 'awesome-vitepress-theme' + +export default Theme +``` + +如果主题需要拓展: + +```js +// .vitepress/theme/index.js +import Theme from 'awesome-vitepress-theme' + +export default { + extends: Theme, + enhanceApp(ctx) { + // ... + } +} +``` + +如果主题需要特殊的 VitePress 配置,也需要在配置中拓展: + +```ts +// .vitepress/theme/config.ts +import baseConfig from 'awesome-vitepress-theme/config' + +export default { + // 扩展主题的基本配置(如需要) + extends: baseConfig +} +``` + +最后,如果主题为其主题配置提供了类型: + +```ts +// .vitepress/theme/config.ts +import baseConfig from 'awesome-vitepress-theme/config' +import { defineConfigWithTheme } from 'vitepress' +import type { ThemeConfig } from 'awesome-vitepress-theme' + +export default defineConfigWithTheme({ + extends: baseConfig, + themeConfig: { + // 类型为 `ThemeConfig` + } +}) +``` diff --git a/docs/zh/guide/data-loading.md b/docs/zh/guide/data-loading.md new file mode 100644 index 00000000..69327c37 --- /dev/null +++ b/docs/zh/guide/data-loading.md @@ -0,0 +1,246 @@ +# 构建时数据加载 {#build-time-data-loading} + +VitePress 提供了**数据加载**的功能,它允许加载任意数据并从页面或组件中导入它。数据加载**只在构建时**执行:最终的数据将被序列化为 JavaScript 包中的 JSON。 + +数据加载可以被用于获取远程数据,也可以基于本地文件生成元数据。例如,可以使用数据加载来解析所有本地 API 页面并自动生成所有 API 入口的索引。 + +## 基本用法 {#basic-usage} + +一个用于数据加载的文件必须以 `.data.js` 或 `.data.ts` 结尾。该文件应该提供一个默认导出的对象,该对象具有 `load()` 方法: + +```js +// example.data.js +export default { + load() { + return { + hello: 'world' + } + } +} +``` + +数据加载模块只在 Node.js 中执行,因此可以按需导入 Node API 和 npm 依赖。 + +然后,可以在 `.md` 页面和 `.vue` 组件中使用 `data` 命名导出从该文件中导入数据: + +```vue + + +
{{ data }}
+``` + +输出: + +```json +{ + "hello": "world" +} +``` + +你会注意到数据加载本身并没有导出 `data`。这是因为 VitePress 在后台调用了 `load()` 方法,并通过名为 `data` 的命名导出隐式地暴露了结果。 + +即使它是异步的,这也是有效的: + +```js +export default { + async load() { + // 获取远程数据 + return (await fetch('...')).json() + } +} +``` + +## 使用本地文件生成数据 {#data-from-local-files} + +当需要基于本地文件生成数据时,应该在数据加载中使用 `watch` 选项,以便这些文件改动时可以触发热更新。 + +`watch` 选项也很方便,因为可以使用 [glob 模式](https://github.com/mrmlnc/fast-glob#pattern-syntax) 匹配多个文件。模式可以相对于数据加载文件本身,`load()` 函数将接收匹配文件的绝对路径。 + +下面的例子展示了如何使用 [csv-parse](https://github.com/adaltas/node-csv/tree/master/packages/csv-parse/) 加载 CSV 文件并将其转换为 JSON。因为此文件仅在构建时执行,因此不会将 CSV 解析器发送到客户端! + +```js +import fs from 'node:fs' +import { parse } from 'csv-parse/sync' + +export default { + watch: ['./data/*.csv'], + load(watchedFiles) { + // watchFiles 是一个所匹配文件的绝对路径的数组。 + // 生成一个博客文章元数据数组,可用于在主题布局中呈现列表。 + return watchedFiles.map((file) => { + return parse(fs.readFileSync(file, 'utf-8'), { + columns: true, + skip_empty_lines: true + }) + }) + } +} +``` + +## `createContentLoader` + +当构建一个内容为主的站点时,我们经常需要创建一个“档案”或“索引”页面:一个我们可以列出内容中的所有可用条目的页面,例如博客文章或 API 页面。我们**可以**直接使用数据加载 API 实现这一点,但由于这是一个常见的用例,VitePress 还提供了一个 `createContentLoader` 辅助函数来简化这个过程: + +```js +// posts.data.js +import { createContentLoader } from 'vitepress' + +export default createContentLoader('posts/*.md', /* options */) +``` + +该辅助函数接受一个相对于[项目根目录](./routing#project-root)的 glob 模式,并返回一个 `{ watch, load }` 数据加载对象,该对象可以用作数据加载文件中的默认导出。它还基于文件修改时间戳实现了缓存以提高开发性能。 + +请注意,数据加载仅适用于 Markdown 文件——匹配的非 Markdown 文件将被跳过。 + +加载的数据将是一个类型为 `ContentData[]` 的数组: + +```ts +interface ContentData { + // 页面的映射 URL,如 /posts/hello.html(不包括 base) + // 手动迭代或使用自定义 `transform` 来标准化路径 + url: string + // 页面的 frontmatter 数据 + frontmatter: Record + + // 只有启用了相关选项,才会出现以下内容 + // 我们将在下面讨论它们 + src: string | undefined + html: string | undefined + excerpt: string | undefined +} +``` + +默认情况下只提供 `url` 和 `frontmatter`。这是因为加载的数据将作为 JSON 内联在客户端包中,我们需要谨慎考虑其大小。下面的例子展示了如何使用数据构建最小的博客索引页面: + +```vue + + + +``` + +### 选项 {#options} + +默认数据可能不适合所有需求——可以选择使用选项转换数据: + +```js +// posts.data.js +import { createContentLoader } from 'vitepress' + +export default createContentLoader('posts/*.md', { + includeSrc: true, // 包含原始 markdown 源? + render: true, // 包含渲染的整页 HTML? + excerpt: true, // 包含摘录? + transform(rawData) { + // 根据需要对原始数据进行 map、sort 或 filter + // 最终的结果是将发送给客户端的内容 + return rawData.sort((a, b) => { + return +new Date(b.frontmatter.date) - +new Date(a.frontmatter.date) + }).map((page) => { + page.src // 原始 markdown 源 + page.html // 渲染的整页 HTML + page.excerpt // 渲染的摘录 HTML(第一个 `---` 上面的内容) + return {/* ... */} + }) + } +}) +``` + +查看它在 [Vue.js 博客](https://github.com/vuejs/blog/blob/main/.vitepress/theme/posts.data.ts)中是如何使用的。 + +`createContentLoader` API 也可以在[构建钩子](/reference/site-config#build-hooks)中使用: + +```js +// .vitepress/config.js +export default { + async buildEnd() { + const posts = await createContentLoader('posts/*.md').load() + // 根据 posts 元数据生成文件,如 RSS 订阅源 + } +} +``` + +**类型** + +```ts +interface ContentOptions { + /** + * 包含 src? + * @default false + */ + includeSrc?: boolean + + /** + * 将 src 渲染为 HTML 并包含在数据中? + * @default false + */ + render?: boolean + + /** + * 如果为 `boolean`,是否解析并包含摘录? (呈现为 HTML) + * + * 如果为 `function`,则控制如何从内容中提取摘录 + * + * 如果为 `string`,则定义用于提取摘录的自定义分隔符 + * 如果 `excerpt` 为 `true`,则默认分隔符为 `---` + * + * @see https://github.com/jonschlinkert/gray-matter#optionsexcerpt + * @see https://github.com/jonschlinkert/gray-matter#optionsexcerpt_separator + * + * @default false + */ + excerpt?: + | boolean + | ((file: { data: { [key: string]: any }; content: string; excerpt?: string }, options?: any) => void) + | string + + /** + * 转换数据。请注意,如果从组件或 Markdown 文件导入,数据将以 JSON 形式内联到客户端包中 + */ + transform?: (data: ContentData[]) => T | Promise +} +``` + +## 为数据加载器导出类型 {#typed-data-loaders} + +当使用 TypeScript 时,可以像这样为 loader 和 `data` 导出类型: + +```ts +import { defineLoader } from 'vitepress' + +export interface Data { + // data 类型 +} + +declare const data: Data +export { data } + +export default defineLoader({ + // 类型检查加载器选项 + watch: ['...'], + async load(): Promise { + // ... + } +}) +``` + +## 配置 {#configuration} + +要获取数据加载中的配置信息,可以使用如下代码: + +```ts +import type { SiteConfig } from 'vitepress' + +const config: SiteConfig = (globalThis as any).VITEPRESS_CONFIG +``` diff --git a/docs/zh/guide/deploy.md b/docs/zh/guide/deploy.md new file mode 100644 index 00000000..c2514be0 --- /dev/null +++ b/docs/zh/guide/deploy.md @@ -0,0 +1,291 @@ +--- +outline: deep +--- + +# 部署 VitePress 站点 {#deploy-your-vitepress-site} + +以下指南基于一些前提: + +- VitePress 站点位于项目的 `docs` 目录中。 +- 你使用的是默认的生成输出目录 (`.vitepress/dist`)。 +- VitePress 作为本地依赖项安装在项目中,并且你已在 `package.json` 中设置以下脚本: + + ```json + { + "scripts": { + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" + } + } + ``` + +## 本地构建与测试 {#build-and-test-locally} + +1. 可以运行以下命令来构建文档: + + ```sh + $ npm run docs:build + ``` + +2. 构建文档后,通过运行以下命令可以在本地预览它: + + ```sh + $ npm run docs:preview + ``` + + `preview` 命令将启动一个本地静态 Web 服务器`http://localhost:4173`,该服务器以 `.vitepress/dist` 作为源文件。这是检查生产版本在本地环境中是否正常的一种简单方法。 + +3. 可以通过传递 `--port` 作为参数来配置服务器的端口。 + + ```json + { + "scripts": { + "docs:preview": "vitepress preview docs --port 8080" + } + } + ``` + + 现在 `docs:preview` 方法将在 `http://localhost:8080` 启动服务器。 + +## 设定 public 根目录 {#setting-a-public-base-path} + +默认情况下,我们假设站点将部署在域名 (`/`)的根路径上。如果站点在子路径中提供服务,例如 `https://mywebsite.com/blog/`,则需要在 VitePress 配置中将 [`base`](../reference/site-config#base)选项设置为 `'/blog/'`。 + +**例:** 如果你使用的是 Github(或 GitLab)页面并部署到 `user.github.io/repo/`,请将 `base` 设置为 `/repo/`。 + +## HTTP 缓存标头 {#http-cache-headers} + +如果可以控制生产服务器上的 HTTP 标头,则可以配置 `cache-control` 标头以在重复访问时获得更好的性能。 + +生产版本对静态资源 (JavaScript、CSS 和其他非 `public` 目录中的导入资源) 使用哈希文件名。如果你使用浏览器开发工具的网络选项卡查看生产预览,你将看到类似 `app.4f283b18.js` 的文件。 + +此哈希 `4f283b18` 是从此文件的内容生成的。相同的哈希 URL 保证提供相同的文件内容——如果内容更改,URL 也会更改。这意味着你可以安全地为这些文件使用最强的缓存标头。所有此类文件都将放置在输出目录的 `assets/` 中,因此你可以为它们配置以下标头: + +``` +Cache-Control: max-age=31536000,immutable +``` + +::: details Netlify 示例 `_headers` 文件 + +``` +/assets/* + cache-control: max-age=31536000 + cache-control: immutable +``` + +注意:该 `_headers` 文件应放置在[public 目录](/guide/asset-handling#the-public-directory)中(在我们的例子中是 `docs/public/_headers`),以便将其逐字复制到输出目录。 + +[Netlify 自定义标头文档](https://docs.netlify.com/routing/headers/) + +::: + +::: details Vercel 配置示例 `vercel.json` + +```json +{ + "headers": [ + { + "source": "/assets/(.*)", + "headers": [ + { + "key": "Cache-Control", + "value": "max-age=31536000, immutable" + } + ] + } + ] +} +``` + +注意:`vercel.json` 文件应放在存储库的根目录中。 + +[Vercel 关于标头配置的文档](https://vercel.com/docs/concepts/projects/project-configuration#headers) + +::: + +## 各平台部署指南 {#platform-guides} + +### Netlify / Vercel / Cloudflare Pages / AWS Amplify / Render + +使用仪表板创建新项目并更改这些设置: + +- **构建命令:** `npm run docs:build` +- **输出目录:** `docs/.vitepress/dist` +- **node 版本:** `18` (或更高版本) + +::: warning +不要为 HTML 代码启用 _Auto Minify_ 等选项。它将从输出中删除对 Vue 有意义的注释。如果被删除,你可能会看到激活不匹配错误。 +::: + +### GitHub Pages + +1. 在项目的 `.github/workflows` 目录中创建一个名为 `deploy.yml` 的文件,其中包含这样的内容: + + ```yaml + # 构建 VitePress 站点并将其部署到 GitHub Pages 的示例工作流程 + # + name: Deploy VitePress site to Pages + + on: + # 在针对 `main` 分支的推送上运行。如果您 + # 使用 `master` 分支作为默认分支,请将其更改为 `master` + push: + branches: [main] + + # 允许您从 Actions 选项卡手动运行此工作流程 + workflow_dispatch: + + # 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages + permissions: + contents: read + pages: write + id-token: write + + # 只允许同时进行一次部署,跳过正在运行和最新队列之间的运行队列 + # 但是,不要取消正在进行的运行,因为我们希望允许这些生产部署完成 + concurrency: + group: pages + cancel-in-progress: false + + jobs: + # 构建工作 + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 # 如果未启用 lastUpdated,则不需要 + # - uses: pnpm/action-setup@v2 # 如果使用 pnpm,请取消注释 + # - uses: oven-sh/setup-bun@v1 # 如果使用 Bun,请取消注释 + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: npm # 或 pnpm / yarn + - name: Setup Pages + uses: actions/configure-pages@v3 + - name: Install dependencies + run: npm ci # 或 pnpm install / yarn install / bun install + - name: Build with VitePress + run: | + npm run docs:build # 或 pnpm docs:build / yarn docs:build / bun run docs:build + touch docs/.vitepress/dist/.nojekyll + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: docs/.vitepress/dist + + # 部署工作 + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + runs-on: ubuntu-latest + name: Deploy + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 + ``` + + ::: warning + 确保 VitePress 中的 `base` 选项配置正确。有关更多详细信息,请参阅[设置根路径](#setting-a-public-base-path)。 + ::: + +2. 在存储库设置中的“Pages”菜单项下,选择“Build and deployment > Source > GitHub Actions”。 + +3. 将更改推送到 `main` 分支并等待 GitHub Action 工作流完成。你应该看到站点部署到 `https://.github.io/[repository]/` 或 `https:///`,这取决于你的设置。你的站点将在每次推送到 `main` 分支时自动部署。 + +### GitLab Pages + +1. 如果你想部署到 `https:// .gitlab.io/ /`,将 VitePress 配置中的 `outDir` 设置为 `../public`。将 `base` 选项配置为 `'//'`。 + +2. 在项目的根目录中创建一个名为 `.gitlab-ci.yml` 的文件,其中包含以下内容。每当你更改内容时,这都会构建和部署你的站点: + + ```yaml + image: node:18 + pages: + cache: + paths: + - node_modules/ + script: + # - apk add git # 如果你使用的是像 alpine 这样的小型 docker 镜像,并且启用了 lastUpdated,请取消注释 + - npm install + - npm run docs:build + artifacts: + paths: + - public + only: + - main + ``` + +### Azure 静态 web 应用 {#azure-static-web-apps} + +1. 参考[官方文档](https://docs.microsoft.com/en-us/azure/static-web-apps/build-configuration)。 + +2. 在配置文件中设置这些值 (并删除不需要的值,如 `api_location`): + + - **`app_location`**: `/` + - **`output_location`**: `docs/.vitepress/dist` + - **`app_build_command`**: `npm run docs:build` + +### Firebase {#firebase} + +1. 在项目的根目录下创建 `firebase.json` 和 `.firebaserc`: + + `firebase.json`: + + ```json + { + "hosting": { + "public": "docs/.vitepress/dist", + "ignore": [] + } + } + ``` + + `.firebaserc`: + + ```json + { + "projects": { + "default": "" + } + } + ``` + +2. 运行 `npm run docs:build` 后,运行此命令进行部署: + + ```sh + firebase deploy + ``` + +### Surge + +1. 运行 `npm run docs:build` 后,运行此命令进行部署: + + ```sh + npx surge docs/.vitepress/dist + ``` + +### Heroku + +1. 参考 [`heroku-buildpack-static`](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-static) 中给出的文档和指南。 + +2. 使用以下内容在项目的根目录中创建一个名为 `static.json` 的文件: + + ```json + { + "root": "docs/.vitepress/dist" + } + ``` + +### Edgio + +请参阅[创建并部署 VitePress 应用程序到 Edgio](https://docs.edg.io/guides/vitepress)。 + +### Kinsta 静态站点托管 {#kinsta-static-site-hosting} + +你可以按照这些[说明](https://kinsta.com/docs/vitepress-static-site-example/) 在 [Kinsta](https://kinsta.com/static-site-hosting/) 上部署 Vitepress 站点。 diff --git a/docs/zh/guide/extending-default-theme.md b/docs/zh/guide/extending-default-theme.md new file mode 100644 index 00000000..e5b0c760 --- /dev/null +++ b/docs/zh/guide/extending-default-theme.md @@ -0,0 +1,337 @@ +--- +outline: deep +--- + +# 扩展默认主题 {#extending-the-default-theme} + +VitePress 默认的主题已经针对文档进行了优化,并且可以进行自定义。请参考[默认主题配置概览](../reference/default-theme-config)获取完整的选项列表。 + +但是有一些情况仅靠配置是不够的。例如: + +1. 需要调整 CSS 样式; +2. 需要修改 Vue 应用实例,例如注册全局组件; +3. 需要通过 layout 插槽将自定义内容注入到主题中; + +这些高级自定义配置将需要使用自定义主题来“拓展”默认主题。 + +:::tip +在继续之前,请确保首先阅读[自定义主题](./custom-theme)以了解其工作原理。 +::: + +## 自定义 CSS {#customizing-css} + +可以通过覆盖根级别的 CSS 变量来自定义默认主题的 CSS: + +```js +// .vitepress/theme/index.js +import DefaultTheme from 'vitepress/theme' +import './custom.css' + +export default DefaultTheme +``` + +```css +/* .vitepress/theme/custom.css */ +:root { + --vp-c-brand-1: #646cff; + --vp-c-brand-2: #747bff; +} +``` + +查看[默认主题 CSS 变量](https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css)来获取可以被覆盖的变量。 + +## 使用自定义字体 {#using-different-fonts} + +VitePress 使用 [Inter](https://rsms.me/inter/) 作为默认字体,并且将其包含在生成的输出中。该字体在生产环境中也会自动预加载。但是如果要使用不同的字体,这可能不是很好。 + +为了避免在生成后的输出中包含 Inter 字体,请从 `vitepress/theme-without-fonts` 中导入主题: + +```js +// .vitepress/theme/index.js +import DefaultTheme from 'vitepress/theme-without-fonts' +import './my-fonts.css' + +export default DefaultTheme +``` + +```css +/* .vitepress/theme/custom.css */ +:root { + --vp-font-family-base: /* normal text font */ + --vp-font-family-mono: /* code font */ +} +``` + +::: warning +如果你在使用像是[团队页](/reference/default-theme-team-page)这样的组件,请确保也从 `vitepress/theme-without-fonts` 中导入它们! +::: + +如果字体是通过 `@font-face` 引用的本地文件,它将会被作为资源被包含在 `.vitepress/dist/asset` 目录下,并且使用哈希后的文件名。为了预加载这个文件,请使用 [transformHead](/reference/site-config#transformhead) 构建钩子: + +```js +// .vitepress/config.js +export default { + transformHead({ assets }) { + // 相应地调整正则表达式以匹配字体 + const myFontFile = assets.find(file => /font-name\.\w+\.woff2/) + if (myFontFile) { + return [ + [ + 'link', + { + rel: 'preload', + href: myFontFile, + as: 'font', + type: 'font/woff2', + crossorigin: '' + } + ] + ] + } + } +} +``` + +## 注册全局组件 {#registering-global-components} + +```js +// .vitepress/theme/index.js +import DefaultTheme from 'vitepress/theme' + +/** @type {import('vitepress').Theme} */ +export default { + extends: DefaultTheme, + enhanceApp({ app }) { + // 注册自定义全局组件 + app.component('MyGlobalComponent' /* ... */) + } +} +``` + +如果使用 TypeScript: +```ts +// .vitepress/theme/index.ts +import type { Theme } from 'vitepress' +import DefaultTheme from 'vitepress/theme' + +export default { + extends: DefaultTheme, + enhanceApp({ app }) { + // 注册自定义全局组件 + app.component('MyGlobalComponent' /* ... */) + } +} satisfies Theme +``` + +因为我们使用 Vite,还可以利用 Vite 的 [glob 导入功能](https://cn.vitejs.dev/guide/features.html#glob-import)来自动注册一个组件目录。 + +## 布局插槽 {#layout-slots} + +默认主题的 `` 组件有一些插槽,能够被用来在页面的特定位置注入内容。下面这个例子展示了将一个组件注入到 outline 之前: + +```js +// .vitepress/theme/index.js +import DefaultTheme from 'vitepress/theme' +import MyLayout from './MyLayout.vue' + +export default { + extends: DefaultTheme, + // 使用注入插槽的包装组件覆盖 Layout + Layout: MyLayout +} +``` + +```vue + + + + +``` + +也可以使用渲染函数。 + +```js +// .vitepress/theme/index.js +import { h } from 'vue' +import DefaultTheme from 'vitepress/theme' +import MyComponent from './MyComponent.vue' + +export default { + extends: DefaultTheme, + Layout() { + return h(DefaultTheme.Layout, null, { + 'aside-outline-before': () => h(MyComponent) + }) + } +} +``` + +默认主题布局的全部可用插槽如下: + +- 当 `layout: 'doc'` (默认) 在 frontmatter 中被启用时: + - `doc-top` + - `doc-bottom` + - `doc-footer-before` + - `doc-before` + - `doc-after` + - `sidebar-nav-before` + - `sidebar-nav-after` + - `aside-top` + - `aside-bottom` + - `aside-outline-before` + - `aside-outline-after` + - `aside-ads-before` + - `aside-ads-after` +- 当 `layout: 'home'` 在 frontmatter 中被启用时: + - `home-hero-before` + - `home-hero-info` + - `home-hero-image` + - `home-hero-after` + - `home-features-before` + - `home-features-after` +- 当 `layout: 'page'` 在 frontmatter 中被启用时: + - `page-top` + - `page-bottom` +- 当未找到页面 (404) 时: + - `not-found` +- 总是启用: + - `layout-top` + - `layout-bottom` + - `nav-bar-title-before` + - `nav-bar-title-after` + - `nav-bar-content-before` + - `nav-bar-content-after` + - `nav-screen-content-before` + - `nav-screen-content-after` + +## Using View Transitions API + +### 关于外观切换 {#on-appearance-toggle} + +可以扩展默认主题以在切换颜色模式时提供自定义过渡动画。例如: + +```vue + + + + + + + +``` + +Result (**warning!**: flashing colors, sudden movements, bright lights): + +
+Demo + +![Appearance Toggle Transition Demo](/appearance-toggle-transition.webp) + +
+ +有关视图过渡动画的更多详细信息,请参阅 [Chrome 文档](https://developer.chrome.com/docs/web-platform/view-transitions/)。 + +### 路由切换时 {#on-route-change} + +即将到来。 + +## 重写内部组件 {#overriding-internal-components} + +可以使用 Vite 的 [aliases](https://vitejs.dev/config/shared-options.html#resolve-alias) 来用自定义组件替换默认主题的组件: + +```ts +import { fileURLToPath, URL } from 'node:url' +import { defineConfig } from 'vitepress' + +export default defineConfig({ + vite: { + resolve: { + alias: [ + { + find: /^.*\/VPNavBar\.vue$/, + replacement: fileURLToPath( + new URL('./components/CustomNavBar.vue', import.meta.url) + ) + } + ] + } + } +}) +``` + +想要了解组件的确切名称请参考我们的[源代码](https://github.com/vuejs/vitepress/tree/main/src/client/theme-default/components)。因为组件是内部的,因此在小版本更迭中,它们名字改动的可能性很小。 diff --git a/docs/zh/guide/frontmatter.md b/docs/zh/guide/frontmatter.md new file mode 100644 index 00000000..778b8111 --- /dev/null +++ b/docs/zh/guide/frontmatter.md @@ -0,0 +1,48 @@ +# frontmatter + +## 用法 {#usage} + +VitePress 支持在所有 Markdown 文件中使用 YAML frontmatter,并使用 [gray-matter](https://github.com/jonschlinkert/gray-matter) 解析。frontmatter 必须位于 Markdown 文件的顶部 (在任何元素之前,包括 ` + + +``` + +## RTL 支持 (实验性功能) {#rtl-support-experimental} + +支持 RTL 需要在配置中指定 `dir: 'rtl'` 并且使用一些 RTLCSS PostCSS 插件,例如 , 或者 。需要配置 PostCSS 插件,使用 `:where([dir="ltr"])` 和 `:where([dir="rtl"])` 作为前缀,以防止 CSS 特异性问题。 diff --git a/docs/zh/guide/markdown.md b/docs/zh/guide/markdown.md new file mode 100644 index 00000000..674b7c2e --- /dev/null +++ b/docs/zh/guide/markdown.md @@ -0,0 +1,898 @@ +# Markdown 拓展 {#markdown-extensions} + +VitePress 带有内置的 Markdown 拓展。 + +## 标题锚点 {#header-anchors} + +标题会自动应用锚点。可以使用 `markdown.anchor` 选项配置锚点的渲染。 + +### 自定义锚点 {#custom-anchors} + +要为标题指定自定义锚点而不是使用自动生成的锚点,请向标题添加后缀: + +``` +# 使用自定义锚点 {#my-anchor} +``` + +这允许将标题链接为 `#my-anchor`,而不是默认的 `#使用自定义锚点`。 + +## 链接 {#links} + +内部和外部链接都会被特殊处理。 + +### 内部链接 {#internal-links} + +内部链接将转换为单页导航的路由链接。此外,子目录中包含的每个 `index.md` 都会自动转换为 `index.html`,并带有相应的 URL `/`。 + +例如,给定以下目录结构: + +``` +. +├─ index.md +├─ foo +│ ├─ index.md +│ ├─ one.md +│ └─ two.md +└─ bar + ├─ index.md + ├─ three.md + └─ four.md +``` + +假设现在处于 `foo/one.md` 文件中: + +```md +[Home](/) +[foo](/foo/) +[foo heading](./#heading) +[bar - three](../bar/three) +[bar - three](../bar/three.md) +[bar - four](../bar/four.html) +``` + +### 页面后缀 {#page-suffix} + +默认情况下,生成的页面和内部链接带有 `.html` 后缀。 + +### 外部链接 {#external-links} + +外部链接带有 `target="_blank" rel="noreferrer"`: + +- [vuejs.org](https://vuejs.org) +- [VitePress on GitHub](https://github.com/vuejs/vitepress) + +## frontmatter {#frontmatter} + +[YAML 格式的 frontmatter](https://jekyllrb.com/docs/front-matter/) 开箱即用: + +```yaml +--- +title: Blogging Like a Hacker +lang: en-US +--- +``` + +此数据将可用于页面的其余部分,以及所有自定义和主题组件。 + +更多信息,参见 [frontmatter](../reference/frontmatter-config)。 + +## GitHub 风格的表格 {#github-style-tables} + +**输入** + +``` +| Tables | Are | Cool | +| ------------- | :-----------: | ----: | +| col 3 is | right-aligned | $1600 | +| col 2 is | centered | $12 | +| zebra stripes | are neat | $1 | +``` + +**输出** + +| Tables | Are | Cool | +| ------------- | :-----------: | -----: | +| col 3 is | right-aligned | \$1600 | +| col 2 is | centered | \$12 | +| zebra stripes | are neat | \$1 | + +## Emoji :tada: + +**输入** + +``` +:tada: :100: +``` + +**输出** + +:tada: :100: + +这里可以找到[所有支持的 emoji 列表](https://github.com/markdown-it/markdown-it-emoji/blob/master/lib/data/full.mjs)。 + +## 目录表 (TOC) {#table-of-contents} + +**输入** + +``` +[[toc]] +``` + +**输出** + +[[toc]] + +可以使用 `markdown.toc` 选项配置 TOC 的呈现效果。 + +## 自定义容器 {#custom-containers} + +自定义容器可以通过它们的类型、标题和内容来定义。 + +### 默认标题 {#default-title} + +**输入** + +```md +::: info +This is an info box. +::: + +::: tip +This is a tip. +::: + +::: warning +This is a warning. +::: + +::: danger +This is a dangerous warning. +::: + +::: details +This is a details block. +::: +``` + +**输出** + +::: info +This is an info box. +::: + +::: tip +This is a tip. +::: + +::: warning +This is a warning. +::: + +::: danger +This is a dangerous warning. +::: + +::: details +This is a details block. +::: + +### 自定义标题 {#custom-title} + +可以通过在容器的 "type" 之后附加文本来设置自定义标题。 + +**输入** + +````md +::: danger STOP +危险区域,请勿继续 +::: + +::: details 点我查看代码 +```js +console.log('Hello, VitePress!') +``` +::: +```` + +**输出** + +::: danger STOP +危险区域,请勿继续 +::: + +::: details 点我查看代码 +```js +console.log('Hello, VitePress!') +``` +::: + +此外,可以通过在站点配置中添加以下内容来全局设置自定义标题,如果不是用英语书写,这会很有帮助: + +```ts +// config.ts +export default defineConfig({ + // ... + markdown: { + container: { + tipLabel: '提示', + warningLabel: '警告', + dangerLabel: '危险', + infoLabel: '信息', + detailsLabel: '详细信息' + } + } + // ... +}) +``` + +### `raw` + +这是一个特殊的容器,可以用来防止与 VitePress 的样式和路由冲突。这在记录组件库时特别有用。可能还想查看 [whyframe](https://whyframe.dev/docs/integrations/vitepress) 以获得更好的隔离。 + +**语法** + +```md +::: raw +Wraps in a
+::: +``` + +`vp-raw` class 也可以直接用于元素。样式隔离目前是可选的: + +- 使用喜欢的包管理器来安装需要的依赖项: + + ```sh + $ npm add -D postcss + ``` + +- 创建 `docs/.postcssrc.cjs` 并将以下内容 + + ```js + import { postcssIsolateStyles } from 'vitepress' + + export default { + plugins: [postcssIsolateStyles()] + } + ``` + + It uses [`postcss-prefix-selector`](https://github.com/postcss/postcss-load-config) under the hood. You can pass its options like this: + + ```js + postcssIsolateStyles({ + includeFiles: [/vp-doc\.css/] // 默认为 /base\.css/ + }) + ``` + +## 代码块中的语法高亮 {#syntax-highlighting-in-code-blocks} + +VitePress 使用 [Shikiji](https://github.com/antfu/shikiji) ([Shiki](https://shiki.matsu.io/) 的改进版本) 在 Markdown 代码块中使用彩色文本实现语法高亮。Shiki 支持多种编程语言。需要做的就是将有效的语言别名附加到代码块的开头: + +**输入** + +```` +```js +export default { + name: 'MyComponent', + // ... +} +``` +```` + +```` +```html +
    +
  • + {{ todo.text }} +
  • +
+``` +```` + +**输出** + +```js +export default { + name: 'MyComponent' + // ... +} +``` + +```html +
    +
  • + {{ todo.text }} +
  • +
+``` + +在 Shikiji 的代码仓库中,可以找到[合法的编程语言列表](https://github.com/antfu/shikiji/blob/main/docs/languages.md)。 + +还可以全局配置中自定义语法高亮主题。有关详细信息,参见 [`markdown` 选项](../reference/site-config#markdown)得到更多信息。 + +## 在代码块中实现行高亮 {#line-highlighting-in-code-blocks} + +**输入** + +```` +```js{4} +export default { + data () { + return { + msg: 'Highlighted!' + } + } +} +``` +```` + +**输出** + +```js{4} +export default { + data () { + return { + msg: 'Highlighted!' + } + } +} +``` + +除了单行之外,还可以指定多个单行、多行,或两者均指定: + +- 多行:例如 `{5-8}`、`{3-10}`、`{10-17}` +- 多个单行:例如 `{4,7,9}` +- 多行与单行:例如 `{4,7-13,16,23-27,40}` + +**输入** + +```` +```js{1,4,6-8} +export default { // Highlighted + data () { + return { + msg: `Highlighted! + This line isn't highlighted, + but this and the next 2 are.`, + motd: 'VitePress is awesome', + lorem: 'ipsum' + } + } +} +``` +```` + +**输出** + +```js{1,4,6-8} +export default { // Highlighted + data () { + return { + msg: `Highlighted! + This line isn't highlighted, + but this and the next 2 are.`, + motd: 'VitePress is awesome', + lorem: 'ipsum', + } + } +} +``` + +也可以使用 `// [!code hl]` 注释实现行高亮。 + +**输入** + +```` +```js +export default { + data () { + return { + msg: 'Highlighted!' // [!code hl] + } + } +} +``` +```` + +**输出** + +```js +export default { + data() { + return { + msg: 'Highlighted!' // [!code hl] + } + } +} +``` + +## 代码块中聚焦 {#focus-in-code-blocks} + +在某一行上添加 `// [!code focus]` 注释将聚焦它并模糊代码的其他部分。 + +此外,可以使用 `// [!code focus:]` 定义要聚焦的行数。 + +**输入** + +`!code` 后面只需要一个空格,为了展示原始的代码而不被实际渲染,这里有两个空格: + +```` +```js +export default { + data () { + return { + msg: 'Focused!' // [!code focus] + } + } +} +``` +```` + +**输出** + +```js +export default { + data() { + return { + msg: 'Focused!' // [!code focus] + } + } +} +``` + +## 代码块中的颜色差异 {#colored-diffs-in-code-blocks} + +在某一行添加 `// [!code --]` 或 `// [!code ++]` 注释将会为该行创建 diff,同时保留代码块的颜色。 + +**输入** + +`!code` 后面只需要一个空格,为了展示原始的代码而不被实际渲染,这里有两个空格。 + +```` +```js +export default { + data () { + return { + msg: 'Removed' // [!code --] + msg: 'Added' // [!code ++] + } + } +} +``` +```` + +**输出** + +```js +export default { + data () { + return { + msg: 'Removed' // [!code --] + msg: 'Added' // [!code ++] + } + } +} +``` + +## 高亮“错误”和“警告” {#errors-and-warnings-in-code-blocks} + +在某一行添加 `// [!code warning]` 或 `// [!code error]` 注释将会为该行相应的着色。 + +**输入** + +`!code` 后面只需要一个空格,为了展示原始的代码而不被实际渲染,这里有两个空格。 + +```` +```js +export default { + data () { + return { + msg: 'Error', // [!code error] + msg: 'Warning' // [!code warning] + } + } +} +``` +```` + +**输出** + +```js +export default { + data() { + return { + msg: 'Error', // [!code error] + msg: 'Warning' // [!code warning] + } + } +} +``` + +## 行号 {#line-numbers} + +可以通过以下配置为每个代码块启用行号: + +```js +export default { + markdown: { + lineNumbers: true + } +} +``` + +查看 [`markdown` 选项](../reference/site-config#markdown) 获取更多信息。 + +可以在代码块中添加 `:line-numbers` / `:no-line-numbers` 标记来覆盖在配置中的设置。 + +还可以通过在 `:line-numbers` 之后添加 `=` 来自定义起始行号,例如 `:line-numbers=2` 表示代码块中的行号从 2 开始。 + +**输入** + +````md +```ts {1} +// 默认禁用行号 +const line2 = 'This is line 2' +const line3 = 'This is line 3' +``` + +```ts:line-numbers {1} +// 启用行号 +const line2 = 'This is line 2' +const line3 = 'This is line 3' +``` + +```ts:line-numbers=2 {1} +// 行号已启用,并从 2 开始 +const line3 = 'This is line 3' +const line4 = 'This is line 4' +``` +```` + +**输出** + +```ts {1} +// 默认禁用行号 +const line2 = 'This is line 2' +const line3 = 'This is line 3' +``` + +```ts:line-numbers {1} +// 启用行号 +const line2 = 'This is line 2' +const line3 = 'This is line 3' +``` + +```ts:line-numbers=2 {1} +// 行号已启用,并从 2 开始 +const line3 = 'This is line 3' +const line4 = 'This is line 4' +``` + +## 导入代码片段 {#import-code-snippets} + +可以通过下面的语法来从现有文件中导入代码片段: + +```md +<<< @/filepath +``` + +此语法同时支持[行高亮](#line-highlighting-in-code-blocks): + +```md +<<< @/filepath{highlightLines} +``` + +**输入** + +```md +<<< @/snippets/snippet.js{2} +``` + +**Code file** + +<<< @/snippets/snippet.js + +**输出** + +<<< @/snippets/snippet.js + +::: tip +`@` 的值对应于源代码根目录,默认情况下是 VitePress 项目根目录,除非配置了 `srcDir`。或者也可以从相对路径导入: + +```md +<<< ../snippets/snippet.js +``` + +::: + +也可以使用 [VS Code region](https://code.visualstudio.com/docs/editor/codebasics#_folding) 来只包含代码文件的相应部分。可以在文件目录后面的 `#` 符号后提供一个自定义的区域名: + +**输入** + +```md +<<< @/snippets/snippet-with-region.js#snippet{1} +``` + +**Code file** + +<<< @/snippets/snippet-with-region.js + +**输出** + +<<< @/snippets/snippet-with-region.js#snippet{1} + +也可以像这样在大括号内(`{}`)指定语言: + +```md +<<< @/snippets/snippet.cs{c#} + + + +<<< @/snippets/snippet.cs{1,2,4-6 c#} + + + +<<< @/snippets/snippet.cs{1,2,4-6 c#:line-numbers} +``` + +如果无法从文件拓展名推测出源语言,这将会很有帮助 + +## 代码组 {#code-groups} + +可以像这样对多个代码块进行分组: + +**输入** + +````md +::: code-group + +```js [config.js] +/** + * @type {import('vitepress').UserConfig} + */ +const config = { + // ... +} + +export default config +``` + +```ts [config.ts] +import type { UserConfig } from 'vitepress' + +const config: UserConfig = { + // ... +} + +export default config +``` + +::: +```` + +**输出** + +::: code-group + +```js [config.js] +/** + * @type {import('vitepress').UserConfig} + */ +const config = { + // ... +} + +export default config +``` + +```ts [config.ts] +import type { UserConfig } from 'vitepress' + +const config: UserConfig = { + // ... +} + +export default config +``` + +::: + +也可以在代码组中[导入代码片段](#import-code-snippets): + +**输入** + +```md +::: code-group + + + +<<< @/snippets/snippet.js + + + +<<< @/snippets/snippet-with-region.js#snippet{1,2 ts:line-numbers} [snippet with region] + +::: +``` + +**输出** + +::: code-group + +<<< @/snippets/snippet.js + +<<< @/snippets/snippet-with-region.js#snippet{1,2 ts:line-numbers} [snippet with region] + +::: + +## 包含 markdown 文件 {#markdown-file-inclusion} + +可以像这样在一个 markdown 文件中包含另一个 markdown 文件,甚至是内嵌的。 + +::: tip +也可以使用 `@`,它的值对应于源代码根目录,默认情况下是 VitePress 项目根目录,除非配置了 `srcDir`。 +::: + +例如,可以这样用相对路径包含 Markdown 文件: + +**输入** + +```md +# Docs + +## Basics + + +``` + +**Part file** (`parts/basics.md`) + +```md +Some getting started stuff. + +### Configuration + +Can be created using `.foorc.json`. +``` + +**等价代码** + +```md +# Docs + +## Basics + +Some getting started stuff. + +### Configuration + +Can be created using `.foorc.json`. +``` + +它还支持选择行范围: + +**输入** + +```md +# Docs + +## Basics + + +``` + +**Part file** (`parts/basics.md`) + +```md +Some getting started stuff. + +### Configuration + +Can be created using `.foorc.json`. +``` + +**等价代码** + +```md +# Docs + +## Basics + +### Configuration + +Can be created using `.foorc.json`. +``` + +所选行范围的格式可以是: `{3,}`、 `{,10}`、`{1,10}` + +::: warning +如果指定的文件不存在,这将不会产生错误。因此,在使用这个功能的时候请保证内容按预期呈现。 +::: + +## 数学方程 {#math-equations} + +现在这是可选的。要启用它, 需要安装 `markdown-it-mathjax3`,在配置文件中设置`markdown.math` 为 `true`: + +```sh +npm add -D markdown-it-mathjax3 +``` + +```ts +// .vitepress/config.ts +export default { + markdown: { + math: true + } +} +``` + +**输入** + +```md +When $a \ne 0$, there are two solutions to $(ax^2 + bx + c = 0)$ and they are +$$ x = {-b \pm \sqrt{b^2-4ac} \over 2a} $$ + +**Maxwell's equations:** + +| equation | description | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| $\nabla \cdot \vec{\mathbf{B}} = 0$ | divergence of $\vec{\mathbf{B}}$ is zero | +| $\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} = \vec{\mathbf{0}}$ | curl of $\vec{\mathbf{E}}$ is proportional to the rate of change of $\vec{\mathbf{B}}$ | +| $\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} = 4 \pi \rho$ | _wha?_ | +``` + +**输出** + +When $a \ne 0$, there are two solutions to $(ax^2 + bx + c = 0)$ and they are +$$ x = {-b \pm \sqrt{b^2-4ac} \over 2a} $$ + +**Maxwell's equations:** + +| equation | description | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| $\nabla \cdot \vec{\mathbf{B}} = 0$ | divergence of $\vec{\mathbf{B}}$ is zero | +| $\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} = \vec{\mathbf{0}}$ | curl of $\vec{\mathbf{E}}$ is proportional to the rate of change of $\vec{\mathbf{B}}$ | +| $\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} = 4 \pi \rho$ | _wha?_ | + +## 图片懒加载 {#image-lazy-loading} + +通过在配置文件中将 `lazyLoading` 设置为 `true`,可以为通过 markdown 添加的每张图片启用懒加载。 + +```js +export default { + markdown: { + image: { + // 默认禁用图片懒加载 + lazyLoading: true + } + } +} +``` + +## 高级配置 {#advanced-configuration} + +VitePress 使用 [markdown-it](https://github.com/markdown-it/markdown-it) 作为 Markdown 渲染器。上面提到的很多拓展功能都是通过自定义插件实现的。可以使用 `.vitepress/config.js` 中的 `markdown` 选项来进一步自定义 `markdown-it` 实例。 + +```js +import { defineConfig } from 'vitepress' +import markdownItAnchor from 'markdown-it-anchor' +import markdownItFoo from 'markdown-it-foo' + +export default defineConfig({ + markdown: { + // markdown-it-anchor 的选项 + // https://github.com/valeriangalliat/markdown-it-anchor#usage + anchor: { + permalink: markdownItAnchor.permalink.headerLink() + }, + // @mdit-vue/plugin-toc 的选项 + // https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-toc#options + toc: { level: [1, 2] }, + config: (md) => { + // 使用更多的 Markdown-it 插件! + md.use(markdownItFoo) + } + } +}) +``` + +请查看[配置参考:站点配置](../reference/site-config#markdown)来获取完整的可配置属性列表。 diff --git a/docs/zh/guide/migration-from-vitepress-0.md b/docs/zh/guide/migration-from-vitepress-0.md new file mode 100644 index 00000000..b6472108 --- /dev/null +++ b/docs/zh/guide/migration-from-vitepress-0.md @@ -0,0 +1,23 @@ +# 从 VitePress 0.x 迁移 {#migration-from-vitepress-0-x} + +如果你来自 VitePress 0.x 版本,VitePress 有了一些重大更改。请按照本指南了解如何将应用程序迁移到最新的 VitePress。 + +## 应用配置 {#app-config} + +- 国际化功能尚未实现。 + +## 主题配置 {#theme-config} + +- `sidebar` 选项改变了它的结构。 + - `children` 现在命名为 `items`。 + - 顶级侧边栏不包含 `link`。我们打算把它改回来。 +- 删除了 `repo`、`repoLabel`、`docsDir`、`docsBranch`、`editLinks`、`editLinkText`,以支持更灵活的api。 + - 要将带有图标的 GitHub 链接添加到导航,请使用 [社交链接](../reference/default-theme-config#nav) 功能。 + - 要添加“编辑此页面”功能,请使用 [编辑链接](../reference/default-theme-edit-link) 功能。 +- `lastUpdated` 选项现在分为` config.lastUpdated` 和 `themeConfig.lastUpdatedText`。 +- `carbonAds.carbon` 更改为 `carbonAds.code`. + +## frontmatter 配置 {#frontmatter-config} + +- `home: true` 选项已更改为 `layout: home`。此外,还修改了许多与主页相关的设置以提供附加功能。详情请参阅 [主页指南](../reference/default-theme-home-page)。 +- `footer` 选项移至 [`themeConfig.footer`](../reference/default-theme-footer). diff --git a/docs/zh/guide/migration-from-vuepress.md b/docs/zh/guide/migration-from-vuepress.md new file mode 100644 index 00000000..c21000cc --- /dev/null +++ b/docs/zh/guide/migration-from-vuepress.md @@ -0,0 +1,30 @@ +# 从 VuePress 迁移 {#migration-from-vuepress} + +## 配置 {#config} + +### 侧边栏 {#sidebar} + +侧边栏不再从 frontmatter 中自动获取。你可以自行阅读 [`frontmatter`](https://github.com/vuejs/vitepress/issues/572#issuecomment-1170116225) 来动态填充侧边栏。[迁移工具](https://github.com/vuejs/vitepress/issues/96)将来可能会提供。 + +## Markdown {#markdown} + +### 图片 {#images} + +与 VuePress 不同,在使用静态图片时,VitePress 会根据配置自动处理这些 [`base`](./asset-handling#base-url)。 + +因此,现在可以在没有 `img` 标签的情况下渲染图像。 + +```diff +- foo ++ ![foo](/foo.png) +``` + +::: warning +对于动态图像,仍然需要 `withBase`,如 [Base URL](./asset-handling#base-url) 中所示。 +::: + +使用 `` 正则表达式查找并替换为 `![$2]($1)` 用 `![](...)` 语法替换所有图像。 + +--- + +更多请继续关注... diff --git a/docs/zh/guide/mpa-mode.md b/docs/zh/guide/mpa-mode.md new file mode 100644 index 00000000..7112ef3e --- /dev/null +++ b/docs/zh/guide/mpa-mode.md @@ -0,0 +1,23 @@ +# MPA 模式 {#mpa-mode} + +可以通过命令行输入 `vitepress build --mpa` 或在配置文件中指定 `mpa: true` 配置选项来启用 MPA (Multi-Page Application) 模式。 + +在 MPA 模式下,所有页面都会默认不包含任何 JavaScript。因此,站点可能评估工具中获得更好的初始访问性能分数。 + +但是,由于 SPA 导航的缺失,跨页面链接将导致重新加载整个页面。MPA 模式下的导航不会像 SPA 模式那样立即响应。 + +同时请注意,默认情况下不使用 JavaScript 意味着你实际上只是将 Vue 作为服务器端模板语言。浏览器不会附加任何事件处理程序,因此将不会有任何交互性。要加载客户端 JavaScript,需要使用特殊的 ` + +# Hello +``` + +` +``` + +### 渲染原始内容 {#rendering-raw-content} + +传递给页面的参数将在客户端 JavaScript payload 中序列化,因此应该避免在参数中传递大量数据,例如从远程 CMS 获取的原始 Markdown 或 HTML 内容。 + +相反,可以使用每个路径对象上的 `content` 属性将此类内容传递到每个页面: + +```js +export default { + async paths() { + const posts = await (await fetch('https://my-cms.com/blog-posts')).json() + + return posts.map((post) => { + return { + params: { id: post.id }, + content: post.content // 原始 Markdown 或 HTML + } + }) + } +} +``` + +然后,使用以下特殊语法将内容呈现为 Markdown 文件本身的一部分: + +```md + +``` diff --git a/docs/zh/guide/sitemap-generation.md b/docs/zh/guide/sitemap-generation.md new file mode 100644 index 00000000..94d83d25 --- /dev/null +++ b/docs/zh/guide/sitemap-generation.md @@ -0,0 +1,53 @@ +# 生成 sitemap {#sitemap-generation} + +VitePress 提供开箱即用的配置,为站点生成 `sitemap.xml` 文件。要启用它,请将以下内容添加到 `.vitepress/config.js` 中: + +```ts +import { defineConfig } from 'vitepress' + +export default defineConfig({ + sitemap: { + hostname: 'https://example.com' + } +}) +``` + +要在 `sitemap.xml` 中包含 `` 标签,可以启用 [`lastUpdated`](../reference/default-theme-last-updated) 选项。 + +## 选项 {#options} + +VitePress 的 sitemap 由 [`sitemap`](https://www.npmjs.com/package/sitemap) 模块提供支持。可以将该模块支持的选项传递给配置文件中的 `sitemap` 选项。这些选项将直接传递给 `SitemapStream` 构造函数。有关更多详细信息,请参阅 [`sitemap` 文档](https://www.npmjs.com/package/sitemap#options-you-can-pass)。例如: + +```ts +import { defineConfig } from 'vitepress' + +export default defineConfig({ + sitemap: { + hostname: 'https://example.com', + lastmodDateOnly: false + } +}) +``` + +## `transformItems` Hook + +在将 sitemap 写入 `sitemap.xml` 文件之前,可以使用 `sitemap.transformItems` 钩子来修改 sitemap。使用 sitemap 调用该钩子,应返回 sitemap 数组。例如: + +```ts +import { defineConfig } from 'vitepress' + +export default defineConfig({ + sitemap: { + hostname: 'https://example.com', + transformItems: (items) => { + // 添加新选项或修改/过滤现有选项 + items.push({ + url: '/extra-page', + changefreq: 'monthly', + priority: 0.8 + }) + return items + } + } +}) +``` diff --git a/docs/zh/guide/ssr-compat.md b/docs/zh/guide/ssr-compat.md new file mode 100644 index 00000000..04ef3f04 --- /dev/null +++ b/docs/zh/guide/ssr-compat.md @@ -0,0 +1,136 @@ +--- +outline: deep +--- + +# SSR 兼容性 {#ssr-compatibility} + +通过使用 Vue 的服务器端渲染 (SSR) 功能,VitePress 能够在生产构建期间在 Node.js 中预渲染应用程序。这意味着主题组件中的所有自定义代码都需要考虑 SSR 兼容性。 + +[Vue 官方文档的 SSR 部分](https://cn.vuejs.org/guide/scaling-up/ssr.html)提供了更多有关 SSR 是什么,SSR / SSG 之间的关系以及编写 SSR 友好代码的常见注意事项等信息。原则上只在 Vue 组件的 `beforeMount` 或 `mounted` 钩子中访问 browser / DOM API。 + +## `` + +如果正在使用或演示不支持 SSR 的组件 (例如,包含自定义指令),则可以将它们包装在内置的 `` 组件中: + +```md + + + +``` + +## 在导入时访问浏览器 API 的库 {#libraries-that-access-browser-api-on-import} + +一些组件或库在**导入时**访问浏览器 API。要使用假定在导入时处于浏览器环境的代码,需要动态导入它们。 + +### 在 mounted 钩子中导入 {#importing-in-mounted-hook} + +```vue + +``` + +### 条件导入 {#conditional-import} + +也可以使用 `import.meta.env.SSR` 标志 ([Vite 环境变量](https://cn.vitejs.dev/guide/env-and-mode.html#env-变量)的一部分) 来有条件地导入依赖项: + +```js +if (!import.meta.env.SSR) { + import('./lib-that-access-window-on-import').then((module) => { + // use code + }) +} +``` + +因为 [`Theme.enhanceApp`](/guide/custom-theme#theme-interface) 可以是异步的,所以可以有条件地导入并注册访问浏览器 API 的 Vue 插件: + +```js +// .vitepress/theme/index.js +/** @type {import('vitepress').Theme} */ +export default { + // ... + async enhanceApp({ app }) { + if (!import.meta.env.SSR) { + const plugin = await import('plugin-that-access-window-on-import') + app.use(plugin.default) + } + } +} +``` + +如果使用 TypeScript: +```ts +// .vitepress/theme/index.ts +import type { Theme } from 'vitepress' + +export default { + // ... + async enhanceApp({ app }) { + if (!import.meta.env.SSR) { + const plugin = await import('plugin-that-access-window-on-import') + app.use(plugin.default) + } + } +} satisfies Theme +``` + +### `defineClientComponent` + +VitePress 为导入 Vue 组件提供了一个方便的辅助函数,该组件可以在导入时访问浏览器 API。 + +```vue + + + +``` + +还可以将 props/children/slots 传递给目标组件: + +```vue + + + +``` + +目标组件将仅在 wrapper 组件的 mounted 钩子中导入。 diff --git a/docs/zh/guide/using-vue.md b/docs/zh/guide/using-vue.md new file mode 100644 index 00000000..79cfd636 --- /dev/null +++ b/docs/zh/guide/using-vue.md @@ -0,0 +1,256 @@ +# 在 Markdown 使用 Vue {#using-vue-in-markdown} + +在 VitePress 中,每个 Markdown 文件都被编译成 HTML,而且将其作为 [Vue 单文件组件](https://cn.vuejs.org/guide/scaling-up/sfc.html)处理。这意味着可以在 Markdown 中使用任何 Vue 功能,包括动态模板、使用 Vue 组件或通过添加 ` + +## Markdown Content + +The count is: {{ count }} + + + + +``` + +:::warning 避免在 Markdown 中使用 ` +``` + +## 使用 teleport 传递组件内容 {#using-teleports} + +Vitepress 目前只有使用 teleport 传送到 body 的 SSG 支持。对于其他地方,可以将它们包裹在内置的 `` 组件中,或者通过 [postRender 钩子](../reference/site-config#postrender)将 teleport 标签注入到最终页面 HTML 中的正确位置。 + + + +::: details +<<< @/components/ModalDemo.vue +::: + +```md + + +
+ // ... +
+
+
+``` + + + + diff --git a/docs/zh/guide/what-is-vitepress.md b/docs/zh/guide/what-is-vitepress.md new file mode 100644 index 00000000..4b620168 --- /dev/null +++ b/docs/zh/guide/what-is-vitepress.md @@ -0,0 +1,57 @@ +# VitePress 是什么? {#what-is-vitepress} + +VitePress 是一个[静态站点生成器](https://en.wikipedia.org/wiki/Static_site_generator) (SSG),专为构建快速、以内容为中心的站点而设计。简而言之,VitePress 获取用 Markdown 编写的源内容,对其应用主题,并生成可以轻松部署到任何地方的静态 HTML 页面。 + +
+ +只是想尝试一下?跳到[快速开始](./getting-started)。 + +
+ +## 使用场景 {#use-cases} + +- **文档** + + VitePress 附带一个专为技术文档设计的默认主题,尤其是那些需要嵌入交互式演示的主题。它支持你正在阅读的这个页面,以及 [Vite](https://vitejs.dev/)、[Pinia](https://pinia.vuejs.org/)、[VueUse](https://vueuse.org/)、[Mermaid](https://mermaid.js.org/)、[Wikimedia Codex](https://doc.wikimedia.org/codex/latest/) 等文档。 + + [Vue.js 官方文档](https://vuejs.org/)也是基于 VitePress 的。但是为了可以在不同的翻译文档之间共享,它自定义了自己的主题 + +- **博客、档案和营销网站** + + VitePress 支持[完全的自定义主题](./custom-theme),具有标准 Vite + Vue 应用程序的开发体验。基于 Vite 构建还意味着可以直接利用其丰富生态系统中的 Vite 插件。此外,VitePress 提供了灵活的 API 来[加载数据](./data-loading) (本地或远程),也可以[动态生成路由](./routing#dynamic-routes)。只要可以在构建时确定数据,就可以使用它来构建几乎任何东西。 + + [Vue.js 官方博客](https://blog.vuejs.org/)是一个简单的博客页面,它根据本地内容生成其索引页面。 + +## 开发体验 {#developer-experience} + +VitePress 旨在使用 Markdown 生成内容时提供出色的开发体验。 + +- **[Vite 驱动](https://cn.vitejs.dev/)**:即时服务器启动,始终立即反映(<100ms)编辑变化,无需重新加载页面。 + +- **[内置 Markdown 扩展](./markdown)**:frontmatter、表格、语法高亮……应有尽有。具体来说,VitePress 提供了许多用于处理代码块的高级功能,使其真正成为技术文档的理想选择。 + +- **[Vue 增强的 Markdown](./using-vue)**:每个 Markdown 页面都是 Vue [单文件组件](https://cn.vuejs.org/guide/scaling-up/sfc.html),这要归功于 Vue 模板与 HTML 的 100% 语法兼容性。可以使用 Vue 模板语法或导入的 Vue 组件在静态内容中嵌入交互性。 + +## 性能 {#performance} + +与许多传统的 SSG 不同,VitePress 生成的站点实际上是一个[单页应用程序](https://en.wikipedia.org/wiki/Single-page_application) (SPA)。 + +- **快速初始加载** + + 对任何页面的初次访问都将提供静态的、预呈现的 HTML,以实现极快的加载速度和最佳的 SEO。然后页面加载一个 JavaScript 包,将页面变成 Vue SPA (这被称为“激活”)。激活是非常快的:在 [PageSpeed Insights](https://pagespeed.web.dev/report?url=https%3A%2F%2Fvitepress.dev%2F) 上,典型的 VitePress 站点即使在网络速度较慢的低端移动设备上也能获得近乎完美的性能分数。 + +- **加载完成后可以快速切换** + + 更重要的是,SPA 模型在首次加载后能够提升用户体验。用户在站点内导航时,不会再触发整个页面的刷新。而是通过获取并动态更新新页面的内容来实现切换。VitePress还会自动预加载视口范围内链接对应的页面片段。这样一来,大部分情况下,用户在加载完成后就能立即浏览新页面。 + +- **高效的交互** + + 为了能够嵌入静态 Markdown 中的动态 Vue 部分,每个 Markdown 页面都被处理为 Vue 组件并编译成 JavaScript。这听起来可能效率低下,但 Vue 编译器足够聪明,可以将静态和动态部分分开,从而最大限度地减少激活成本和有效负载大小。对于初始页面加载,静态部分会自动从 JavaScript 有效负载中删除,并在激活期间跳过。 + +## VuePress 又是什么? {#what-about-vuepress} + +VitePress 灵感来源于 VuePress。最初的 VuePress 基于 Vue 2 和 webpack。借助 Vue 3 和 Vite,VitePress 提供了更好的开发体验、更好的生产性能、更精美的默认主题和更灵活的自定义 API。 + +VitePress 和 VuePress 之间的 API 区别主要在于主题和自定义。如果使用的是带有默认主题的 VuePress 1,迁移到 VitePress 应该相对简单。 + +VuePress 2 也投入了精力,它也支持 Vue 3 和 Vite,与 VuePress 1 的兼容性更好。但是,并行维护两个 SSG 是难以持续的,因此 Vue 团队决定将重点放在 VitePress,作为长期的主要 SSG 选择推荐。 diff --git a/docs/zh/index.md b/docs/zh/index.md new file mode 100644 index 00000000..3f7c2207 --- /dev/null +++ b/docs/zh/index.md @@ -0,0 +1,56 @@ +--- +layout: home + +title: VitePress +titleTemplate: 由 Vite 和 Vue 驱动的静态站点生成器 + +hero: + name: VitePress + text: 由 Vite 和 Vue 驱动的静态站点生成器 + tagline: 简单、强大、快速。就是你想要的现代 SSG 框架! + actions: + - theme: brand + text: 快速开始 + link: /zh/guide/getting-started + - theme: alt + text: GitHub + link: https://github.com/vuejs/vitepress + image: + src: /vitepress-logo-large.webp + alt: VitePress + +features: + - icon: 📝 + title: 专注内容 + details: 只需 Markdown 即可轻松创建美观的文档站点。 + - icon: + title: 享受 Vite 无可比拟的体验 + details: 服务器即时启动,闪电般的热更新,还可以使用基于 Vite 生态的插件。 + - icon: + title: 使用 Vue 自定义 + details: 直接在 Markdown 中使用 Vue 语法和组件,或者使用 Vue 组件构建自定义主题。 + - icon: 🚀 + title: 速度真的很快! + details: 采用静态 HTML 实现快速的页面初次加载,使用客户端路由实现快速的页面切换导航。 +--- + diff --git a/docs/zh/reference/cli.md b/docs/zh/reference/cli.md new file mode 100644 index 00000000..71d892c6 --- /dev/null +++ b/docs/zh/reference/cli.md @@ -0,0 +1,74 @@ +# 命令行接口 {#command-line-interface} + +## `vitepress dev` + +使用指定目录作为根目录来启动 VitePress 开发服务器。默认为当前目录。在当前目录下运行时也可以省略 `dev` 命令。 + +### 用法 + +```sh +# 从当前目录启动,省略 `dev` +vitepress + +# 从子目录启动 +vitepress dev [root] +``` + +### 选项 {#options} + +| 选项 | 说明 | +| --------------- | ------------------------------------------ | +| `--open [path]` | 启动时打开浏览器 (`boolean \| string`) | +| `--port ` | 指定端口 (`number`) | +| `--base ` | public base URL (默认值: `/`) (`string`) | +| `--cors` | 启用 CORS | +| `--strictPort` | 如果指定的端口已被占用则退出 (`boolean`) | +| `--force` | 强制优化程序忽略缓存并重新绑定 (`boolean`) | + +## `vitepress build` + +构建用于生产环境的 VitePress 站点。 + +### 用法 + +```sh +vitepress build [root] +``` + +### 选项\ + +| 选项 | 说明 | +| ------------------------------ | ------------------------------------------------------------------------------------------------- | +| `--mpa` (experimental) | [MPA 模式](../guide/mpa-mode) 下构建,无需客户端激活 (`boolean`) | +| `--base ` | public base URL (默认值: `/`) (`string`) | +| `--target ` | 转译目标 (默认值:`"modules"`) (`string`) | +| `--outDir ` | 输出目录 (默认值:`.vitepress/dist`) (`string`) | +| `--minify [minifier]` | 启用/禁用压缩,或指定要使用的压缩程序 (默认值:`"esbuild"`) (`boolean \| "terser" \| "esbuild"`) | +| `--assetsInlineLimit ` | 静态资源 base64 内联阈值(以字节为单位)(默认值:`4096`) (`number`) | + +## `vitepress preview` + +在本地预览生产版本。 + +### 用法 + +```sh +vitepress preview [root] +``` + +### 选项 + +| 选项 | 说明 | +| --------------- | -------------------------------------- | +| `--base ` | public base URL (默认值: `/`) (`string`) | +| `--port ` | 指定端口 (`number`) | + +## `vitepress init` + +在当前目录中启动[安装向导](../guide/getting-started#setup-wizard)。 + +### 用法 + +```sh +vitepress init +``` diff --git a/docs/zh/reference/default-theme-badge.md b/docs/zh/reference/default-theme-badge.md new file mode 100644 index 00000000..c19cb129 --- /dev/null +++ b/docs/zh/reference/default-theme-badge.md @@ -0,0 +1,69 @@ +# 徽标 {#badge} + +徽标可让你为标题添加状态。例如,指定部分的类型或支持的版本可能很有用。 + +## 用法 {#usage} + +可以使用全局组件 `Badge` 。 + +```html +### Title +### Title +### Title +### Title +``` + +上面的代码渲染如下: + +### Title +### Title +### Title +### Title + +## 自定义子节点 {#custom-children} + +`` 接受 `children`,这将显示在徽标中。 + +```html +### Title custom element +``` + +### Title custom element + +## 自定义不同类型徽标的背景色 {#customize-type-color} + +可以通过覆盖 css 来自定义不同类型 `` 的样式。以下是默认值。 + +```css +:root { + --vp-badge-info-border: transparent; + --vp-badge-info-text: var(--vp-c-text-2); + --vp-badge-info-bg: var(--vp-c-default-soft); + + --vp-badge-tip-border: transparent; + --vp-badge-tip-text: var(--vp-c-brand-1); + --vp-badge-tip-bg: var(--vp-c-brand-soft); + + --vp-badge-warning-border: transparent; + --vp-badge-warning-text: var(--vp-c-warning-1); + --vp-badge-warning-bg: var(--vp-c-warning-soft); + + --vp-badge-danger-border: transparent; + --vp-badge-danger-text: var(--vp-c-danger-1); + --vp-badge-danger-bg: var(--vp-c-danger-soft); +} +``` + +## `` + +`` 组件接受以下属性: + +```ts +interface Props { + // 当传递 `` 时,该值将被忽略 + text?: string + + // 默认为 `tip`. + type?: 'info' | 'tip' | 'warning' | 'danger' +} +``` diff --git a/docs/zh/reference/default-theme-carbon-ads.md b/docs/zh/reference/default-theme-carbon-ads.md new file mode 100644 index 00000000..1633b281 --- /dev/null +++ b/docs/zh/reference/default-theme-carbon-ads.md @@ -0,0 +1,22 @@ +# Carbon Ads {#carbon-ads} + +VitePress 内置了对 [Carbon Ads](https://www.carbonads.net/) 的原生支持。通过在配置中定义 Carbon Ads 凭据,VitePress 将在页面上显示广告。 + +```js +export default { + themeConfig: { + carbonAds: { + code: 'your-carbon-code', + placement: 'your-carbon-placement' + } + } +} +``` + +这些值用于调用 carbon CDN 脚本,如下所示。 + +```js +`//cdn.carbonads.com/carbon.js?serve=${code}&placement=${placement}` +``` + +要了解有关 Carbon Ads 配置的更多信息,请访问 [Carbon Ads 站点](https://www.carbonads.net/)。 diff --git a/docs/zh/reference/default-theme-config.md b/docs/zh/reference/default-theme-config.md new file mode 100644 index 00000000..fca9b4fb --- /dev/null +++ b/docs/zh/reference/default-theme-config.md @@ -0,0 +1,449 @@ +# 默认主题配置 {#default-theme-config} + +主题配置可让自定义主题。可以通过将 `themeConfig` 添加到配置文件来定义主题配置: + +```ts +export default { + lang: 'en-US', + title: 'VitePress', + description: 'Vite & Vue powered static site generator.', + + // 主题相关配置 + themeConfig: { + logo: '/logo.svg', + nav: [...], + sidebar: { ... } + } +} +``` + +**此页面上记录的选项仅适用于默认主题**。不同的主题需要不同的主题配置。使用自定义主题时,主题配置对象将传递给主题,以便主题可以基于它作出不同表现。 + +## i18nRouting + +- 类型:`boolean` + +将本地语言更改为 `zh` 会将 URL 从 `/foo`(或 `/en/foo/`)更改为 `/zh/foo`。可以通过将 `themeConfig.i18nRouting` 设置为 `false` 来禁用此行为。 + +## logo + +- 类型:`ThemeableImage` + +导航栏上显示的 Logo,位于站点标题右侧。可以接受一个路径字符串,或者一个对象来设置在浅色/深色模式下不同的 Logo。 + +```ts +export default { + themeConfig: { + logo: '/logo.svg' + } +} +``` + +```ts +type ThemeableImage = + | string + | { src: string; alt?: string } + | { light: string; dark: string; alt?: string } +``` + +## siteTitle + +- 类型:`string | false` + +可以自定义此项以替换导航中的默认站点标题(应用配置中的 `title`)。当设置为 `false` 时,导航中的标题将被禁用。这在当 `logo` 已经包含站点标题文本时很有用。 + +```ts +export default { + themeConfig: { + siteTitle: 'Hello World' + } +} +``` + +## nav + +- 类型:`NavItem` + +导航菜单项的配置。可以在[默认主题: 导航栏](./default-theme-nav#navigation-links) 了解更多详情。 + +```ts +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 + +interface NavItemWithLink { + text: string + link: string + activeMatch?: string + target?: string + rel?: string +} + +interface NavItemChildren { + text?: string + items: NavItemWithLink[] +} + +interface NavItemWithChildren { + text?: string + items: (NavItemChildren | NavItemWithLink)[] + activeMatch?: string +} +``` + +## sidebar + +- 类型:`Sidebar` + +侧边栏菜单项的配置。可以在[默认主题: 侧边栏](./default-theme-sidebar) 了解更多详情。 + +```ts +export default { + themeConfig: { + sidebar: [ + { + text: 'Guide', + items: [ + { text: 'Introduction', link: '/introduction' }, + { text: 'Getting Started', link: '/getting-started' }, + ... + ] + } + ] + } +} +``` + +```ts +export type Sidebar = SidebarItem[] | SidebarMulti + +export interface SidebarMulti { + [path: string]: SidebarItem[] +} + +export type SidebarItem = { + /** + * 侧边栏项的文本标签 + */ + text?: string + + /** + * 侧边栏项的链接 + */ + link?: string + + /** + * 侧边栏项的子项 + */ + items?: SidebarItem[] + + /** + * 如果未指定,侧边栏组不可折叠 + * + * 如果为 `true`,则侧边栏组可折叠并且默认折叠 + * + * 如果为 `false`,则侧边栏组可折叠但默认展开 + */ + collapsed?: boolean +} +``` + +## aside + +- 类型:`boolean | 'left'` +- 默认值:`true` +- 每个页面可以通过 [frontmatter](./frontmatter-config#aside) 覆盖 + +将此值设置为 `false` 可禁用 aside(大纲) 容器。\ +将此值设置为 `true` 将在页面右侧渲染。\ +将此值设置为 `left` 将在页面左侧渲染。 + +如果想对所有页面禁用它,应该使用 `outline: false`。 + +## outline + +- 类型:`Outline | Outline['level'] | false` +- 每个页面可以通过 [frontmatter](./frontmatter-config#outline) 覆盖层级 + +将此值设置为 `false` 可禁止渲染大纲容器。更多详情请参考该接口: + +```ts +interface Outline { + /** + * outline 中要显示的标题级别。 + * 单个数字表示只显示该级别的标题。 + * 如果传递的是一个元组,第一个数字是最小级别,第二个数字是最大级别。 + * `'deep'` 与 `[2, 6]` 相同,将显示从 `

` 到 `

` 的所有标题。 + * + * @default 2 + */ + level?: number | [number, number] | 'deep' + + /** + * 显示在 outline 上的标题。 + * + * @default 'On this page' + */ + label?: string +} +``` + +## socialLinks + +- 类型:`SocialLink[]` + +可以定义此选项以在导航栏中展示带有图标的社交帐户链接。 + +```ts +export default { + themeConfig: { + socialLinks: [ + { icon: 'github', link: 'https://github.com/vuejs/vitepress' }, + { icon: 'twitter', link: '...' }, + // 可以通过将 SVG 作为字符串传递来添加自定义图标: + { + icon: { + svg: 'Dribbble' + }, + link: '...', + // 也可以为可访问性添加一个自定义标签(可选但推荐): + ariaLabel: 'cool link' + } + ] + } +} +``` + +```ts +interface SocialLink { + icon: SocialLinkIcon + link: string + ariaLabel?: string +} + +type SocialLinkIcon = + | 'discord' + | 'facebook' + | 'github' + | 'instagram' + | 'linkedin' + | 'mastodon' + | 'slack' + | 'twitter' + | 'youtube' + | { svg: string } +``` + +## footer + +- 类型:`Footer` +- 可以通过 [frontmatter](./frontmatter-config#footer) 进行覆盖。 + +页脚配置。可以添加 message 和 copyright。由于设计原因,仅当页面不包含侧边栏时才会显示页脚。 + +```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 +} +``` + +## editLink + +- 类型:`EditLink` +- 每个页面可以通过 [frontmatter](./frontmatter-config#editlink) 覆盖 + +编辑链接可让显示链接以编辑 Git 管理服务(例如 GitHub 或 GitLab)上的页面。有关详细信息,请参阅 [默认主题:编辑链接](./default-theme-edit-link)。 + +```ts +export default { + themeConfig: { + editLink: { + pattern: 'https://github.com/vuejs/vitepress/edit/main/docs/:path', + text: 'Edit this page on GitHub' + } + } +} +``` + +```ts +export interface EditLink { + pattern: string + text?: string +} +``` + +## lastUpdated + +- 类型:`LastUpdatedOptions` + +允许自定义上次更新的文本和日期格式。 + +```ts +export default { + themeConfig: { + lastUpdated: { + text: 'Updated at', + formatOptions: { + dateStyle: 'full', + timeStyle: 'medium' + } + } + } +} +``` + +```ts +export interface LastUpdatedOptions { + /** + * @default 'Last updated' + */ + text?: string + + /** + * @default + * { dateStyle: 'short', timeStyle: 'short' } + */ + formatOptions?: Intl.DateTimeFormatOptions & { forceLocale?: boolean } +} +``` + +## algolia + +- 类型:`AlgoliaSearch` + +支持使用 [Algolia DocSearch](https://docsearch.algolia.com/docs/what-is-docsearch) 搜索站点文档。在 [默认主题:搜索](./default-theme-search) 中了解更多信息。 + +```ts +export interface AlgoliaSearchOptions extends DocSearchProps { + locales?: Record> +} +``` + +在[这里](https://github.com/vuejs/vitepress/blob/main/types/docsearch.d.ts)查看完整配置。 + +## carbonAds {#carbon-ads} + +- 类型:`CarbonAdsOptions` + +一个配置即可展示 [Carbon Ads](https://www.carbonads.net/)。 + +```ts +export default { + themeConfig: { + carbonAds: { + code: 'your-carbon-code', + placement: 'your-carbon-placement' + } + } +} +``` + +```ts +export interface CarbonAdsOptions { + code: string + placement: string +} +``` + +在 [Default Theme: Carbon Ads](./default-theme-carbon-ads) 中了解更多信息。 + +## docFooter + +- 类型:`DocFooter` + +可用于自定义出现在上一页和下一页链接上方的文本。如果不是用英语编写文档,这很有帮助。也可用于全局禁用上一页/下一页链接。如果想有选择地启用/禁用上一个/下一个链接,可以使用 [frontmatter](./default-theme-prev-next-links)。 + +```ts +export default { + themeConfig: { + docFooter: { + prev: 'Pagina prior', + next: 'Proxima pagina' + } + } +} +``` + +```ts +export interface DocFooter { + prev?: string | false + next?: string | false +} +``` + +## darkModeSwitchLabel + +- 类型:`string` +- 默认值:`Appearance` + +用于自定义暗模式开关标签,该标签仅在移动端视图中显示。 + +## lightModeSwitchTitle + +- 类型:`string` +- 默认值:`Switch to light theme` + +用于自定义悬停时显示的亮模式开关标题。 + +## darkModeSwitchTitle + +- 类型:`string` +- 默认值:`Switch to dark theme` + +用于自定义悬停时显示的暗模式开关标题。 + +## sidebarMenuLabel + +- 类型:`string` +- 默认值:`Menu` + +用于自定义侧边栏菜单标签,该标签仅在移动端视图中显示。 + +## returnToTopLabel + +- 类型:`string` +- 默认值:`Return to top` + +用于自定义返回顶部按钮的标签,该标签仅在移动端视图中显示。 + +## langMenuLabel + +- 类型:`string` +- 默认值:`Change language` + +用于自定义导航栏中语言切换按钮的 aria-label,仅当使用 [i18n](../guide/i18n) 时才使用此选项。 + +## externalLinkIcon + +- 类型:`boolean` +- 默认值:`false` + +是否在 markdown 中的外部链接旁显示外部链接图标。 diff --git a/docs/zh/reference/default-theme-edit-link.md b/docs/zh/reference/default-theme-edit-link.md new file mode 100644 index 00000000..92f3cd25 --- /dev/null +++ b/docs/zh/reference/default-theme-edit-link.md @@ -0,0 +1,60 @@ +# 编辑链接 {#edit-link} + +## 站点级配置 {#site-level-config} + +编辑链接让你可以显示一个链接,以在 GitHub 或 GitLab 等 Git 管理服务上编辑页面。要启用它,请将 `themeConfig.editLink` 选项添加到配置中。 + +```js +export default { + themeConfig: { + editLink: { + pattern: 'https://github.com/vuejs/vitepress/edit/main/docs/:path' + } + } +} +``` + +`pattern` 选项定义链接的 URL 结构,并且 `:path` 将被替换为页面路径。 + +你还可以放置一个接受 [`PageData`](./runtime-api#usedata) 作为参数并返回 URL 字符串的纯函数。 + +```js +export default { + themeConfig: { + editLink: { + pattern: ({ filePath }) => { + if (filePath.startsWith('packages/')) { + return `https://github.com/acme/monorepo/edit/main/${filePath}` + } else { + return `https://github.com/acme/monorepo/edit/main/docs/${filePath}` + } + } + } + } +} +``` + +它不应该有副作用,也不应该访问其范围之外的任何东西,因为它将在浏览器中被序列化和执行。 + +默认情况下,这将在文档页面底部添加链接文本“Edit this page”。可以通过定义 `text` 选项来自定义此文本。 + +```js +export default { + themeConfig: { + editLink: { + pattern: 'https://github.com/vuejs/vitepress/edit/main/docs/:path', + text: 'Edit this page on GitHub' + } + } +} +``` + +## frontmatter 配置 {#frontmatter-config} + +可以使用 frontmatter 上的 `editLink` 选项单独禁用某个页面的编辑链接: + +```yaml +--- +editLink: false +--- +``` diff --git a/docs/zh/reference/default-theme-footer.md b/docs/zh/reference/default-theme-footer.md new file mode 100644 index 00000000..7499089f --- /dev/null +++ b/docs/zh/reference/default-theme-footer.md @@ -0,0 +1,53 @@ +# 页脚 {#footer} + +配置好 `themeConfig.footer`,VitePress 将在全局页面底部显示页脚。 + +```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 +} +``` + +上面的配置也支持 HTML 字符串。所以,例如,如果想配置页脚文本有一些链接,可以调整配置如下: + +```ts +export default { + themeConfig: { + footer: { + message: 'Released under the MIT License.', + copyright: 'Copyright © 2019-present Evan You' + } + } +} +``` + +::: warning +只有内联元素可以在 `message` 和 `copyright` 中使用,因为它们渲染在 `

` 元素中。如果想添加块元素,请考虑使用 [`layout-bottom`](../guide/extending-default-theme#layout-slots) 插槽。 +::: + +请注意,当[侧边栏](./default-theme-sidebar)可见时,不会显示页脚。 + +## frontmatter 配置 {#frontmatter-config} + +可以使用 frontmatter 上的 `footer` 选项在单独页面上禁用此功能: + +```yaml +--- +footer: false +--- +``` diff --git a/docs/zh/reference/default-theme-home-page.md b/docs/zh/reference/default-theme-home-page.md new file mode 100644 index 00000000..ac5848c4 --- /dev/null +++ b/docs/zh/reference/default-theme-home-page.md @@ -0,0 +1,155 @@ +# 主页 {#home-page} + +VitePress 默认主题提供了一个首页布局,也可以在[此站点首页](../)看到。可以通过 [frontmatter](./frontmatter-config) 指定 `layout: home` 在任何页面上使用它 + +```yaml +--- +layout: home +--- +``` + +但是,仅此选项不会有太大作用。可以通过设置其他选项(例如 `hero` 和 `features`)向主页添加几个不同的预模板化。 + +## Hero 部分 {#hero-section} + +Hero section 位于主页顶部。以下是配置 Hero 的方法。 + +```yaml +--- +layout: home + +hero: + name: VitePress + text: Vite & Vue powered static site generator. + tagline: Lorem ipsum... + image: + src: /logo.png + alt: VitePress + 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 { + // `text` 上方的字符,带有品牌颜色,预计简短,例如产品名称 + name?: string + + // hero 部分的主要文字,被定义为 `h1` 标签 + text: string + + // `text` 下方的标语 + tagline?: string + + // text 和 tagline 区域旁的图片 + image?: ThemeableImage + + // 主页 hero 部分的操作按钮 + actions?: HeroAction[] +} + +type ThemeableImage = + | string + | { src: string; alt?: string } + | { light: string; dark: string; alt?: string } + +interface HeroAction { + // 按钮的颜色主题,默认为 `brand` + theme?: 'brand' | 'alt' + + // 按钮的标签 + text: string + + // 按钮的目标链接 + link: string +} +``` + +### 自定义 name 的颜色 {#customizing-the-name-color} + +VitePress 通过 (`--vp-c-brand-1`) 设置 `name` 的颜色 .但是,可以通过覆盖 `--vp-home-hero-name-color` 变量来自定义此颜色。 + +```css +:root { + --vp-home-hero-name-color: blue; +} +``` + +也可以通过组合 `--vp-home-hero-name-background` 来进一步自定义 `name` 为渐变色。 + +```css +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient(120deg, #bd34fe, #41d1ff); +} +``` + +## Features 部分 {#features-section} + +在 Features section,可以在 Hero section 之后列出任意数量的 Features。可以在 frontmatter 中配置 `features`。 + +可以为每个 feature 提供一个图标,可以是表情符号或任何类型的图像。当配置的图标是图片(svg, png, jpeg...)时,必须提供合适的宽度和高度的图标;还可以在需要时配置其描述、固有大小以及深色和浅色主题下的不同表现。 + +```yaml +--- +layout: home + +features: + - icon: 🛠️ + title: Simple and minimal, always + details: Lorem ipsum... + - icon: + src: /cool-feature-icon.svg + title: Another cool feature + details: Lorem ipsum... + - icon: + dark: /dark-feature-icon.svg + light: /light-feature-icon.svg + title: Another cool feature + details: Lorem ipsum... +--- +``` + +```ts +interface Feature { + // 在每个 feature 框中显示图标 + icon?: FeatureIcon + + // feature 的标题 + title: string + + // feature 的详情 + details: string + + // 点击 feature 组件时的链接,可以是内部链接,也可以是外部链接。 + // + // 例如 `guide/reference/default-theme-home-page` 或 `https://example.com` + link?: string + + // feature 组件内显示的链接文本,最好与 `link` 选项一起使用 + // + // 例如 `Learn more`, `Visit page` 等 + linkText?: string + + // `link` 选项的链接 rel 属性 + // + // 例如 `external` + rel?: string +} + +type FeatureIcon = + | string + | { src: string; alt?: string; width?: string; height: string } + | { + light: string + dark: string + alt?: string + width?: string + height: string + } +``` \ No newline at end of file diff --git a/docs/zh/reference/default-theme-last-updated.md b/docs/zh/reference/default-theme-last-updated.md new file mode 100644 index 00000000..aa519731 --- /dev/null +++ b/docs/zh/reference/default-theme-last-updated.md @@ -0,0 +1,27 @@ +# 最后更新于 {#last-updated} + +最近一条内容的更新时间会显示在页面右下角。要启用它,请将 `lastUpdated` 选项添加到配置中。 + +::: tip +你必须提交 markdown 文件才能看到最近更新时间。 +::: + +## 全局配置 {#site-level-config} + +```js +export default { + lastUpdated: true +} +``` + +## frontmatter 配置 {#frontmatter-config} + +可以使用 frontmatter 上的 `lastUpdated` 选项单独禁用某个页面的最后更新展示: + +```yaml +--- +lastUpdated: false +--- +``` + +另请参阅[默认主题:最近更新时间](./default-theme-config#lastupdated) 了解更多详细信息。主题级别的任何 [truthy](https://developer.mozilla.org/zh-CN/docs/Glossary/Truthy) 值也将启用该功能,除非在站点或页面级别明确禁用。 diff --git a/docs/zh/reference/default-theme-layout.md b/docs/zh/reference/default-theme-layout.md new file mode 100644 index 00000000..1353ab46 --- /dev/null +++ b/docs/zh/reference/default-theme-layout.md @@ -0,0 +1,62 @@ +# 布局 {#layout} + +可以通过设置页面 [frontmatter](./frontmatter-config) 选项来选择页面布局。有 3 种布局选项 `doc`、`page` 和 `home`。如果未指定任何内容,则该页面将被视为 `doc` 页面。 + +```yaml +--- +layout: doc +--- +``` + +## doc 布局 {#doc-layout} + +`doc` 是默认布局,它将整个 Markdown 内容设置为“documentation”外观。它的工作原理是将整个内容包装在 css `vp-doc` 类中,并将样式应用于它下面的元素。 + +几乎所有通用元素,例如 `p`, 或 `h2` 都有特殊的样式。因此,请记住,如果在 Markdown 内容中添加任何自定义 HTML,这些内容也会受到这些样式的影响。 + +它还提供下面列出的文档特定功能。这些功能仅在此布局中启用。 + +- [编辑链接](./default-theme-edit-link) +- [上下页链接](./default-theme-prev-next-links) +- [大纲](./default-theme-config#outline) +- [Carbon Ads](./default-theme-carbon-ads) + +## page 布局 {#page-layout} + +`page` 被视为“空白页”。Markdown 仍然会被解析,所有的 [Markdown 拓展](../guide/markdown) 都和 `doc` 布局一样运行,但它没有任何默认样式。 + +`page` 布局将使可以自行设计所有内容,而不会受 VitePress 主题影响。当想要创建自己的自定义页面时,这很有用。 + +请注意,即使在此布局中,如果页面具有匹配的侧边栏配置,侧边栏仍会显示。 + +## home 布局 {#home-layout} + +`home` 将生成模板化的“主页”。在此布局中,可以设置额外的选项,例如 `hero` 和 `features` 以进一步自定义内容。请访问[默认主题: 主页](./default-theme-home-page)了解更多详情。 + +## 无布局 {#no-layout} + +如果不想要任何布局,可以通过 frontmatter 传递 `layout: false`。如果想要一个完全可自定义的登录页面(默认情况下没有任何侧边栏、导航栏或页脚),此选项很有用。 + +## 自定义布局 {#custom-layout} + +也可以使用自定义布局: + +```md +--- +layout: foo +--- +``` + +这将在上下文中查找注册名为 `foo` 的组件。例如,可以在 `.vitepress/theme/index.ts`中全局注册组件: + +```ts +import DefaultTheme from 'vitepress/theme' +import Foo from './Foo.vue' + +export default { + extends: DefaultTheme, + enhanceApp({ app }) { + app.component('foo', Foo) + } +} +``` diff --git a/docs/zh/reference/default-theme-nav.md b/docs/zh/reference/default-theme-nav.md new file mode 100644 index 00000000..ca844c65 --- /dev/null +++ b/docs/zh/reference/default-theme-nav.md @@ -0,0 +1,161 @@ +# 导航栏 {#nav} + +Nav 是显示在页面顶部的导航栏。它包含站点标题、全局菜单链接等。 + +## 站点标题和图标 {#site-title-and-logo} + +默认情况下,nav 显示 [`config.title`](./site-config#title) 作为站点的标题。如果想更改导航栏上显示的内容,可以在 `themeConfig.siteTitle` 选项中定义自定义文本。 + +```js +export default { + themeConfig: { + siteTitle: 'My Custom Title' + } +} +``` + +如果站点有图标,则可以通过传递图片路径来显示它。应该将图标直接放在 `public` 中,并赋值该绝对路径。 + +```js +export default { + themeConfig: { + logo: '/my-logo.svg' + } +} +``` + +添加图标时,它会与站点标题一起显示。如果只需要图标并且想要隐藏站点标题文本,请将 `siteTitle` 选项设置为 `false`。 + +```js +export default { + themeConfig: { + logo: '/my-logo.svg', + siteTitle: false + } +} +``` + +如果想添加 `alt` 属性或根据暗/亮模式自定义它,还可以将图标作为对象传递。有关详细信息,请参阅 [`themeConfig.logo`](./default-theme-config#logo)。 + +## 导航链接 {#navigation-links} + +可以定义 `themeConfig.nav` 选项以将链接添加到导航栏。 + +```js +export default { + themeConfig: { + nav: [ + { text: 'Guide', link: '/guide' }, + { text: 'Config', link: '/config' }, + { text: 'Changelog', link: 'https://github.com/...' } + ] + } +} +``` + +`text` 是 nav 中显示的实际文本,而 `link` 是单击文本时将导航到的链接。对于链接,将路径设置为不带 `.md` 后缀的实际文件,并且始终以 `/` 开头。 + +导航链接也可以是下拉菜单。为此,请替换 `link` 选项,设置 `items` 数组。 + +```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' } + ] + } + ] + } +} +``` + +请注意,下拉菜单标题(上例中的 `下拉菜单`)不能具有 `link` 属性,因为它是打开下拉对话框的按钮。 + +还可以通过传入更多嵌套项来进一步向下拉菜单项添加“sections”。 + +```js +export default { + themeConfig: { + nav: [ + { text: 'Guide', link: '/guide' }, + { + text: 'Dropdown Menu', + items: [ + { + // 该部分的标题 + text: 'Section A Title', + items: [ + { text: 'Section A Item A', link: '...' }, + { text: 'Section B Item B', link: '...' } + ] + } + ] + }, + { + text: 'Dropdown Menu', + items: [ + { + // 也可以省略标题 + items: [ + { text: 'Section A Item A', link: '...' }, + { text: 'Section B Item B', link: '...' } + ] + } + ] + } + ] + } +} +``` + +### 自定义链接的路由匹配状态 {#customize-link-s-active-state} + +当前页面位于匹配路径下时,导航菜单项将突出显示。如果 想自定义要匹配的路径,请将 `activeMatch` 属性和正则表达式定义为字符串值。 + +```js +export default { + themeConfig: { + nav: [ + // 当用户位于 `/config/` 路径时,该链接处于激活状态 + { + text: 'Guide', + link: '/guide', + activeMatch: '/config/' + } + ] + } +} +``` + +::: warning +`activeMatch` 应为正则表达式字符串,但必须将其定义为字符串。我们不能在这里使用实际的 RegExp 对象,因为它在构建期间不可序列化。 +::: + +### 自定义链接的“target”和“rel”属性 {#customize-link-s-target-and-rel-attributes} + +默认情况下,VitePress 会根据链接是否为外部链接自动判断 `target` 和 `rel` 属性。但如果愿意,也可以自定义它们。 + +```js +export default { + themeConfig: { + nav: [ + { + text: 'Merchandise', + link: 'https://www.thegithubshop.com/', + target: '_self', + rel: 'sponsored' + } + ] + } +} +``` + +## 社交链接 {#social-links} + +参考 [`socialLinks`](./default-theme-config#sociallinks)。 diff --git a/docs/zh/reference/default-theme-prev-next-links.md b/docs/zh/reference/default-theme-prev-next-links.md new file mode 100644 index 00000000..cdbe8434 --- /dev/null +++ b/docs/zh/reference/default-theme-prev-next-links.md @@ -0,0 +1,43 @@ +# 上下页链接 {#prev-next-links} + +可以自定义上一页和下一页的文本和链接 (显示在文档页脚处)。如果要使其与侧边栏上的文本不同,这会很有帮助。此外,你可能会发现,要禁用未包含在侧边栏中的页面的页脚或链接时,这很有用。 + +## prev + +- 类型:`string | false | { text?: string; link?: string }` + +- 说明: + + 指定要在指向上一页的链接上显示的文本/链接。如果没有在 frontmatter 中设置它,文本/链接将从侧边栏配置中推断出来。 + +- 示例: + + - 仅自定义文本: + + ```yaml + --- + prev: 'Get Started | Markdown' + --- + ``` + + - 自定义文本和链接: + + ```yaml + --- + prev: + text: 'Markdown' + link: '/guide/markdown' + --- + ``` + + - 隐藏上一页: + + ```yaml + --- + prev: false + --- + ``` + +## next + +与 `prev` 相同,但用于下一页。 diff --git a/docs/zh/reference/default-theme-search.md b/docs/zh/reference/default-theme-search.md new file mode 100644 index 00000000..4449a4ce --- /dev/null +++ b/docs/zh/reference/default-theme-search.md @@ -0,0 +1,379 @@ +--- +outline: deep +--- + +# 搜索 {#search} + +## 本地搜索 {#local-search} + +得益于 [minisearch](https://github.com/lucaong/minisearch/),VitePress 支持使用浏览器内索引进行模糊全文搜索。要启用此功能,只需在 `.vitepress/config.ts` 文件中将 `themeConfig.search.provider` 选项设置为 `'local'` 即可: + +```ts +import { defineConfig } from 'vitepress' + +export default defineConfig({ + themeConfig: { + search: { + provider: 'local' + } + } +}) +``` + +示例结果: + +![搜索弹窗截图](/search.png) + +或者,你可以使用 [Algolia DocSearch](#algolia-search) 或一些社区插件,例如: 或者 。 + +### i18n {#local-search-i18n} + +你可以使用这样的配置来使用多语言搜索: + +```ts +import { defineConfig } from 'vitepress' + +export default defineConfig({ + themeConfig: { + search: { + provider: 'local', + options: { + locales: { + zh: { + translations: { + button: { + buttonText: '搜索文档', + buttonAriaLabel: '搜索文档' + }, + modal: { + noResultsText: '无法找到相关结果', + resetButtonTitle: '清除查询条件', + footer: { + selectText: '选择', + navigateText: '切换' + } + } + } + } + } + } + } + } +}) +``` + +### MiniSearch 配置项 {#mini-search-options} + +你可以像这样配置 MiniSearch : + +```ts +import { defineConfig } from 'vitepress' + +export default defineConfig({ + themeConfig: { + search: { + provider: 'local', + options: { + miniSearch: { + /** + * @type {Pick} + */ + options: { + /* ... */ + }, + /** + * @type {import('minisearch').SearchOptions} + * @default + * { fuzzy: 0.2, prefix: true, boost: { title: 4, text: 2, titles: 1 } } + */ + searchOptions: { + /* ... */ + } + } + } + } + } +}) +``` + +参阅 [MiniSearch 文档](https://lucaong.github.io/minisearch/classes/MiniSearch.MiniSearch.html)了解更多信息。 + +### 自定义渲染内容 {#custom-content-renderer} + +可以在索引之前自定义用于渲染 Markdown 内容的函数: + +```ts +import { defineConfig } from 'vitepress' + +export default defineConfig({ + themeConfig: { + search: { + provider: 'local', + options: { + /** + * @param {string} src + * @param {import('vitepress').MarkdownEnv} env + * @param {import('markdown-it')} md + */ + _render(src, env, md) { + // 返回 html 字符串 + } + } + } + } +}) +``` + +该函数将从客户端站点数据中剥离,因此你可以在其中使用 Node.js API。 + +#### 示例:从搜索中排除页面 {#example-excluding-pages-from-search} + +你可以通过将 `search: false` 添加到页面的 `frontmatter` 来从搜索中排除页面。或者: + +```ts +import { defineConfig } from 'vitepress' + +export default defineConfig({ + themeConfig: { + search: { + provider: 'local', + options: { + _render(src, env, md) { + const html = md.render(src, env) + if (env.frontmatter?.search === false) return '' + if (env.relativePath.startsWith('some/path')) return '' + return html + } + } + } + } +}) +``` + +::: warning 注意 +如果提供了自定义的 `_render` 函数,你需要自己处理 `search: false` 的 frontmatter。此外,在调用 `md.render` 之前,`env` 对象不会完全填充,因此对可选 `env` 属性(如 `frontmatter` )的任何检查都应该在此之后完成。 +::: + +#### 示例:转换内容-添加锚点{#example-transforming-content-adding-anchors} + +```ts +import { defineConfig } from 'vitepress' + +export default defineConfig({ + themeConfig: { + search: { + provider: 'local', + options: { + _render(src, env, md) { + const html = md.render(src, env) + if (env.frontmatter?.title) + return md.render(`# ${env.frontmatter.title}`) + html + return html + } + } + } + } +}) +``` + +## Algolia Search {#algolia-search} + +VitePress 支持使用 [Algolia DocSearch](https://docsearch.algolia.com/docs/what-is-docsearch) 搜索文档站点。请参阅他们的入门指南。在你的 `.vitepress/config.ts` 中,你至少需要提供以下内容才能使其正常工作: + +```ts +import { defineConfig } from 'vitepress' + +export default defineConfig({ + themeConfig: { + search: { + provider: 'algolia', + options: { + appId: '...', + apiKey: '...', + indexName: '...' + } + } + } +}) +``` + +### i18n {#algolia-search-i18n} + +你可以使用这样的配置来使用多语言搜索: + +```ts +import { defineConfig } from 'vitepress' + +export default defineConfig({ + themeConfig: { + search: { + provider: 'algolia', + options: { + appId: '...', + apiKey: '...', + indexName: '...', + locales: { + zh: { + placeholder: '搜索文档', + translations: { + button: { + buttonText: '搜索文档', + buttonAriaLabel: '搜索文档' + }, + modal: { + searchBox: { + resetButtonTitle: '清除查询条件', + resetButtonAriaLabel: '清除查询条件', + cancelButtonText: '取消', + cancelButtonAriaLabel: '取消' + }, + startScreen: { + recentSearchesTitle: '搜索历史', + noRecentSearchesText: '没有搜索历史', + saveRecentSearchButtonTitle: '保存至搜索历史', + removeRecentSearchButtonTitle: '从搜索历史中移除', + favoriteSearchesTitle: '收藏', + removeFavoriteSearchButtonTitle: '从收藏中移除' + }, + errorScreen: { + titleText: '无法获取结果', + helpText: '你可能需要检查你的网络连接' + }, + footer: { + selectText: '选择', + navigateText: '切换', + closeText: '关闭', + searchByText: '搜索提供者' + }, + noResultsScreen: { + noResultsText: '无法找到相关结果', + suggestedQueryText: '你可以尝试查询', + reportMissingResultsText: '你认为该查询应该有结果?', + reportMissingResultsLinkText: '点击反馈' + } + } + } + } + } + } + } + } +}) +``` + +[这些选项](https://github.com/vuejs/vitepress/blob/main/types/docsearch.d.ts)可以被覆盖。请参阅 Algolia 官方文档以了解更多信息。 + +### 爬虫配置 {#crawler-config} + +以下是基于此站点使用的示例配置: + +```ts +new Crawler({ + appId: '...', + apiKey: '...', + rateLimit: 8, + startUrls: ['https://vitepress.dev/'], + renderJavaScript: false, + sitemaps: [], + exclusionPatterns: [], + ignoreCanonicalTo: false, + discoveryPatterns: ['https://vitepress.dev/**'], + schedule: 'at 05:10 on Saturday', + actions: [ + { + indexName: 'vitepress', + pathsToMatch: ['https://vitepress.dev/**'], + recordExtractor: ({ $, helpers }) => { + return helpers.docsearch({ + recordProps: { + lvl1: '.content h1', + content: '.content p, .content li', + lvl0: { + selectors: '', + defaultValue: 'Documentation' + }, + lvl2: '.content h2', + lvl3: '.content h3', + lvl4: '.content h4', + lvl5: '.content h5' + }, + indexHeadings: true + }) + } + } + ], + initialIndexSettings: { + vitepress: { + attributesForFaceting: ['type', 'lang'], + attributesToRetrieve: ['hierarchy', 'content', 'anchor', 'url'], + attributesToHighlight: ['hierarchy', 'hierarchy_camel', 'content'], + attributesToSnippet: ['content:10'], + camelCaseAttributes: ['hierarchy', 'hierarchy_radio', 'content'], + searchableAttributes: [ + 'unordered(hierarchy_radio_camel.lvl0)', + 'unordered(hierarchy_radio.lvl0)', + 'unordered(hierarchy_radio_camel.lvl1)', + 'unordered(hierarchy_radio.lvl1)', + 'unordered(hierarchy_radio_camel.lvl2)', + 'unordered(hierarchy_radio.lvl2)', + 'unordered(hierarchy_radio_camel.lvl3)', + 'unordered(hierarchy_radio.lvl3)', + 'unordered(hierarchy_radio_camel.lvl4)', + 'unordered(hierarchy_radio.lvl4)', + 'unordered(hierarchy_radio_camel.lvl5)', + 'unordered(hierarchy_radio.lvl5)', + 'unordered(hierarchy_radio_camel.lvl6)', + 'unordered(hierarchy_radio.lvl6)', + 'unordered(hierarchy_camel.lvl0)', + 'unordered(hierarchy.lvl0)', + 'unordered(hierarchy_camel.lvl1)', + 'unordered(hierarchy.lvl1)', + 'unordered(hierarchy_camel.lvl2)', + 'unordered(hierarchy.lvl2)', + 'unordered(hierarchy_camel.lvl3)', + 'unordered(hierarchy.lvl3)', + 'unordered(hierarchy_camel.lvl4)', + 'unordered(hierarchy.lvl4)', + 'unordered(hierarchy_camel.lvl5)', + 'unordered(hierarchy.lvl5)', + 'unordered(hierarchy_camel.lvl6)', + 'unordered(hierarchy.lvl6)', + 'content' + ], + distinct: true, + attributeForDistinct: 'url', + customRanking: [ + 'desc(weight.pageRank)', + 'desc(weight.level)', + 'asc(weight.position)' + ], + ranking: [ + 'words', + 'filters', + 'typo', + 'attribute', + 'proximity', + 'exact', + 'custom' + ], + highlightPreTag: '', + highlightPostTag: '', + minWordSizefor1Typo: 3, + minWordSizefor2Typos: 7, + allowTyposOnNumericTokens: false, + minProximity: 1, + ignorePlurals: true, + advancedSyntax: true, + attributeCriteriaComputedByMinProximity: true, + removeWordsIfNoResults: 'allOptional' + } + } +}) +``` + + diff --git a/docs/zh/reference/default-theme-sidebar.md b/docs/zh/reference/default-theme-sidebar.md new file mode 100644 index 00000000..8a001c0d --- /dev/null +++ b/docs/zh/reference/default-theme-sidebar.md @@ -0,0 +1,213 @@ +# 侧边栏 {#sidebar} + +侧边栏是文档的主要导航块。可以在 [`themeConfig.sidebar`](./default-theme-config#sidebar) 中配置侧边栏菜单。 + +```js +export default { + themeConfig: { + sidebar: [ + { + text: 'Guide', + items: [ + { text: 'Introduction', link: '/introduction' }, + { text: 'Getting Started', link: '/getting-started' }, + ... + ] + } + ] + } +} +``` + +## 基本用法 {#the-basics} + +侧边栏菜单的最简单形式是传入一个链接数组。第一级项目定义侧边栏的“部分”。它应该包含作为小标题的 `text` 和作为实际导航链接的 `items`。 + +```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' }, + ... + ] + } + ] + } +} +``` + +每个 `link` 都应指定以 `/` 开头的实际文件的路径。如果在链接末尾添加斜杠,它将显示相应目录的 `index.md`。 + +```js +export default { + themeConfig: { + sidebar: [ + { + text: 'Guide', + items: [ + // 显示的是 `/guide/index.md` 页面 + { text: 'Introduction', link: '/guide/' } + ] + } + ] + } +} +``` + +可以进一步将侧边栏项目嵌入到 6 级深度,从根级别上计数。请注意,深度超过 6 级将被忽略,并且不会在侧边栏上显示。 + +```js +export default { + themeConfig: { + sidebar: [ + { + text: 'Level 1', + items: [ + { + text: 'Level 2', + items: [ + { + text: 'Level 3', + items: [ + ... + ] + } + ] + } + ] + } + ] + } +} +``` + +## 多侧边栏 {#multiple-sidebars} + +可能会根据页面路径显示不同的侧边栏。例如,如本站点所示,可能希望在文档中创建单独的侧边栏,例如“指南”页面和“配置参考”页面。 + +为此,首先将你的页面组织到每个所需部分的目录中: + +``` +. +├─ guide/ +│ ├─ index.md +│ ├─ one.md +│ └─ two.md +└─ config/ + ├─ index.md + ├─ three.md + └─ four.md +``` + +然后,更新配置以定义每个部分的侧边栏。这一次,应该传递一个对象而不是数组。 + +```js +export default { + themeConfig: { + sidebar: { + // 当用户位于 `guide` 目录时,会显示此侧边栏 + '/guide/': [ + { + text: 'Guide', + items: [ + { text: 'Index', link: '/guide/' }, + { text: 'One', link: '/guide/one' }, + { text: 'Two', link: '/guide/two' } + ] + } + ], + + // 当用户位于 `config` 目录时,会显示此侧边栏 + '/config/': [ + { + text: 'Config', + items: [ + { text: 'Index', link: '/config/' }, + { text: 'Three', link: '/config/three' }, + { text: 'Four', link: '/config/four' } + ] + } + ] + } + } +} +``` + +## 可折叠的侧边栏组 {#collapsible-sidebar-groups} + +通过向侧边栏组添加 `collapsed` 选项,它会显示一个切换按钮来隐藏/显示每个部分。 + +```js +export default { + themeConfig: { + sidebar: [ + { + text: 'Section Title A', + collapsed: false, + items: [...] + } + ] + } +} +``` + +默认情况下,所有部分都是“打开”的。如果 希望它们在初始页面加载时“关闭”,请将 `collapsed` 选项设置为 `true`。 + +```js +export default { + themeConfig: { + sidebar: [ + { + text: 'Section Title A', + collapsed: true, + items: [...] + } + ] + } +} +``` + +## `useSidebar` + +返回侧边栏相关数据。返回的对象具有以下类型: + +```ts +export interface DocSidebar { + isOpen: Ref + sidebar: ComputedRef + sidebarGroups: ComputedRef + hasSidebar: ComputedRef + hasAside: ComputedRef + leftAside: ComputedRef + isSidebarEnabled: ComputedRef + open: () => void + close: () => void + toggle: () => void +} +``` + +**示例:** + +```vue + + + +``` diff --git a/docs/zh/reference/default-theme-team-page.md b/docs/zh/reference/default-theme-team-page.md new file mode 100644 index 00000000..0ff5568e --- /dev/null +++ b/docs/zh/reference/default-theme-team-page.md @@ -0,0 +1,257 @@ + + +# 团队页 {#team-page} + +如果你想介绍你的团队,你可以使用 Team components 来构建团队页面。有两种使用这些组件的方法。一种是将其嵌入文档页面,另一种是创建完整的团队页面。 + +## 在页面中显示团队成员 {#show-team-members-in-a-page} + +你可以在任何页面上使用从 `vitepress/theme` 暴露出的公共组件 `` 显示团队成员。 + +```html + + +# Our Team + +Say hello to our awesome team. + + +``` + +以上将在卡片外观元素中显示团队成员。它应该显示类似于下面的内容。 + + + +`` 组件有 2 种不同的尺寸,`small` 和 `medium`。虽然它取决于你的偏好,但通常尺寸在文档页面中使用时 `small` 应该更适合。此外,你可以为每个成员添加更多属性,例如添加“描述”或“赞助”按钮。在 [``](#vpteammembers)中了解更多信息。 + +在文档页面中嵌入团队成员对于小型团队来说非常有用,某种情况下,完整的贡献团队可能太大了,可以引入部分成员作为文档上下文的参考。 + +如果你有大量成员,或者只是想有更多空间来展示团队成员,请考虑[创建一个完整的团队页面](#create-a-full-team-page)。 + +## 创建一个完整的团队页面 {#create-a-full-team-page} + +除了将团队成员添加到 doc 页面,你还可以创建一个完整的团队页面,类似于创建自定义[默认主题:主页](./default-theme-home-page)的方式。 + +要创建团队页面,首先,创建一个新的 md 文件。文件名无所谓,这里我们就叫它 `team.md` 吧。在这个文件中,在 frontmatter 设置 `layout: page`,然后你可以使用 `TeamPage` 组件来组成页面结构。 + +```html +--- +layout: page +--- + + + + + + + + + +``` + +创建完整的团队页面时,请记住用 `` 组件包装所有团队相关组件,以获得正确的布局结构,如间距。 + +`` 组件添加页面标题部分。标题是 `

` 标题。使用 `#title` 和 `#lead` 插槽来介绍你的团队。 + +`` 和在 doc 页面中使用时一样。它将显示成员列表。 + +### 添加分段以划分团队成员 {#add-sections-to-divide-team-members} + +你可以将“分段”添加到团队页面。例如,你可能有不同类型的团队成员,例如核心团队成员和社区合作伙伴。你可以将这些成员分成几个部分,以更好地解释每组的角色。 + +为此,将 `` 组件添加到我们之前创建的 `team.md` 文件中。 + +```html +--- +layout: page +--- + + + + + + + + + + + + + + +``` + +`` 组件可以有类似于 `VPTeamPageTitle` 组件的 `#title` 和 `#lead` 插槽,还有用于显示团队成员的 `#members` 插槽。 + +请记住将 `` 组件放入 `#members` 插槽中。 + +## `` + +`` 组件显示给定的成员列表。 + +```html + +``` + +```ts +interface Props { + // 每个成员的大小,默认为 `medium` + size?: 'small' | 'medium' + + // 显示的成员列表 + members: TeamMember[] +} + +interface TeamMember { + // 成员的头像图像 + avatar: string + + // 成员的名称 + name: string + + // 成员姓名下方的标题 + // 例如:Developer, Software Engineer, etc. + title?: string + + // 成员所属的组织 + org?: string + + // 组织的 URL + orgLink?: string + + // 成员的描述 + desc?: string + + // 社交媒体链接,例如 GitHub、Twitter 等,可以在此处传入 Social Links 对象 + // 参见: https://vitepress.dev/reference/default-theme-config.html#sociallinks + links?: SocialLink[] + + // 成员 sponsor 页面的 URL + sponsor?: string + + // sponsor 链接的文本,默认为 'Sponsor' + actionText?: string +} +``` + +## `` + +创建完整团队页面时的根组件。它只接受一个插槽。它将设置所有传入的团队相关组件的样式。 + +## `` + +添加页面的标题。最好在一开始就在 `` 下使用。它接受 `#title` 和 `#lead` 插槽。 + +```html + + + + + + +``` + +## `` + +在团队页面中创建一个“分段”。它接受 `#title`、`#lead` 和 `#members` 插槽。你可以在 `` 中添加任意数量的分段。 + +```html + + ... + + + + + + +``` diff --git a/docs/zh/reference/frontmatter-config.md b/docs/zh/reference/frontmatter-config.md new file mode 100644 index 00000000..48ff8c49 --- /dev/null +++ b/docs/zh/reference/frontmatter-config.md @@ -0,0 +1,221 @@ +--- +outline: deep +--- + +# frontmatter 配置 {#frontmatter-config} + +frontmatter 支持基于页面的配置。在每个 markdown 文件中,可以使用 frontmatter 配置来覆盖站点级别或主题级别的配置选项。此外,还有一些配置选项只能在 frontmatter 中定义。 + +示例用法: + +```md +--- +title: Docs with VitePress +editLink: true +--- +``` + +可以通过 Vue 表达式中的 `$frontmatter` 全局变量访问 frontmatter 数据: + +```md +{{ $frontmatter.title }} +``` + +## title + +- 类型:`string` + +页面的标题。它与 [config.title](./site-config#title) 相同,并且覆盖站点级配置。 + +```yaml +--- +title: VitePress +--- +``` + +## titleTemplate + +- 类型:`string | boolean` + +标题的后缀。它与 [config.titleTemplate](./site-config#titletemplate) 相同,它会覆盖站点级别的配置。 + +```yaml +--- +title: VitePress +titleTemplate: Vite & Vue powered static site generator +--- +``` + +## description + +- 类型:`string` + +页面的描述。它与 [config.description](./site-config#description) 相同,它会覆盖站点级别的配置。 + +```yaml +--- +description: VitePress +--- +``` + +## head + +- 类型:`HeadConfig[]` + +指定要为当前页面注入的额外 head 标签。将附加在站点级配置注入的头部标签之后。 + +```yaml +--- +head: + - - meta + - name: description + content: hello + - - meta + - name: keywords + content: super duper SEO +--- +``` + +```ts +type HeadConfig = + | [string, Record] + | [string, Record, string] +``` + +## 仅默认主题 {#default-theme-only} + +以下 frontmatter 选项仅在使用默认主题时适用。 + +### layout + +- 类型:`doc | home | page` +- 默认值:`doc` + +指定页面的布局。 + +- `doc`——它将默认文档样式应用于 markdown 内容。 +- `home`——“主页”的特殊布局。可以添加额外的选项,例如 `hero` 和 `features`,以快速创建漂亮的落地页。 +- `page`——表现类似于 `doc`,但它不对内容应用任何样式。当想创建一个完全自定义的页面时很有用。 + +```yaml +--- +layout: doc +--- +``` + +### hero + +当 `layout` 设置为 `home` 时,定义主页 hero 部分的内容。更多详细信息:[默认主题:主页](./default-theme-home-page)。 + +### features + +定义当`layout` 设置为 `home` 时要在 features 部分中显示的项目。更多详细信息:[默认主题:主页](./default-theme-home-page)。 + +### navbar + +- 类型:`boolean` +- 默认值:`true` + +是否显示[导航栏](./default-theme-nav)。 + +```yaml +--- +navbar: false +--- +``` + +### sidebar + +- 类型:`boolean` +- 默认值:`true` + +是否显示 [侧边栏](./default-theme-sidebar). + +```yaml +--- +sidebar: false +--- +``` + +### aside + +- 类型:`boolean | 'left'` +- 默认值:`true` + +定义侧边栏组件在 `doc` 布局中的位置。 + +将此值设置为 `false` 可禁用侧边栏容器。\ +将此值设置为 `true` 会将侧边栏渲染到右侧。\ +将此值设置为 `left` 会将侧边栏渲染到左侧。 + +```yaml +--- +aside: false +--- +``` + +### outline + +- 类型:`number | [number, number] | 'deep' | false` +- 默认值:`2` + +大纲中显示的标题级别。它与 [config.themeConfig.outline.level](./default-theme-config#outline) 相同,它会覆盖站点级的配置。 + +### lastUpdated + +- 类型:`boolean | Date` +- 默认值:`true` + +是否在当前页面的页脚中显示[最近更新时间](./default-theme-last-updated)的文本。如果指定了日期时间,则会显示该日期时间而不是上次 git 修改的时间戳。 + +```yaml +--- +lastUpdated: false +--- +``` + +### editLink + +- 类型:`boolean` +- 默认值:`true` + +是否在当前页的页脚显示[编辑链接](./default-theme-edit-link)。 + +```yaml +--- +editLink: false +--- +``` + +### footer + +- 类型:`boolean` +- 默认值:`true` + +是否显示[页脚](./default-theme-footer)。 + +```yaml +--- +footer: false +--- +``` + +### pageClass + +- 类型:`string` + +将额外的类名称添加到特定页面。 + +```yaml +--- +pageClass: custom-page-class +--- +``` + +然后可以在 `.vitepress/theme/custom.css` 文件中自定义该特定页面的样式: + +```css +.custom-page-class { +  /* 特定页面的样式 */ +} +``` diff --git a/docs/zh/reference/runtime-api.md b/docs/zh/reference/runtime-api.md new file mode 100644 index 00000000..6bd232f7 --- /dev/null +++ b/docs/zh/reference/runtime-api.md @@ -0,0 +1,164 @@ +# 运行时 API {#runtime-api} + +VitePress 提供了几个内置的 API 来让你访问应用程序数据。VitePress 还附带了一些可以在全局范围内使用的内置组件。 + +辅助函数可从 `vitepress` 全局导入,通常用于自定义主题 Vue 组件。但是,它们也可以在 `.md` 页面内使用,因为 markdown 文件被编译成 Vue [单文件组件](https://vuejs.org/guide/scaling-up/sfc.html)。 + +以 `use*` 开头的方法表示它是一个 [Vue 3 Composition API](https://vuejs.org/guide/introduction.html#composition-api) 函数(“Composable(可组合)”),只能在 `setup()` 或 ` + + +``` + +## `useRoute` + +返回具有以下类型的当前路由对象: + +```ts +interface Route { + path: string + data: PageData + component: Component | null +} +``` + +## `useRouter` + +返回 VitePress 路由实例,以便可以以编程方式导航到另一个页面。 + +```ts +interface Router { + /** + * 当前路由 + */ + route: Route + /** + * 导航到新的 URL + */ + go: (to?: string) => Promise + /** + * 在路由更改前调用。返回 `false` 表示取消导航 + */ + onBeforeRouteChange?: (to: string) => Awaitable + /** + * 在页面组件加载前(history 状态更新后)调用。返回 `false` 表示取消导航 + */ + onBeforePageLoad?: (to: string) => Awaitable + /** + * 在路由更改后调用 + */ + onAfterRouteChanged?: (to: string) => Awaitable +} +``` + +## `withBase` + +- **Type**: `(path: string) => string` + +将配置的 [`base`](./site-config#base) 追加到给定的 URL 路径。另请参阅 [Base URL](../guide/asset-handling#base-url)。 + +## `` + +`` 组件显示渲染的 markdown 内容。在[创建自己的主题时](../guide/custom-theme)很有用。 + +```vue + +``` + +## `` + +`` 组件仅在客户端渲染其插槽。 + +由于 VitePress 应用程序在生成静态构建时是在 Node.js 中服务器渲染的,因此任何 Vue 使用都必须符合通用代码要求。简而言之,确保仅在 beforeMount 或 mounted 钩子中访问 Browser/DOM API。 + +如果正在使用或演示对 SSR 不友好的组件 (例如,包含自定义指令),可以将它们包装在 `ClientOnly` 组件中。 + +```vue-html + + + +``` + +- 相关文档:[SSR 兼容性](../guide/ssr-compat) + +## `$frontmatter` + +在 Vue 表达式中直接访问当前页面的 [frontmatter](../guide/frontmatter) 数据。 + +```md +--- +title: Hello +--- + +# {{ $frontmatter.title }} +``` + +## `$params` + +在 Vue 表达式中直接访问当前页面的[动态路由参数](../guide/routing#dynamic-routes)。 + +```md +- package name: {{ $params.pkg }} +- version: {{ $params.version }} +``` diff --git a/docs/zh/reference/site-config.md b/docs/zh/reference/site-config.md new file mode 100644 index 00000000..fd5c2c4f --- /dev/null +++ b/docs/zh/reference/site-config.md @@ -0,0 +1,695 @@ +--- +outline: deep +--- + +# 站点配置 {#site-config} + +站点配置可以定义站点的全局设置。应用配置选项适用于每个 VitePress 站点,无论它使用什么主题。例如根目录或站点的标题。 + +## 概览 {#overview} + +### 配置解析 {#config-resolution} + +配置文件总是从 `/.vitepress/config.[ext]` 解析,其中 `` 是 VitePress [项目根目录](../guide/routing#root-and-source-directory),`[ext]` 是支持的文件扩展名之一。开箱即用地支持 TypeScript。支持的扩展名包括 `.js`、`.ts`、`.mjs` 和 `.mts`。 + +建议在配置文件中使用 ES 模块语法。配置文件应该默认导出一个对象: + +```ts +export default { + // 应用级配置选项 + lang: 'en-US', + title: 'VitePress', + description: 'Vite & Vue powered static site generator.', + ... +} +``` + +:::details 异步的动态配置 + +如果需要动态生成配置,也可以默认导出一个函数,例如: + +```ts +import { defineConfig } from 'vitepress' + +export default async () => defineConfig({ + const posts = await (await fetch('https://my-cms.com/blog-posts')).json() + + return { + // 应用级配置选项 + lang: 'en-US', + title: 'VitePress', + description: 'Vite & Vue powered static site generator.', + + // 主题级别配置选项 + themeConfig: { + sidebar: [ + ...posts.map((post) => ({ + text: post.name, + link: `/posts/${post.name}` + })) + ] + } + } +}) +``` + +也可以在最外层使用 `await`。例如: + +```ts +import { defineConfig } from 'vitepress' + +const posts = await (await fetch('https://my-cms.com/blog-posts')).json() + +export default defineConfig({ + // 应用级配置选项 + lang: 'en-US', + title: 'VitePress', + description: 'Vite & Vue powered static site generator.', + + // 主题级别配置选项 + themeConfig: { + sidebar: [ + ...posts.map((post) => ({ + text: post.name, + link: `/posts/${post.name}` + })) + ] + } +}) +``` + +::: + +### 配置智能提示 {#config-intellisense} + +使用 `defineConfig` 辅助函数将为配置选项提供 TypeScript 支持的智能提示。假设 IDE 支持它,那么智能提示在 JavaScript 和 TypeScript 中都将触发。 + +```js +import { defineConfig } from 'vitepress' + +export default defineConfig({ + // ... +}) +``` + +### 主题类型提示 {#typed-theme-config} + +默认情况下,`defineConfig` 辅助函数期望默认主题的主题配置数据类型: + +```ts +import { defineConfig } from 'vitepress' + +export default defineConfig({ + themeConfig: { + // 类型为 `DefaultTheme.Config` + } +}) +``` + +如果使用自定义主题并希望对主题配置进行类型检查,则需要改用 `defineConfigWithTheme`,并通过通用参数传递自定义主题的配置类型: + +```ts +import { defineConfigWithTheme } from 'vitepress' +import type { ThemeConfig } from 'your-theme' + +export default defineConfigWithTheme({ + themeConfig: { + // 类型为 `ThemeConfig` + } +}) +``` + +### Vite、Vue 和 Markdown 配置 + +- **Vite** + + 可以使用 VitePress 配置中的 [vite](#vite) 选项配置底层 Vite 实例。无需创建单独的 Vite 配置文件。 + +- **Vue** + + VitePress 已经包含 Vite 的官方 Vue 插件([@vitejs/plugin-vue](https://github.com/vitejs/vite-plugin-vue))。可以配置 VitePress 中的 [vue](#vue) 选项。 + +- **Markdown** + + 可以使用 VitePress 配置中的 [markdown](#markdown) 选项配置底层的 [Markdown-It](https://github.com/markdown-it/markdown-it) 实例。 + +## 站点元数据 {#site-metadata} + +### title + +- 类型:`string` +- 默认值: `VitePress` +- 每个页面可以通过 [frontmatter](./frontmatter-config#title) 覆盖 + +站点的标题。使用默认主题时,这将显示在导航栏中。 + +它还将用作所有单独页面标题的默认后缀,除非定义了 [`titleTemplate`](#titletemplate)。单个页面的最终标题将是其第一个 `

` 标题的文本内容加上的全局 `title`。例如使用以下配置和页面内容: + +```ts +export default { + title: 'My Awesome Site' +} +``` + +```md +# Hello +``` + +页面标题就是 `Hello | My Awesome Site`. + +### titleTemplate + +- 类型:`string | boolean` +- 每个页面可以通过 [frontmatter](./frontmatter-config#titletemplate) 覆盖 + +允许自定义每个页面的标题后缀或整个标题。例如: + +```ts +export default { + title: 'My Awesome Site', + titleTemplate: 'Custom Suffix' +} +``` + +```md +# Hello +``` + +页面标题就是 `Hello | Custom Suffix`. + +要完全自定义标题的呈现方式,可以在 `titleTemplate` 中使用 `:title` 标识符: + +```ts +export default { + titleTemplate: ':title - Custom Suffix' +} +``` + +这里的 `:title` 将替换为从页面的第一个 `

` 标题推断出的文本。上一个示例页面的标题将是 `Hello - Custom Suffix`。 + +该选项可以设置为 `false` 以禁用标题后缀。 + +### description + +- 类型:`string` +- 默认值: `A VitePress site` +- 每个页面可以通过 [frontmatter](./frontmatter-config#description) 覆盖 + +站点的描述。这将呈现为页面 HTML 中的 `` 标签。 + +```ts +export default { + description: 'A VitePress site' +} +``` + +### head + +- 类型:`HeadConfig[]` +- 默认值: `[]` +- 每个页面可以通过 [frontmatter](./frontmatter-config#head) 添加 + +要在页面 HTML 的 `` 标记中呈现的其他元素。用户添加的标签在结束 `head` 标签之前呈现,在 VitePress 标签之后。 + +```ts +type HeadConfig = + | [string, Record] + | [string, Record, string] +``` + +#### 示例:添加一个图标 {#example-adding-a-favicon} + +```ts +export default { + head: [['link', { rel: 'icon', href: '/favicon.ico' }]] +} // 将 favicon.ico 放在公共目录中,如果设置了 base,则使用 /base/favicon.ico + +/* 渲染成: + +*/ +``` + +#### 示例:添加谷歌字体 {#example-adding-google-fonts} + +```ts +export default { + head: [ + [ + 'link', + { rel: 'preconnect', href: 'https://fonts.googleapis.com' } + ], + [ + 'link', + { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' } + ], + [ + 'link', + { href: 'https://fonts.googleapis.com/css2?family=Roboto&display=swap', rel: 'stylesheet' } + ] + ] +} + +/* 渲染成: + + + +*/ +``` + +#### 示例:添加一个 serviceWorker {#example-registering-a-service-worker} + +```ts +export default { + head: [ + [ + 'script', + { id: 'register-sw' }, + `;(() => { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/sw.js') + } + })()` + ] + ] +} + +/* 渲染成: + +*/ +``` + +#### 示例:使用谷歌分析 {#example-using-google-analytics} + +```ts +export default { + head: [ + [ + 'script', + { async: '', src: 'https://www.googletagmanager.com/gtag/js?id=TAG_ID' } + ], + [ + 'script', + {}, + `window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + gtag('js', new Date()); + gtag('config', 'TAG_ID');` + ] + ] +} + +/* 渲染成: + + +*/ +``` + +### lang + +- 类型:`string` +- 默认值: `en-US` + +站点的 lang 属性。这将呈现为页面 HTML 中的 `` 标签。 + +```ts +export default { + lang: 'en-US' +} +``` + +### base + +- 类型:`string` +- 默认值: `/` + +站点将部署到的 base URL。如果计划在子路径(例如 GitHub 页面)下部署站点,则需要设置此项。如果计划将站点部署到 `https://foo.github.io/bar/`,那么应该将 `base` 设置为 `“/bar/”`。它应该始终以 `/`开头和结尾。 + +base 会自动添加到其他选项中以 `/` 开头的所有 URL 前面,因此只需指定一次。 + +```ts +export default { + base: '/base/' +} +``` + +## 路由 {#routing} + +### cleanUrls + +- 类型:`boolean` +- 默认值: `false` + +当设置为 `true` 时,VitePress 将从 URL 中删除 `.html` 后缀。另请参阅[生成简洁的 URL](../guide/routing#generating-clean-url)。 + +::: warning 需要服务器支持 +要启用此功能,可能需要在托管平台上进行额外配置。要使其正常工作,服务器必须能够在**不重定向的情况下**访问 `/foo` 时提供 `/foo.html`。 +::: + +### rewrites + +- 类型:`Record` + +自定义目录 <-> URL 映射。详细信息请参阅[路由:路由重写](../guide/routing#route-rewrites)。 + +```ts +export default { + rewrites: { + 'source/:page': 'destination/:page' + } +} +``` + +## 构建 {#build} + +### srcDir + +- 类型:`string` +- 默认值: `.` + +markdown 页面的目录,相对于项目根目录。另请参阅[根目录和源目录](../guide/routing#root-and-source-directory)。 + +```ts +export default { + srcDir: './src' +} +``` + +### srcExclude + +- 类型:`string` +- 默认值: `undefined` + +用于匹配应作为源内容输出的 markdown 文件的 [全局模式](https://github.com/mrmlnc/fast-glob#pattern-syntax)。 + +```ts +export default { + srcExclude: ['**/README.md', '**/TODO.md'] +} +``` + +### outDir + +- 类型:`string` +- 默认值: `./.vitepress/dist` + +项目的构建输出位置,相对于[项目根目录](../guide/routing#root-and-source-directory)。 + +```ts +export default { + outDir: '../public' +} +``` + +### assetsDir + +- 类型:`string` +- 默认值: `assets` + +指定放置生成的静态资源的目录。该路径应位于 [`outDir`](#outdir) 内,并相对于它进行解析。 + +```ts +export default { + assetsDir: 'static' +} +``` + +### cacheDir + +- 类型:`string` +- 默认值: `./.vitepress/cache` + +缓存文件的目录,相对于[项目根目录](../guide/routing#root-and-source-directory)。另请参阅:[cacheDir](https://vitejs.dev/config/shared-options.html#cachedir)。 + +```ts +export default { + cacheDir: './.vitepress/.vite' +} +``` + +### ignoreDeadLinks + +- 类型:`boolean | 'localhostLinks' | (string | RegExp | ((link: string) => boolean))[]` +- 默认值: `false` + +当设置为 `true` 时,VitePress 不会因为死链而导致构建失败。 + +当设置为 `'localhostLinks'` ,出现死链时构建将失败,但不会检查 `localhost` 链接。 + +```ts +export default { + ignoreDeadLinks: true +} +``` + +它也可以是一组精确的 url 字符串、正则表达式模式或自定义过滤函数。 + +```ts +export default { + ignoreDeadLinks: [ + // 忽略精确网址 "/playground" + '/playground', + // 忽略所有 localhost 链接 + /^https?:\/\/localhost/, + // 忽略所有包含 "/repl/" 的链接 + /\/repl\//, + // 自定义函数,忽略所有包含 "ignore "的链接 + (url) => { + return url.toLowerCase().includes('ignore') + } + ] +} +``` + +### mpa + +- 类型:`boolean` +- 默认值: `false` + +设置为 `true` 时,生产应用程序将在 [MPA 模式](../guide/mpa-mode)下构建。MPA 模式默认提供 零 JavaScript 支持,代价是禁用客户端导航,并且需要明确选择加入才能进行交互。 + +## 主题 {#theming} + +### appearance + +- 类型:`boolean | 'dark' | 'force-dark' | import('@vueuse/core').UseDarkOptions` +- 默认值: `true` + +是否启用深色模式(通过将 `.dark` 类添加到 `` 元素)。 + +- 如果该选项设置为 `true`,则默认主题将由用户的首选配色方案决定。 +- 如果该选项设置为 `dark`,则默认情况下主题将是深色的,除非用户手动切换它。 +- 如果该选项设置为 `false`,用户将无法切换主题。 + +此选项注入一个内联脚本,使用 `vitepress-theme-appearance` key 从本地存储恢复用户设置。这确保在呈现页面之前应用 `.dark` 类以避免闪烁。 + +`appearance.initialValue` 只能是 `'dark' | undefined`。 不支持 Refs 或 getters。 + +### lastUpdated + +- 类型:`boolean` +- 默认值: `false` + +是否使用 Git 获取每个页面的最后更新时间戳。时间戳将包含在每个页面的页面数据中,可通过 [`useData`](./runtime-api#usedata) 访问。 + +使用默认主题时,启用此选项将显示每个页面的最后更新时间。可以通过 [`themeConfig.lastUpdatedText`](./default-theme-config#lastupdatedtext) 选项自定义文本。 + +## 自定义 {#customization} + +### markdown + +- 类型:`MarkdownOption` + +配置 Markdown 解析器选项。VitePress 使用 [Markdown-it](https://github.com/markdown-it/markdown-it) 作为解析器,使用[Shikiji](https://github.com/antfu/shikiji) ([Shiki](https://shiki.matsu.io/) 的改进版本) 来高亮不同语言语法。在此选项中,可以传递各种 Markdown 相关选项以满足的需要。 + +```js +export default { + markdown: {...} +} +``` + +查看[类型声明和 jsdocs](https://github.com/vuejs/vitepress/blob/main/src/node/markdown/markdown.ts) 以获得所有可配置的选项。 + +### vite + +- 类型:`import('vite').UserConfig` + +将原始 [Vite 配置](https://vitejs.dev/config/)传递给内部 Vite 开发服务器 / bundler。 + +```js +export default { + vite: { + // Vite 配置选项 + } +} +``` + +### vue + +- 类型:`import('@vitejs/plugin-vue').Options` + +将原始的 [@vitejs/plugin-vue 选项](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue#options)传递给内部插件实例。 + +```js +export default { + vue: { + // @vitejs/plugin-vue 选项 + } +} +``` + +## 构建钩子 {#build-hooks} + +VitePress 构建钩子允许向站点添加新功能和行为: + +- Sitemap +- Search Indexing +- PWA +- Teleport + +### buildEnd + +- 类型:`(siteConfig: SiteConfig) => Awaitable` + +`buildEnd` 是一个构建 CLI 钩子,它将在构建(SSG)完成后但在 VitePress CLI 进程退出之前运行。 + +```ts +export default { + async buildEnd(siteConfig) { + // ... + } +} +``` + +### postRender + +- 类型:`(context: SSGContext) => Awaitable` + + `postRender` 是一个构建钩子,在 SSG 渲染完成时调用。它将允许在 SSG 期间处理传递的内容。 + +```ts +export default { + async postRender(context) { + // ... + } +} +``` + +```ts +interface SSGContext { + content: string + teleports?: Record + [key: string]: any +} +``` + +### transformHead + +- 类型:`(context: TransformContext) => Awaitable` + +`transformHead` 是一个构建钩子,用于在生成每个页面之前转换 head。它将允许添加无法静态添加到 VitePress 配置中的 head entries。只需要返回额外的 entries,它们将自动与现有 entries 合并。 + +::: warning +不要改变 `context` 中的任何东西。 +::: + +```ts +export default { + async transformHead(context) { + // ... + } +} +``` + +```ts +interface TransformContext { + page: string // 例如 index.md (相对于 srcDir) + assets: string[] // 所有非 js/css 资源均作为完全解析的公共 URL + siteConfig: SiteConfig + siteData: SiteData + pageData: PageData + title: string + description: string + head: HeadConfig[] + content: string +} +``` + +请注意,仅在静态生成站点时才会调用此挂钩。在开发期间不会调用它。如果需要在开发期间添加动态头条目,可以使用 [`transformPageData`](#transformpagedata) 钩子来替代: + +```ts +export default { + transformPageData(pageData) { + pageData.frontmatter.head ??= [] + pageData.frontmatter.head.push([ + 'meta', + { + name: 'og:title', + content: + pageData.frontmatter.layout === 'home' + ? `VitePress` + : `${pageData.title} | VitePress` + } + ]) + } +} +``` + +### transformHtml + +- 类型:`(code: string, id: string, context: TransformContext) => Awaitable` + +`transformHtml` 是一个构建钩子,用于在保存到磁盘之前转换每个页面的内容。 + +::: warning +不要改变 `context` 中的任何东西。另外,修改 html 内容可能会导致运行时出现激活问题。 +::: + +```ts +export default { + async transformHtml(code, id, context) { + // ... + } +} +``` + +### transformPageData + +- 类型:`(pageData: PageData, context: TransformPageContext) => Awaitable | { [key: string]: any } | void>` + +`transformPageData` 是一个钩子,用于转换每个页面的 `pageData`。可以直接改变 `pageData` 或返回将合并到 `PageData` 中的更改值。 + +::: warning +不要改变 `context` 中的任何东西。请注意,这可能会影响开发服务器的性能,特别是当在钩子中有一些网络请求或大量计算(例如生成图像)时。可以通过判断 `process.env.NODE_ENV === 'production'` 匹配符合条件的情况。 +::: + +```ts +export default { + async transformPageData(pageData, { siteConfig }) { + pageData.contributors = await getPageContributors(pageData.relativePath) + } + + // 或返回要合并的数据 + async transformPageData(pageData, { siteConfig }) { + return { + contributors: await getPageContributors(pageData.relativePath) + } + } +} +``` + +```ts +interface TransformPageContext { + siteConfig: SiteConfig +} +``` diff --git a/package.json b/package.json index b875fdb5..19889419 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "vitepress", - "version": "1.0.0-rc.34", + "version": "1.0.0-rc.35", "description": "Vite & Vue powered static site generator", "type": "module", - "packageManager": "pnpm@8.13.1", + "packageManager": "pnpm@8.14.0", "main": "dist/node/index.js", "types": "types/index.d.ts", "exports": { @@ -100,12 +100,11 @@ "focus-trap": "^7.5.4", "mark.js": "8.11.1", "minisearch": "^6.3.0", - "mrmime": "^2.0.0", - "shikiji": "^0.9.15", - "shikiji-core": "^0.9.15", - "shikiji-transformers": "^0.9.15", + "shikiji": "^0.9.17", + "shikiji-core": "^0.9.17", + "shikiji-transformers": "^0.9.17", "vite": "^5.0.10", - "vue": "^3.4.3" + "vue": "^3.4.4" }, "peerDependencies": { "markdown-it-mathjax3": "^4.3.2", @@ -148,7 +147,7 @@ "@types/node": "^20.10.6", "@types/postcss-prefix-selector": "^1.16.3", "@types/prompts": "^2.4.9", - "@vue/shared": "^3.4.3", + "@vue/shared": "^3.4.4", "chokidar": "^3.5.3", "compression": "^1.7.4", "conventional-changelog-cli": "^4.1.0", @@ -195,7 +194,7 @@ "sitemap": "^7.1.1", "supports-color": "^9.4.0", "typescript": "^5.3.3", - "vitest": "^1.1.0", + "vitest": "^1.1.1", "vue-tsc": "^1.8.27", "wait-on": "^7.2.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13c8f4d2..d7d31eb0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,16 +22,16 @@ importers: version: 13.0.7 '@vitejs/plugin-vue': specifier: ^5.0.2 - version: 5.0.2(vite@5.0.10)(vue@3.4.3) + version: 5.0.2(vite@5.0.10)(vue@3.4.4) '@vue/devtools-api': specifier: ^6.5.1 version: 6.5.1 '@vueuse/core': specifier: ^10.7.1 - version: 10.7.1(vue@3.4.3) + version: 10.7.1(vue@3.4.4) '@vueuse/integrations': specifier: ^10.7.1 - version: 10.7.1(focus-trap@7.5.4)(vue@3.4.3) + version: 10.7.1(focus-trap@7.5.4)(vue@3.4.4) focus-trap: specifier: ^7.5.4 version: 7.5.4 @@ -41,24 +41,21 @@ importers: minisearch: specifier: ^6.3.0 version: 6.3.0 - mrmime: - specifier: ^2.0.0 - version: 2.0.0 shikiji: - specifier: ^0.9.15 - version: 0.9.15 + specifier: ^0.9.17 + version: 0.9.17 shikiji-core: - specifier: ^0.9.15 - version: 0.9.15 + specifier: ^0.9.17 + version: 0.9.17 shikiji-transformers: - specifier: ^0.9.15 - version: 0.9.15 + specifier: ^0.9.17 + version: 0.9.17 vite: specifier: ^5.0.10 version: 5.0.10(@types/node@20.10.6) vue: - specifier: ^3.4.3 - version: 3.4.3(typescript@5.3.3) + specifier: ^3.4.4 + version: 3.4.4(typescript@5.3.3) devDependencies: '@clack/prompts': specifier: ^0.7.0 @@ -145,8 +142,8 @@ importers: specifier: ^2.4.9 version: 2.4.9 '@vue/shared': - specifier: ^3.4.3 - version: 3.4.3 + specifier: ^3.4.4 + version: 3.4.4 chokidar: specifier: ^3.5.3 version: 3.5.3 @@ -286,8 +283,8 @@ importers: specifier: ^5.3.3 version: 5.3.3 vitest: - specifier: ^1.1.0 - version: 1.1.0(@types/node@20.10.6)(supports-color@9.4.0) + specifier: ^1.1.1 + version: 1.1.1(@types/node@20.10.6)(supports-color@9.4.0) vue-tsc: specifier: ^1.8.27 version: 1.8.27(typescript@5.3.3) @@ -1293,7 +1290,7 @@ packages: resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} dev: false - /@vitejs/plugin-vue@5.0.2(vite@5.0.10)(vue@3.4.3): + /@vitejs/plugin-vue@5.0.2(vite@5.0.10)(vue@3.4.4): resolution: {integrity: sha512-kEjJHrLb5ePBvjD0SPZwJlw1QTRcjjCA9sB5VyfonoXVBxTS7TMnqL6EkLt1Eu61RDeiuZ/WN9Hf6PxXhPI2uA==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: @@ -1301,41 +1298,41 @@ packages: vue: ^3.2.25 dependencies: vite: 5.0.10(@types/node@20.10.6) - vue: 3.4.3(typescript@5.3.3) + vue: 3.4.4(typescript@5.3.3) dev: false - /@vitest/expect@1.1.0: - resolution: {integrity: sha512-9IE2WWkcJo2BR9eqtY5MIo3TPmS50Pnwpm66A6neb2hvk/QSLfPXBz2qdiwUOQkwyFuuXEUj5380CbwfzW4+/w==} + /@vitest/expect@1.1.1: + resolution: {integrity: sha512-Qpw01C2Hyb3085jBkOJLQ7HRX0Ncnh2qV4p+xWmmhcIUlMykUF69zsnZ1vPmAjZpomw9+5tWEGOQ0GTfR8U+kA==} dependencies: - '@vitest/spy': 1.1.0 - '@vitest/utils': 1.1.0 + '@vitest/spy': 1.1.1 + '@vitest/utils': 1.1.1 chai: 4.3.10 dev: true - /@vitest/runner@1.1.0: - resolution: {integrity: sha512-zdNLJ00pm5z/uhbWF6aeIJCGMSyTyWImy3Fcp9piRGvueERFlQFbUwCpzVce79OLm2UHk9iwaMSOaU9jVHgNVw==} + /@vitest/runner@1.1.1: + resolution: {integrity: sha512-8HokyJo1SnSi3uPFKfWm/Oq1qDwLC4QDcVsqpXIXwsRPAg3gIDh8EbZ1ri8cmQkBxdOu62aOF9B4xcqJhvt4xQ==} dependencies: - '@vitest/utils': 1.1.0 + '@vitest/utils': 1.1.1 p-limit: 5.0.0 pathe: 1.1.1 dev: true - /@vitest/snapshot@1.1.0: - resolution: {integrity: sha512-5O/wyZg09V5qmNmAlUgCBqflvn2ylgsWJRRuPrnHEfDNT6tQpQ8O1isNGgo+VxofISHqz961SG3iVvt3SPK/QQ==} + /@vitest/snapshot@1.1.1: + resolution: {integrity: sha512-WnMHjv4VdHLbFGgCdVVvyRkRPnOKN75JJg+LLTdr6ah7YnL75W+7CTIMdzPEPzaDxA8r5yvSVlc1d8lH3yE28w==} dependencies: magic-string: 0.30.5 pathe: 1.1.1 pretty-format: 29.7.0 dev: true - /@vitest/spy@1.1.0: - resolution: {integrity: sha512-sNOVSU/GE+7+P76qYo+VXdXhXffzWZcYIPQfmkiRxaNCSPiLANvQx5Mx6ZURJ/ndtEkUJEpvKLXqAYTKEY+lTg==} + /@vitest/spy@1.1.1: + resolution: {integrity: sha512-hDU2KkOTfFp4WFFPWwHFauddwcKuGQ7gF6Un/ZZkCogoAiTMN7/7YKvUDbywPZZ754iCQGjdUmXN3t4k0jm1IQ==} dependencies: tinyspy: 2.2.0 dev: true - /@vitest/utils@1.1.0: - resolution: {integrity: sha512-z+s510fKmYz4Y41XhNs3vcuFTFhcij2YF7F8VQfMEYAAUfqQh0Zfg7+w9xdgFGhPf3tX3TicAe+8BDITk6ampQ==} + /@vitest/utils@1.1.1: + resolution: {integrity: sha512-E9LedH093vST/JuBSyHLFMpxJKW3dLhe/flUSPFedoyj4wKiFX7Jm8gYLtOIiin59dgrssfmFv0BJ1u8P/LC/A==} dependencies: diff-sequences: 29.6.3 loupe: 2.3.7 @@ -1361,40 +1358,40 @@ packages: path-browserify: 1.0.1 dev: true - /@vue/compiler-core@3.4.3: - resolution: {integrity: sha512-u8jzgFg0EDtSrb/hG53Wwh1bAOQFtc1ZCegBpA/glyvTlgHl+tq13o1zvRfLbegYUw/E4mSTGOiCnAJ9SJ+lsg==} + /@vue/compiler-core@3.4.4: + resolution: {integrity: sha512-U5AdCN+6skzh2bSJrkMj2KZsVkUpgK8/XlxjSRYQZhNPcvt9/kmgIMpFEiTyK+Dz5E1J+8o8//BEIX+bakgVSw==} dependencies: '@babel/parser': 7.23.6 - '@vue/shared': 3.4.3 + '@vue/shared': 3.4.4 entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.0.2 - /@vue/compiler-dom@3.4.3: - resolution: {integrity: sha512-oGF1E9/htI6JWj/lTJgr6UgxNCtNHbM6xKVreBWeZL9QhRGABRVoWGAzxmtBfSOd+w0Zi5BY0Es/tlJrN6WgEg==} + /@vue/compiler-dom@3.4.4: + resolution: {integrity: sha512-iSwkdDULCN+Vr8z6uwdlL044GJ/nUmECxP9vu7MzEs4Qma0FwDLYvnvRcyO0ZITuu3Os4FptGUDnhi1kOLSaGw==} dependencies: - '@vue/compiler-core': 3.4.3 - '@vue/shared': 3.4.3 + '@vue/compiler-core': 3.4.4 + '@vue/shared': 3.4.4 - /@vue/compiler-sfc@3.4.3: - resolution: {integrity: sha512-NuJqb5is9I4uzv316VRUDYgIlPZCG8D+ARt5P4t5UDShIHKL25J3TGZAUryY/Aiy0DsY7srJnZL5ryB6DD63Zw==} + /@vue/compiler-sfc@3.4.4: + resolution: {integrity: sha512-OTFcU6vUxUNHBcarzkp4g6d25nvcmDvFDzPRvSrIsByFFPRYN+y3b+j9HxYwt6nlWvGyFCe0roeJdJlfYxbCBg==} dependencies: '@babel/parser': 7.23.6 - '@vue/compiler-core': 3.4.3 - '@vue/compiler-dom': 3.4.3 - '@vue/compiler-ssr': 3.4.3 - '@vue/shared': 3.4.3 + '@vue/compiler-core': 3.4.4 + '@vue/compiler-dom': 3.4.4 + '@vue/compiler-ssr': 3.4.4 + '@vue/shared': 3.4.4 estree-walker: 2.0.2 magic-string: 0.30.5 postcss: 8.4.32 source-map-js: 1.0.2 dev: false - /@vue/compiler-ssr@3.4.3: - resolution: {integrity: sha512-wnYQtMBkeFSxgSSQbYGQeXPhQacQiog2c6AlvMldQH6DB+gSXK/0F6DVXAJfEiuBSgBhUc8dwrrG5JQcqwalsA==} + /@vue/compiler-ssr@3.4.4: + resolution: {integrity: sha512-1DU9DflSSQlx/M61GEBN+NbT/anUki2ooDo9IXfTckCeKA/2IKNhY8KbG3x6zkd3KGrxzteC7de6QL88vEb41Q==} dependencies: - '@vue/compiler-dom': 3.4.3 - '@vue/shared': 3.4.3 + '@vue/compiler-dom': 3.4.4 + '@vue/shared': 3.4.4 dev: false /@vue/devtools-api@6.5.1: @@ -1411,8 +1408,8 @@ packages: dependencies: '@volar/language-core': 1.11.1 '@volar/source-map': 1.11.1 - '@vue/compiler-dom': 3.4.3 - '@vue/shared': 3.4.3 + '@vue/compiler-dom': 3.4.4 + '@vue/shared': 3.4.4 computeds: 0.0.1 minimatch: 9.0.3 muggle-string: 0.3.1 @@ -1421,53 +1418,53 @@ packages: vue-template-compiler: 2.7.16 dev: true - /@vue/reactivity@3.4.3: - resolution: {integrity: sha512-q5f9HLDU+5aBKizXHAx0w4whkIANs1Muiq9R5YXm0HtorSlflqv9u/ohaMxuuhHWCji4xqpQ1eL04WvmAmGnFg==} + /@vue/reactivity@3.4.4: + resolution: {integrity: sha512-DFsuJBf6sfhd5SYzJmcBTUG9+EKqjF31Gsk1NJtnpJm9liSZ806XwGJUeNBVQIanax7ODV7Lmk/k17BgxXNuTg==} dependencies: - '@vue/shared': 3.4.3 + '@vue/shared': 3.4.4 dev: false - /@vue/runtime-core@3.4.3: - resolution: {integrity: sha512-C1r6QhB1qY7D591RCSFhMULyzL9CuyrGc+3PpB0h7dU4Qqw6GNyo4BNFjHZVvsWncrUlKX3DIKg0Y7rNNr06NQ==} + /@vue/runtime-core@3.4.4: + resolution: {integrity: sha512-zWWwNQAj5JdxrmOA1xegJm+c4VtyIbDEKgQjSb4va5v7gGTCh0ZjvLI+htGFdVXaO9bs2J3C81p5p+6jrPK8Bw==} dependencies: - '@vue/reactivity': 3.4.3 - '@vue/shared': 3.4.3 + '@vue/reactivity': 3.4.4 + '@vue/shared': 3.4.4 dev: false - /@vue/runtime-dom@3.4.3: - resolution: {integrity: sha512-wrsprg7An5Ec+EhPngWdPuzkp0BEUxAKaQtN9dPU/iZctPyD9aaXmVtehPJerdQxQale6gEnhpnfywNw3zOv2A==} + /@vue/runtime-dom@3.4.4: + resolution: {integrity: sha512-Nlh2ap1J/eJQ6R0g+AIRyGNwpTJQACN0dk8I8FRLH8Ev11DSvfcPOpn4+Kbg5xAMcuq0cHB8zFYxVrOgETrrvg==} dependencies: - '@vue/runtime-core': 3.4.3 - '@vue/shared': 3.4.3 + '@vue/runtime-core': 3.4.4 + '@vue/shared': 3.4.4 csstype: 3.1.3 dev: false - /@vue/server-renderer@3.4.3(vue@3.4.3): - resolution: {integrity: sha512-BUxt8oVGMKKsqSkM1uU3d3Houyfy4WAc2SpSQRebNd+XJGATVkW/rO129jkyL+kpB/2VRKzE63zwf5RtJ3XuZw==} + /@vue/server-renderer@3.4.4(vue@3.4.4): + resolution: {integrity: sha512-+AjoiKcC41k7SMJBYkDO9xs79/Of8DiThS9mH5l2MK+EY0to3psI0k+sElvVqQvsoZTjHMEuMz0AEgvm2T+CwA==} peerDependencies: - vue: 3.4.3 + vue: 3.4.4 dependencies: - '@vue/compiler-ssr': 3.4.3 - '@vue/shared': 3.4.3 - vue: 3.4.3(typescript@5.3.3) + '@vue/compiler-ssr': 3.4.4 + '@vue/shared': 3.4.4 + vue: 3.4.4(typescript@5.3.3) dev: false - /@vue/shared@3.4.3: - resolution: {integrity: sha512-rIwlkkP1n4uKrRzivAKPZIEkHiuwY5mmhMJ2nZKCBLz8lTUlE73rQh4n1OnnMurXt1vcUNyH4ZPfdh8QweTjpQ==} + /@vue/shared@3.4.4: + resolution: {integrity: sha512-abSgiVRhfjfl3JALR/cSuBl74hGJ3SePgf1mKzodf1eMWLwHZbfEGxT2cNJSsNiw44jEgrO7bNkhchaWA7RwNw==} - /@vueuse/core@10.7.1(vue@3.4.3): + /@vueuse/core@10.7.1(vue@3.4.4): resolution: {integrity: sha512-74mWHlaesJSWGp1ihg76vAnfVq9NTv1YT0SYhAQ6zwFNdBkkP+CKKJmVOEHcdSnLXCXYiL5e7MaewblfiYLP7g==} dependencies: '@types/web-bluetooth': 0.0.20 '@vueuse/metadata': 10.7.1 - '@vueuse/shared': 10.7.1(vue@3.4.3) - vue-demi: 0.14.6(vue@3.4.3) + '@vueuse/shared': 10.7.1(vue@3.4.4) + vue-demi: 0.14.6(vue@3.4.4) transitivePeerDependencies: - '@vue/composition-api' - vue dev: false - /@vueuse/integrations@10.7.1(focus-trap@7.5.4)(vue@3.4.3): + /@vueuse/integrations@10.7.1(focus-trap@7.5.4)(vue@3.4.4): resolution: {integrity: sha512-cKo5LEeKVHdBRBtMTOrDPdR0YNtrmN9IBfdcnY2P3m5LHVrsD0xiHUtAH1WKjHQRIErZG6rJUa6GA4tWZt89Og==} peerDependencies: async-validator: '*' @@ -1508,10 +1505,10 @@ packages: universal-cookie: optional: true dependencies: - '@vueuse/core': 10.7.1(vue@3.4.3) - '@vueuse/shared': 10.7.1(vue@3.4.3) + '@vueuse/core': 10.7.1(vue@3.4.4) + '@vueuse/shared': 10.7.1(vue@3.4.4) focus-trap: 7.5.4 - vue-demi: 0.14.6(vue@3.4.3) + vue-demi: 0.14.6(vue@3.4.4) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -1521,10 +1518,10 @@ packages: resolution: {integrity: sha512-jX8MbX5UX067DYVsbtrmKn6eG6KMcXxLRLlurGkZku5ZYT3vxgBjui2zajvUZ18QLIjrgBkFRsu7CqTAg18QFw==} dev: false - /@vueuse/shared@10.7.1(vue@3.4.3): + /@vueuse/shared@10.7.1(vue@3.4.4): resolution: {integrity: sha512-v0jbRR31LSgRY/C5i5X279A/WQjD6/JsMzGa+eqt658oJ75IvQXAeONmwvEMrvJQKnRElq/frzBR7fhmWY5uLw==} dependencies: - vue-demi: 0.14.6(vue@3.4.3) + vue-demi: 0.14.6(vue@3.4.4) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -1688,7 +1685,7 @@ packages: /axios@1.6.3(debug@4.3.4): resolution: {integrity: sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==} dependencies: - follow-redirects: 1.15.3(debug@4.3.4) + follow-redirects: 1.15.4(debug@4.3.4) form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -2437,8 +2434,8 @@ packages: tabbable: 6.2.0 dev: false - /follow-redirects@1.15.3(debug@4.3.4): - resolution: {integrity: sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==} + /follow-redirects@1.15.4(debug@4.3.4): + resolution: {integrity: sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -3338,6 +3335,7 @@ packages: /mrmime@2.0.0: resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} engines: {node: '>=10'} + dev: true /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -3982,20 +3980,20 @@ packages: resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} dev: true - /shikiji-core@0.9.15: - resolution: {integrity: sha512-7hqIcUKS15OMs/61Qp2GvO1fSajBB36bDqi8vexIg5kp80V6v6SGtBrlq+nLlo7erMG2d1kvIuTIq1bwKI6fEg==} + /shikiji-core@0.9.17: + resolution: {integrity: sha512-r1FWTXk6SO2aYqfWgcsJ11MuVQ1ymPSdXzJjK7q8EXuyqu8yc2N5qrQy5+BL6gTVOaF4yLjbxFjF+KTRM1Sp8Q==} dev: false - /shikiji-transformers@0.9.15: - resolution: {integrity: sha512-k0sQ6tX26/cdb8QV9CCwwr7QjRp6/AVP9C0oNIXNld3of+xCrpf74kD74piybG6vMfzBoHGsz/s60RVBJOUaYQ==} + /shikiji-transformers@0.9.17: + resolution: {integrity: sha512-2CCG9qSLS6Bn/jbeUTEuvC6YSuP8gm8VyX5VjmCvDKyCPGhlLJbH1k/kg9wfRt7cJqpYjhdMDgT5rkdYrOZnsA==} dependencies: - shikiji: 0.9.15 + shikiji: 0.9.17 dev: false - /shikiji@0.9.15: - resolution: {integrity: sha512-+inN4cN+nY7b0uCPOiqFHAk+cn2DEdM3AIQgPhAV7QKqhww/o7OGS5xvLh3SNnjke9C/HispALqGOQGYHVq7KQ==} + /shikiji@0.9.17: + resolution: {integrity: sha512-0z/1NfkhBkm3ijrfFeHg3G9yDNuHhXdAGbQm7tRxj4WQ5z2y0XDbnagFyKyuV2ebCTS1Mwy1I3n0Fzcc/4xdmw==} dependencies: - shikiji-core: 0.9.15 + shikiji-core: 0.9.17 dev: false /side-channel@1.0.4: @@ -4432,8 +4430,8 @@ packages: engines: {node: '>= 0.8'} dev: true - /vite-node@1.1.0(@types/node@20.10.6)(supports-color@9.4.0): - resolution: {integrity: sha512-jV48DDUxGLEBdHCQvxL1mEh7+naVy+nhUUUaPAZLd3FJgXuxQiewHcfeZebbJ6onDqNGkP4r3MhQ342PRlG81Q==} + /vite-node@1.1.1(@types/node@20.10.6)(supports-color@9.4.0): + resolution: {integrity: sha512-2bGE5w4jvym5v8llF6Gu1oBrmImoNSs4WmRVcavnG2me6+8UQntTqLiAMFyiAobp+ZXhj5ZFhI7SmLiFr/jrow==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true dependencies: @@ -4488,8 +4486,8 @@ packages: optionalDependencies: fsevents: 2.3.3 - /vitest@1.1.0(@types/node@20.10.6)(supports-color@9.4.0): - resolution: {integrity: sha512-oDFiCrw7dd3Jf06HoMtSRARivvyjHJaTxikFxuqJjO76U436PqlVw1uLn7a8OSPrhSfMGVaRakKpA2lePdw79A==} + /vitest@1.1.1(@types/node@20.10.6)(supports-color@9.4.0): + resolution: {integrity: sha512-Ry2qs4UOu/KjpXVfOCfQkTnwSXYGrqTbBZxw6reIYEFjSy1QUARRg5pxiI5BEXy+kBVntxUYNMlq4Co+2vD3fQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -4514,11 +4512,11 @@ packages: optional: true dependencies: '@types/node': 20.10.6 - '@vitest/expect': 1.1.0 - '@vitest/runner': 1.1.0 - '@vitest/snapshot': 1.1.0 - '@vitest/spy': 1.1.0 - '@vitest/utils': 1.1.0 + '@vitest/expect': 1.1.1 + '@vitest/runner': 1.1.1 + '@vitest/snapshot': 1.1.1 + '@vitest/spy': 1.1.1 + '@vitest/utils': 1.1.1 acorn-walk: 8.3.1 cac: 6.7.14 chai: 4.3.10 @@ -4533,7 +4531,7 @@ packages: tinybench: 2.5.1 tinypool: 0.8.1 vite: 5.0.10(@types/node@20.10.6) - vite-node: 1.1.0(@types/node@20.10.6)(supports-color@9.4.0) + vite-node: 1.1.1(@types/node@20.10.6)(supports-color@9.4.0) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -4545,7 +4543,7 @@ packages: - terser dev: true - /vue-demi@0.14.6(vue@3.4.3): + /vue-demi@0.14.6(vue@3.4.4): resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==} engines: {node: '>=12'} hasBin: true @@ -4557,7 +4555,7 @@ packages: '@vue/composition-api': optional: true dependencies: - vue: 3.4.3(typescript@5.3.3) + vue: 3.4.4(typescript@5.3.3) dev: false /vue-template-compiler@2.7.16: @@ -4579,19 +4577,19 @@ packages: typescript: 5.3.3 dev: true - /vue@3.4.3(typescript@5.3.3): - resolution: {integrity: sha512-GjN+culMAGv/mUbkIv8zMKItno8npcj5gWlXkSxf1SPTQf8eJ4A+YfHIvQFyL1IfuJcMl3soA7SmN1fRxbf/wA==} + /vue@3.4.4(typescript@5.3.3): + resolution: {integrity: sha512-suZXgDVT8lRNhKmxdkwOsR0oyUi8is7mtqI18qW97JLoyorEbE9B2Sb4Ws/mR/+0AgA/JUtsv1ytlRSH3/pDIA==} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@vue/compiler-dom': 3.4.3 - '@vue/compiler-sfc': 3.4.3 - '@vue/runtime-dom': 3.4.3 - '@vue/server-renderer': 3.4.3(vue@3.4.3) - '@vue/shared': 3.4.3 + '@vue/compiler-dom': 3.4.4 + '@vue/compiler-sfc': 3.4.4 + '@vue/runtime-dom': 3.4.4 + '@vue/server-renderer': 3.4.4(vue@3.4.4) + '@vue/shared': 3.4.4 typescript: 5.3.3 dev: false diff --git a/src/client/app/data.ts b/src/client/app/data.ts index ccca8123..c75f79e7 100644 --- a/src/client/app/data.ts +++ b/src/client/app/data.ts @@ -44,9 +44,9 @@ export interface VitePressData { title: Ref description: Ref lang: Ref - isDark: Ref dir: Ref localeIndex: Ref + isDark: Ref } // site data is a singleton @@ -89,14 +89,12 @@ export function initData(route: Route): VitePressData { frontmatter: computed(() => route.data.frontmatter), params: computed(() => route.data.params), lang: computed(() => site.value.lang), - dir: computed(() => route.data.frontmatter.dir || site.value.dir || 'ltr'), + dir: computed(() => route.data.frontmatter.dir || site.value.dir), localeIndex: computed(() => site.value.localeIndex || 'root'), - title: computed(() => { - return createTitle(site.value, route.data) - }), - description: computed(() => { - return route.data.description || site.value.description - }), + title: computed(() => createTitle(site.value, route.data)), + description: computed( + () => route.data.description || site.value.description + ), isDark } } diff --git a/src/client/app/index.ts b/src/client/app/index.ts index e653ef96..6ad9955e 100644 --- a/src/client/app/index.ts +++ b/src/client/app/index.ts @@ -39,13 +39,13 @@ const Theme = resolveThemeExtends(RawTheme) const VitePressApp = defineComponent({ name: 'VitePressApp', setup() { - const { site } = useData() + const { site, lang, dir } = useData() // change the language on the HTML element based on the current lang onMounted(() => { watchEffect(() => { - document.documentElement.lang = site.value.lang - document.documentElement.dir = site.value.dir + document.documentElement.lang = lang.value + document.documentElement.dir = dir.value }) }) @@ -138,7 +138,11 @@ function newRouter(): Router { pageFilePath = pageFilePath.replace(/\.js$/, '.lean.js') } - pageModule = import(/*@vite-ignore*/ pageFilePath) + if (import.meta.env.SSR) { + pageModule = import(/*@vite-ignore*/ pageFilePath + '?t=' + Date.now()) + } else { + pageModule = import(/*@vite-ignore*/ pageFilePath) + } } if (inBrowser) { diff --git a/src/client/app/router.ts b/src/client/app/router.ts index 3a7d8cb5..02d98944 100644 --- a/src/client/app/router.ts +++ b/src/client/app/router.ts @@ -1,7 +1,6 @@ import { reactive, inject, markRaw, nextTick, readonly } from 'vue' import type { Component, InjectionKey } from 'vue' -import { lookup } from 'mrmime' -import { notFoundPageData } from '../shared' +import { notFoundPageData, treatAsHtml } from '../shared' import type { PageData, PageDataPayload, Awaitable } from '../shared' import { inBrowser, withBase } from './utils' import { siteDataRef } from './data' @@ -182,8 +181,7 @@ export function createRouter( link.baseURI ) const currentUrl = window.location - const mimeType = lookup(pathname) - // only intercept inbound links + // only intercept inbound html links if ( !e.ctrlKey && !e.shiftKey && @@ -191,8 +189,7 @@ export function createRouter( !e.metaKey && !target && origin === currentUrl.origin && - // intercept only html and unknown types (assume html) - (!mimeType || mimeType === 'text/html') + treatAsHtml(pathname) ) { e.preventDefault() if ( diff --git a/src/client/theme-default/components/VPLocalSearchBox.vue b/src/client/theme-default/components/VPLocalSearchBox.vue index 2f49250c..965ab1df 100644 --- a/src/client/theme-default/components/VPLocalSearchBox.vue +++ b/src/client/theme-default/components/VPLocalSearchBox.vue @@ -28,6 +28,7 @@ import { } from 'vue' import type { ModalTranslations } from '../../../../types/local-search' import { pathToFile } from '../../app/utils' +import { escapeRegExp } from '../../shared' import { useData } from '../composables/data' import { LRUCache } from '../support/lru' import { createSearchTranslate } from '../support/translation' @@ -146,8 +147,8 @@ const cache = new LRUCache>(16) // 16 files debouncedWatch( () => [searchIndex.value, filterText.value, showDetailedList.value] as const, async ([index, filterTextValue, showDetailedListValue], old, onCleanup) => { - - if (old?.[0] !== index) { // in case of hmr + if (old?.[0] !== index) { + // in case of hmr cache.clear() } @@ -396,11 +397,7 @@ function formMarkRegex(terms: Set) { return new RegExp( [...terms] .sort((a, b) => b.length - a.length) - .map((term) => { - return `(${term - .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') - .replace(/-/g, '\\x2d')})` - }) + .map((term) => `(${escapeRegExp(term)})`) .join('|'), 'gi' ) diff --git a/src/client/theme-default/components/VPNavBarTitle.vue b/src/client/theme-default/components/VPNavBarTitle.vue index 51f4233c..eadef086 100644 --- a/src/client/theme-default/components/VPNavBarTitle.vue +++ b/src/client/theme-default/components/VPNavBarTitle.vue @@ -1,4 +1,5 @@