Nuxt MDC
MDC 強化了常規的 Markdown,可以編寫與任何 Vue 元件深度互動的文件。MDC 代表 MarkDown 元件。
功能
- 將 Markdown 語法與 HTML 標籤或 Vue 元件混合使用
- 解包任何生成的內容(例如:每個 Markdown 段落新增的
<p>
) - 使用帶命名插槽的 Vue 元件
- 支援內聯元件
- 支援巢狀元件的非同步渲染
- 向內聯 HTML 標籤新增屬性和類
在 https://content.nuxt.com/docs/files/markdown 上了解更多關於 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
函式的結果並渲染內容。例如,這是 Browser 部分 中示例程式碼的擴充套件版本,它使用 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>
散文元件
散文元件是取代常規 HTML 標籤的元件列表。例如,@nuxtjs/mdc
不會渲染 <p>
標籤,而是渲染 <ProseP>
元件。當您想為 Markdown 檔案新增額外功能時,這非常有用。例如,您可以為程式碼塊新增一個 複製
按鈕。
您可以透過在 nuxt.config.ts
中將 prose
選項設定為 false
來停用散文元件。或者擴充套件散文元件的對映以新增您自己的元件。
export default defineNuxtConfig({
modules: ['@nuxtjs/mdc'],
mdc: {
components: {
prose: false, // Disable predefined prose components
map: {
p: 'MyCustomPComponent'
}
}
}
})
為了自定義這些元件,您只需建立一個與您要控制的散文元件同名的元件即可。請務必將這些散文元件放在它們自己的散文資料夾中,並告知 nuxt 全域性註冊它們,以便 MDC 能夠正確訪問。
export default defineNuxtConfig({
modules: ['@nuxtjs/mdc'],
mdc: {
components: {
prose: true
}
},
components: {
global: true,
path: './components/prose'
}
})
以下是可用散文元件的列表
標籤 | 元件 | 原始檔 | 描述 |
---|---|---|---|
p | <ProseP> | ProseP.vue | 段落 |
h1 | <ProseH1> | ProseH1.vue | 一級標題 |
h2 | <ProseH2> | ProseH2.vue | 二級標題 |
h3 | <ProseH3> | ProseH3.vue | 三級標題 |
h4 | <ProseH4> | ProseH4.vue | 四級標題 |
h5 | <ProseH5> | ProseH5.vue | 五級標題 |
h6 | <ProseH6> | ProseH6.vue | 六級標題 |
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: {
// Register/Configure remark plugin to extend the parser, e.g.
// 'remark-math': {
// src: 'remark-math',
// options: {
// singleDollarTextMath: true,
// },
// },
},
rehypePlugins: {
// Register/Configure rehype plugin to extend the parser, e.g.
// 'rehype-mathjax': {
// src: 'rehype-mathjax',
// options: {
// tex: {
// inlineMath: [['$', '$'], ['\\(', '\\)']],
// displayMath: [['$$', '$$'], ['\\[', '\\]']],
// },
// },
// },
},
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 函式中執行(如果子元件不需要非同步/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
)以將模組的匯入別名到 stub 檔案
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 '@shikijs/themes/material-theme-palenight'
import HtmlLang from '@shikijs/langs/html'
import MdcLang from '@shikijs/langs/mdc'
import TsLang from '@shikijs/langs/typescript'
import VueLang from '@shikijs/langs/vue'
import ScssLang from '@shikijs/langs/scss'
import YamlLang from '@shikijs/langs/yaml'
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