Skip to content
当前页导航

在 Markdown 中使用 Vue

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

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

SSR Compatibility

所有 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> and <style>

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

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>

Avoid <style scoped> in Markdown

在 Markdown 中使用时,<stylescoped>需要为当前页面上的每个元素添加特殊属性,这会显着增大页面大小。当页面需要局部范围的样式时,首选<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 应用实例来全局注册它们。有关示例,请参阅扩展默认主题中的相关部分。

IMPORTANT

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

在标头中使用组件 <ComponentInHeader />

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

MarkdownOutput HTMLParsed Header
 # text <Tag/> 
<h1>text <Tag/></h1>text
 # text `<Tag/>` 
<h1>text <code>&lt;Tag/&gt;</code></h1>text <Tag/>

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

TIP

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

转译

您可以通过使用v-pre指令将 Vue 插值包装在<span>或其他元素中来转义:

输入

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

输出

This {{ will be displayed as-is }}

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

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

输出

{{ 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>

使用 Teleports

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

Details
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>

本文档由全栈行动派(qzxdp.cn)翻译并整理