透過 100+ 個技巧學習 Nuxt!

mdc
@nuxtjs/mdc

MDC 強力擴展了 Markdown 的功能,讓您能夠撰寫與任何 Vue 元件深度互動的文件。

Nuxt MDC

Nuxt MDC

npm versionnpm downloadsLicenseNuxt

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 區段中

nuxt.config.ts
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。

mdc-test.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>

<MDCSlot>

此元件是 Vue 的 <slot/> 元件的替代品,專為 MDC 設計。使用此元件,您可以渲染元件的子節點,同時移除一個或多個包裝元素。在下面的範例中,Alert 元件接收文字及其預設插槽(子節點)。但是,如果元件使用正常的 <slot/> 渲染此插槽,它將在文字周圍渲染一個 <p> 元素。

markdown.md
::alert
This is an Alert
::
Alert.vue
<template>
  <div class="alert">
    <!-- Slot will render <p> tag around the text -->
    <slot />
  </div>
</template>

將每個文字包裝在段落中是 markdown 的預設行為。MDC 並不是要打破 markdown 的行為;相反地,MDC 的目標是讓 markdown 變得更強大。在這個範例以及所有類似的情況下,您可以使用 <MDCSlot /> 來移除不需要的包裝器。

Alert.vue
<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 元件的映射以新增您自己的元件。

nuxt.config.ts
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 公開了一個方便的 helper 來解析 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 函數是一個通用 helper,您也可以在瀏覽器中使用它,例如在 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 helper 也接受選項作為第二個參數,以控制解析器的行為。(查看 MDCParseOptions 介面↗︎)。

名稱預設值描述
remark.plugins{}註冊/配置解析器的 remark 外掛程式。
rehype.options{}配置 remark-rehype 選項。
rehype.plugins{}註冊/配置解析器的 rehype 外掛程式。
highlightfalse控制是否應突出顯示程式碼區塊。您也可以提供自訂的 highlighter。
toc.depth2包含在目錄中的最大標題深度。
toc.searchDepth2搜尋標題的巢狀標籤的最大深度。

查看 MDCParseOptions 類型↗︎

配置

您可以透過在您的 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
      }
    }
  }
})

查看 ModuleOptions 類型↗︎


渲染巢狀非同步元件

MDCRenderer 也支援渲染巢狀非同步元件,方法是等待其樹狀結構中的任何子元件解析其頂層 async setup()

此行為允許渲染非同步的 MDC 區塊元件(例如透過 defineAsyncComponent),以及引入本身內部使用 MDCRenderer 來渲染 markdown 的元件,然後才允許父元件解析。

為了讓父 MDCRenderer 元件正確等待子非同步元件解析

  1. 子元件中的所有功能必須在具有頂層 await 的 async setup 函數中執行(如果子元件中不需要 async/await 行為,例如沒有資料提取,則元件將正常解析)。
  2. 子元件的 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 應用程式中,這表示在任何 useAsyncDatauseFetch 呼叫上設定 immediate: false 都會阻止MDCRenderer 等待,並且父元件可能會在子元件完成渲染之前解析,從而導致 hydration 錯誤或內容遺失。

簡單範例:非同步元件

您的巢狀 MDC 區塊元件可以使用頂層 async setup() 作為其生命週期的一部分,例如在允許父元件解析之前等待資料提取。

請參閱遊樂場中的程式碼 AsyncComponent 元件 作為範例,若要查看實際行為,請執行 pnpm dev 並導航至 /async-components 路由以查看遊樂場。

進階範例:MDC「程式碼片段」

為了示範這些巢狀非同步區塊元件有多強大,您可以允許使用者在您的專案中定義 markdown 文件子集,這些文件將用作父文件中的可重複使用的「程式碼片段」。

您將在您的專案中建立一個自訂區塊元件,該元件負責從 API 提取程式碼片段 markdown 內容,使用 parseMarkdown 取得 ast 節點,並在其自己的 MDCMDCRenderer 元件中渲染它。

請參閱遊樂場中的程式碼 PageSnippet 元件 作為範例,若要查看實際行為,請執行 pnpm dev 並導航至 /async-components/advanced 路由以查看遊樂場。

處理遞迴

如果您的專案實作了「可重複使用的程式碼片段」類型的方法,您可能會想要防止使用遞迴程式碼片段,其中巢狀 MDCRenderer 嘗試在其元件樹狀結構中的某個位置載入另一個具有相同內容的子元件(表示匯入自身),並且您的應用程式將陷入無限迴圈。

解決此問題的一種方法是利用 Vue 的 provide/inject 來向下傳遞已渲染的「程式碼片段」的歷史記錄,以便子元件可以正確判斷它是否被遞迴呼叫,並停止鏈結。這可以與在呼叫 parseMarkdown 函數後解析 ast 文件節點結合使用,以在 DOM 中渲染內容之前從 ast 中剝離遞迴節點樹狀結構。

有關如何使用此模式防止無限迴圈和遞迴的範例,請參閱遊樂場的 PageSnippet 元件 中的程式碼。


在您的 Vue 專案中渲染

<MDCRenderer> 元件與一些匯出的套件工具程式結合使用,也可以在一般的(非 Nuxt)Vue 專案中使用。

若要在您的標準 Vue 專案中實作,請依照以下指示操作。

安裝套件

依照上面的安裝指示,忽略將 Nuxt 模組新增到 nuxt.config.ts 檔案的步驟。

Stub Nuxt 模組匯入

由於您未使用 Nuxt,因此您需要在您的 Vue 專案的 Vite 配置檔案中 stub 一些模組的匯入。這對於避免模組嘗試存取 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 '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 在線上深入研究此模組

Edit @nuxtjs/mdc

或在本機端

  1. Clone 此儲存庫
  2. 使用 pnpm install 安裝依賴項
  3. 使用 pnpm dev 啟動開發伺服器

授權條款

MIT 授權條款

版權所有 (c) NuxtLabs