跳至内容

在 Markdown 中使用 Vue

在 VitePress 中,每个 Markdown 文件都会被编译成 HTML,然后作为 Vue 单文件组件 处理。这意味着您可以在 Markdown 中使用任何 Vue 功能,包括动态模板、使用 Vue 组件,或者通过添加 <script> 标签来实现任意页面内 Vue 组件逻辑。

值得注意的是,VitePress 利用 Vue 的编译器自动检测和优化 Markdown 内容中纯粹的静态部分。静态内容被优化为单个占位符节点,并从页面的 JavaScript 负载中删除,以便进行初始访问。它们在客户端水合过程中也会被跳过。简而言之,您只需为任何给定页面上的动态部分付费。

SSR 兼容性

所有 Vue 使用都需要与 SSR 兼容。有关详细信息和常见解决方法,请参阅 SSR 兼容性

模板

插值

每个 Markdown 文件首先被编译成 HTML,然后作为 Vue 组件传递到 Vite 进程管道。这意味着您可以在文本中使用 Vue 风格的插值

输入

md
{{ 1 + 1 }}

输出

2

指令

指令也适用(请注意,根据设计,原始 HTML 在 Markdown 中也是有效的)

输入

html
<span v-for="i in 3">{{ i }}</span>

输出

1 2 3 

<script><style>

Markdown 文件中的根级 <script><style> 标签的工作方式与 Vue SFC 中一样,包括 <script setup><style module> 等。这里的主要区别在于没有 <template> 标签:所有其他根级内容都是 Markdown。还要注意,所有标签都应该放在前置信息之后

html
---
hello: world
---

<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

## Markdown Content

The count is: {{ count }}

<button :class="$style.button" @click="count++">Increment</button>

<style module>
.button {
  color: red;
  font-weight: bold;
}
</style>

避免在 Markdown 中使用 <style scoped>

当在 Markdown 中使用时,<style scoped> 需要为当前页面上的每个元素添加特殊属性,这将显著增加页面大小。当在页面中需要局部作用域样式时,<style module> 是首选。

您还可以访问 VitePress 的运行时 API,例如 useData 帮助程序,它提供了对当前页面元数据的访问权限

输入

html
<script setup>
import { useData } from 'vitepress'

const { page } = useData()
</script>

<pre>{{ page }}</pre>

输出

json
{
  "path": "/using-vue.html",
  "title": "Using Vue in Markdown",
  "frontmatter": {},
  ...
}

使用组件

您可以在 Markdown 文件中直接导入和使用 Vue 组件。

在 Markdown 中导入

如果一个组件只被少数页面使用,建议在使用的地方显式导入它们。这允许它们被正确地代码分割,并且只有在显示相关页面时才会加载

md
<script setup>
import CustomComponent from '../components/CustomComponent.vue'
</script>

# Docs

This is a .md using a custom component

<CustomComponent />

## More docs

...

全局注册组件

如果一个组件将在大多数页面上使用,可以通过自定义 Vue 应用实例来全局注册它们。有关示例,请参阅 扩展默认主题 中的相关部分。

重要

确保自定义组件的名称包含连字符或使用 PascalCase。否则,它将被视为内联元素,并被包装在 <p> 标签中,这将导致水合不匹配,因为 <p> 不允许在其中放置块级元素。

在标题中使用组件

您可以在标题中使用 Vue 组件,但请注意以下语法之间的区别

Markdown输出 HTML解析后的标题
 # text <Tag/> 
<h1>text <Tag/></h1>text
 # text `<Tag/>` 
<h1>text <code>&lt;Tag/&gt;</code></h1>text <Tag/>

<code> 包裹的 HTML 将按原样显示;只有没有被包裹的 HTML 将被 Vue 解析。

提示

输出 HTML 是由 Markdown-it 完成的,而解析后的标题由 VitePress 处理(并用于侧边栏和文档标题)。

转义

您可以通过将 Vue 插值包装在 <span> 或其他带有 v-pre 指令的元素中来转义它们

输入

md
This <span v-pre>{{ will be displayed as-is }}</span>

输出

{{ 将按原样显示 }}

或者,您可以将整个段落包装在 v-pre 自定义容器中

md
::: v-pre
{{ This will be displayed as-is }}
:::

输出

{{ 这将按原样显示 }}

在代码块中取消转义

默认情况下,所有围栏代码块都会自动用 v-pre 包装,因此围栏内部不会处理任何 Vue 语法。要启用围栏内部的 Vue 风格插值,可以在语言后追加 -vue 后缀,例如 js-vue

输入

md
```js-vue
Hello {{ 1 + 1 }}
```

输出

js
Hello 2

请注意,这可能会阻止某些标记被正确地语法高亮显示。

使用 CSS 预处理器

VitePress 对 CSS 预处理器提供了 内置支持.scss.sass.less.styl.stylus 文件。无需为它们安装特定于 Vite 的插件,但必须安装相应的预处理器本身

# .scss and .sass
npm install -D sass

# .less
npm install -D less

# .styl and .stylus
npm install -D stylus

然后您可以在 Markdown 和主题组件中使用以下内容

vue
<style lang="sass">
.title
  font-size: 20px
</style>

使用传送门

Vitepress 目前仅支持将传送门到 body 的 SSG 支持。对于其他目标,您可以将它们包装在内置的 <ClientOnly> 组件中,或者通过 postRender 钩子 将传送门标记注入到最终页面 HTML 的正确位置。

详细信息
vue
<script setup lang="ts">
import { ref } from 'vue'
const showModal = ref(false)
</script>

<template>
  <button class="modal-button" @click="showModal = true">Show Modal</button>

  <Teleport to="body">
    <Transition name="modal">
      <div v-show="showModal" class="modal-mask">
        <div class="modal-container">
          <p>Hello from the modal!</p>
          <div class="model-footer">
            <button class="modal-button" @click="showModal = false">
              Close
            </button>
          </div>
        </div>
      </div>
    </Transition>
  </Teleport>
</template>

<style scoped>
.modal-mask {
  position: fixed;
  z-index: 200;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity 0.3s ease;
}

.modal-container {
  width: 300px;
  margin: auto;
  padding: 20px 30px;
  background-color: var(--vp-c-bg);
  border-radius: 2px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
  transition: all 0.3s ease;
}

.model-footer {
  margin-top: 8px;
  text-align: right;
}

.modal-button {
  padding: 4px 8px;
  border-radius: 4px;
  border-color: var(--vp-button-alt-border);
  color: var(--vp-button-alt-text);
  background-color: var(--vp-button-alt-bg);
}

.modal-button:hover {
  border-color: var(--vp-button-alt-hover-border);
  color: var(--vp-button-alt-hover-text);
  background-color: var(--vp-button-alt-hover-bg);
}

.modal-enter-from,
.modal-leave-to {
  opacity: 0;
}

.modal-enter-from .modal-container,
.modal-leave-to .modal-container {
  transform: scale(1.1);
}
</style>
md
<ClientOnly>
  <Teleport to="#modal">
    <div>
      // ...
    </div>
  </Teleport>
</ClientOnly>