Nuxt MDC
MDC 增强了常规 Markdown 的功能,可以编写与任何 Vue 组件深度交互的文档。MDC 代表 MarkDown Components(Markdown 组件)。
特性
- 将 Markdown 语法与 HTML 标签或 Vue 组件混合使用
- 解包任何生成的内容 (例如:每个 Markdown 段落添加的
<p>
) - 使用带命名插槽的 Vue 组件
- 支持内联组件
- 支持嵌套组件的异步渲染
- 向内联 HTML 标签添加属性和类
在 https://content.nuxtjs.org/guide/writing/mdc 上了解更多关于 MDC 语法的信息
!注意 您可以在您的 Nuxt 项目(标准配置)或任何 Vue 项目中使用此包。
请参阅下面的 在您的 Vue 项目中渲染 以获取更多信息。
安装
npx nuxi@latest module add mdc
然后,将 @nuxtjs/mdc
添加到您的 nuxt.config.ts
的 modules 部分
export default defineNuxtConfig({
modules: ['@nuxtjs/mdc']
})
就是这样!您可以开始在您的 Nuxt 项目中编写和渲染 markdown 文件了 ✨
渲染
@nuxtjs/mdc
公开了三个组件来渲染 markdown 文件。
<MDC>
使用 <MDC>
,您可以直接在您的组件/页面中解析和渲染 markdown 内容。此组件接受原始 markdown,使用 parseMarkdown
函数解析它,然后使用 <MDCRenderer>
渲染它。
<script setup lang="ts">
const md = `
::alert
Hello MDC
::
`
</script>
<template>
<MDC :value="md" tag="article" />
</template>
请注意,::alert
将使用 components/mdc/Alert.vue
组件。
<MDCRenderer>
此组件将获取 parseMarkdown
函数的结果并渲染内容。例如,这是 浏览器部分 中示例代码的扩展版本,它使用 MDCRenderer
来渲染解析后的 markdown。
<script setup lang="ts">
import { parseMarkdown } from '@nuxtjs/mdc/runtime'
const { data: ast } = await useAsyncData('markdown', () => parseMarkdown('::alert\nMissing markdown input\n::'))
</script>
<template>
<MDCRenderer :body="ast.body" :data="ast.data" />
</template>
<MDCSlot>
此组件是 Vue 的 <slot/>
组件的替代品,专门为 MDC 设计。使用此组件,您可以渲染组件的子元素,同时删除一个或多个包装元素。在下面的示例中,Alert 组件接收文本及其默认插槽(子元素)。但是,如果组件使用普通的 <slot/>
渲染此插槽,它将在文本周围渲染一个 <p>
元素。
::alert
This is an Alert
::
<template>
<div class="alert">
<!-- Slot will render <p> tag around the text -->
<slot />
</div>
</template>
将每个文本包裹在段落中是 markdown 的默认行为。MDC 并非旨在打破 markdown 行为;相反,MDC 的目标是使 markdown 更加强大。在此示例和所有类似情况下,您可以使用 <MDCSlot />
来删除不需要的包装器。
<template>
<div class="alert">
<!-- MDCSlot will only render the actual text without the wrapping <p> -->
<MDCSlot unwrap="p" />
</div>
</template>
Prose 组件
Prose 组件是一系列组件,它们将被渲染以代替常规 HTML 标签。例如,@nuxtjs/mdc
不会渲染 <p>
标签,而是渲染 <ProseP>
组件。当您想为您的 markdown 文件添加额外功能时,这非常有用。例如,您可以向您的代码块添加一个 copy
按钮。
您可以通过在 nuxt.config.ts
中将 prose
选项设置为 false
来禁用 prose 组件。或者扩展 prose 组件的映射以添加您自己的组件。
export default defineNuxtConfig({
modules: ['@nuxtjs/mdc'],
mdc: {
components: {
prose: false, // Disable predefined prose components
map: {
p: 'MyCustomPComponent'
}
}
}
})
为了自定义这些组件,您只需创建一个与您尝试控制的 prose 组件同名的组件。确保将这些 prose 组件放在它们自己的 prose 文件夹中,并告诉 nuxt 全局注册它们,以便 MDC 可以获得正确的访问权限。
export default defineNuxtConfig({
modules: ['@nuxtjs/mdc'],
mdc: {
components: {
prose: true
}
},
components: {
global: true,
path: './components/prose'
}
})
以下是可用的 prose 组件列表
标签 | 组件 | 来源 | 描述 |
---|---|---|---|
p | <ProseP> | ProseP.vue | 段落 |
h1 | <ProseH1> | ProseH1.vue | 标题 1 |
h2 | <ProseH2> | ProseH2.vue | 标题 2 |
h3 | <ProseH3> | ProseH3.vue | 标题 3 |
h4 | <ProseH4> | ProseH4.vue | 标题 4 |
h5 | <ProseH5> | ProseH5.vue | 标题 5 |
h6 | <ProseH6> | ProseH6.vue | 标题 6 |
ul | <ProseUl> | ProseUl.vue | 无序列表 |
ol | <ProseOl> | ProseOl.vue | 有序列表 |
li | <ProseLi> | ProseLi.vue | 列表项 |
blockquote | <ProseBlockquote> | ProseBlockquote.vue | 块引用 |
hr | <ProseHr> | ProseHr.vue | 水平线 |
pre | <ProsePre> | ProsePre.vue | 预格式化文本 |
code | <ProseCode> | ProseCode.vue | 代码块 |
table | <ProseTable> | ProseTable.vue | 表格 |
thead | <ProseThead> | ProseThead.vue | 表头 |
tbody | <ProseTbody> | ProseTbody.vue | 表体 |
tr | <ProseTr> | ProseTr.vue | 表格行 |
th | <ProseTh> | ProseTh.vue | 表格标题 |
td | <ProseTd> | ProseTd.vue | 表格数据 |
a | <ProseA> | ProseA.vue | 锚链接 |
img | <ProseImg> | ProseImg.vue | 图片 |
em | <ProseEm> | ProseEm.vue | 强调 |
strong | <ProseStrong> | ProseStrong.vue | 加粗 |
解析 Markdown
Nuxt MDC 公开了一个方便的助手来解析 MDC 文件。您可以从 @nuxtjs/mdc/runtime
导入 parseMarkdown
函数,并使用它来解析用 MDC 语法编写的 markdown 文件。
Node.js
// server/api/parse-mdc.ts
import { parseMarkdown } from '@nuxtjs/mdc/runtime'
export default eventHandler(async () => {
const mdc = [
'# Hello MDC',
'',
'::alert',
'This is an Alert',
'::'
].join('\n')
const ast = await parseMarkdown(mdc)
return ast
})
浏览器
parseMarkdown
函数是一个通用助手,您也可以在浏览器中使用它,例如在 Vue 组件内部。
<script setup lang="ts">
import { parseMarkdown } from '@nuxtjs/mdc/runtime'
const { data: ast } = await useAsyncData('markdown', () => parseMarkdown('::alert\nMissing markdown input\n::'))
</script>
<template>
<MDCRenderer :body="ast.body" :data="ast.data" />
</template>
选项
parseMarkdown
助手还接受选项作为第二个参数来控制解析器的行为。(查看 MDCParseOptions
接口↗︎)。
名称 | 默认 | 描述 |
---|---|---|
remark.plugins | {} | 注册 / 配置解析器的 remark 插件。 |
rehype.options | {} | 配置 remark-rehype 选项。 |
rehype.plugins | {} | 注册 / 配置解析器的 rehype 插件。 |
highlight | false | 控制代码块是否应该高亮显示。您还可以提供自定义高亮器。 |
toc.depth | 2 | 包含在目录中的最大标题深度。 |
toc.searchDepth | 2 | 搜索标题的嵌套标签的最大深度。 |
配置
您可以通过在您的 nuxt.config.js
中提供 mdc
属性来配置模块;以下是默认选项
import { defineNuxtConfig } from 'nuxt/config'
export default defineNuxtConfig({
modules: ['@nuxtjs/mdc'],
mdc: {
remarkPlugins: {
plugins: {
// Register/Configure remark plugin to extend the parser
}
},
rehypePlugins: {
options: {
// Configure rehype options to extend the parser
},
plugins: {
// Register/Configure rehype plugin to extend the parser
}
},
headings: {
anchorLinks: {
// Enable/Disable heading anchor links. { h1: true, h2: false }
}
},
highlight: false, // Control syntax highlighting
components: {
prose: false, // Add predefined map to render Prose Components instead of HTML tags, like p, ul, code
map: {
// This map will be used in `<MDCRenderer>` to control rendered components
}
}
}
})
渲染嵌套的异步组件
MDCRenderer
还支持渲染*嵌套的*异步组件,方法是等待其树中的任何子组件解析其顶级的 async setup()
。
这种行为允许渲染异步的 MDC 块组件(例如通过 defineAsyncComponent
),以及引入组件本身在允许父组件解析之前内部使用 MDCRenderer
渲染 markdown。
为了使父 MDCRenderer
组件正确等待子异步组件解析
- 子组件中的所有功能必须在具有顶级
await
的异步 setup 函数中执行(如果子组件中不需要 async/await 行为,例如没有数据获取,则组件将正常解析)。 - 子组件的
template
内容应该用内置的Suspense
组件 包装,并将suspensible
prop 设置为true
。<template> <Suspense suspensible> <pre>{{ data }}</pre> </Suspense> </template> <script setup> const { data } = await useAsyncData(..., { immediate: true, // This is the default, but is required for this functionality }) </script>
在 Nuxt 应用程序中,这意味着在任何useAsyncData
或useFetch
调用上设置immediate: false
将阻止父MDCRenderer
等待,并且父组件可能会在子组件完成渲染之前解析,从而导致水合错误或内容丢失。
简单示例:异步组件
您的嵌套 MDC 块组件可以将顶级 async setup()
用作其生命周期的一部分,例如在允许父组件解析之前等待数据获取。
请参阅 playground 中的代码 AsyncComponent
组件 作为示例,要查看实际行为,请运行 pnpm dev
并导航到 /async-components
路由以查看 playground。
高级示例:MDC “代码片段”
为了演示这些嵌套异步块组件的强大功能,您可以允许用户在您的项目中定义 markdown 文档的子集,这些子集将在父文档中用作可重用的“代码片段”。
您将在您的项目中创建一个自定义块组件,该组件处理从 API 获取代码片段 markdown 内容,使用 parseMarkdown
获取 ast
节点,并在其自身的 MDC
或 MDCRenderer
组件中渲染它。
请参阅 playground 中的代码 PageSnippet
组件 作为示例,要查看实际行为,请运行 pnpm dev
并导航到 /async-components/advanced
路由以查看 playground。
处理递归
如果您的项目实现了“可重用代码片段”类型的方法,您可能希望防止使用递归代码片段,即嵌套的 MDCRenderer
尝试在其组件树中的某个位置加载另一个具有相同内容的子项(意味着导入自身),并且您的应用程序将被抛入无限循环。
解决此问题的一种方法是利用 Vue 的 provide/inject
来传递渲染的“代码片段”的历史记录,以便子项可以正确确定它是否被递归调用,并停止链。这可以与在调用 parseMarkdown
函数后解析 ast
文档节点结合使用,以在 DOM 中渲染内容之前从 ast
中剥离递归节点树。
有关如何使用此模式防止无限循环和递归的示例,请参阅 playground 的 PageSnippet
组件 中的代码。
在您的 Vue 项目中渲染
<MDCRenderer>
组件与一些导出的包实用程序结合使用,也可以在普通的(非 Nuxt)Vue 项目中使用。
要在您的标准 Vue 项目中实现,请按照以下说明操作。
安装包
按照上面的 安装说明 进行操作,忽略将 Nuxt 模块添加到 nuxt.config.ts
文件的步骤。
桩 Nuxt 模块导入
由于您没有使用 Nuxt,因此您需要在 Vue 项目的 Vite 配置文件中桩一些模块的导入。这对于避免在模块尝试访问 Nuxt 特定的导入时出错是必要的。
在您的 Vue 项目的根目录中创建一个新文件,例如 stub-mdc-imports.js
,并添加以下内容
// stub-mdc-imports.js
export default {}
接下来,更新您的 Vue 项目的 Vite 配置文件(例如 vite.config.ts
),将模块的导入别名到桩文件
import { defineConfig } from 'vite'
import path from 'path'
export default defineConfig({
resolve: {
alias: {
'#mdc-imports': path.resolve(__dirname, './stub-mdc-imports.js'),
'#mdc-configs': path.resolve(__dirname, './stub-mdc-imports.js'),
}
}
})
用法
接下来,让我们创建一个新的 Vue composable 来处理 markdown 内容的解析,以及使用 Shiki 向代码块添加语法高亮。
// composables/useMarkdownParser.ts
// Import package exports
import {
createMarkdownParser,
rehypeHighlight,
createShikiHighlighter,
} from '@nuxtjs/mdc/runtime'
// Import desired Shiki themes and languages
import MaterialThemePalenight from 'shiki/themes/material-theme-palenight.mjs'
import HtmlLang from 'shiki/langs/html.mjs'
import MdcLang from 'shiki/langs/mdc.mjs'
import TsLang from 'shiki/langs/typescript.mjs'
import VueLang from 'shiki/langs/vue.mjs'
import ScssLang from 'shiki/langs/scss.mjs'
import YamlLang from 'shiki/langs/yaml.mjs'
export default function useMarkdownParser() {
let parser: Awaited<ReturnType<typeof createMarkdownParser>>
const parse = async (markdown: string) => {
if (!parser) {
parser = await createMarkdownParser({
rehype: {
plugins: {
highlight: {
instance: rehypeHighlight,
options: {
// Pass in your desired theme(s)
theme: 'material-theme-palenight',
// Create the Shiki highlighter
highlighter: createShikiHighlighter({
bundledThemes: {
'material-theme-palenight': MaterialThemePalenight,
},
// Configure the bundled languages
bundledLangs: {
html: HtmlLang,
mdc: MdcLang,
vue: VueLang,
yml: YamlLang,
scss: ScssLang,
ts: TsLang,
typescript: TsLang,
},
}),
},
},
},
},
})
}
return parser(markdown)
}
return parse
}
现在将我们刚刚创建的 useMarkdownParser
composable 以及导出的类型接口导入到您的宿主项目的 Vue 组件中,并利用它们来处理原始 markdown 并初始化 <MDCRenderer>
组件。
<script setup lang="ts">
import { onBeforeMount, ref, watch } from 'vue'
// Import package exports
import MDCRenderer from '@nuxtjs/mdc/runtime/components/MDCRenderer.vue'
import type { MDCParserResult } from '@nuxtjs/mdc'
import useMarkdownParser from './composables/useMarkdownParser';
const md = ref(`
# Just a Vue app
This is markdown content rendered via the \`<MDCRenderer>\` component, including MDC below.
::alert
Hello MDC
::
\`\`\`ts
const a = 1;
\`\`\`
`);
const ast = ref<MDCParserResult | null>(null)
const parse = useMarkdownParser()
onBeforeMount(async () => {
ast.value = await parse(md.value)
})
</script>
<template>
<Suspense>
<MDCRenderer v-if="ast?.body" :body="ast.body" :data="ast.data" />
</Suspense>
</template>
贡献
您可以使用 StackBlitz 在线深入了解此模块
或者本地
- 克隆此仓库
- 使用
pnpm install
安装依赖项 - 使用
pnpm dev
启动开发服务器
许可证
版权所有 (c) NuxtLabs