Nuxt MDC

repository·main·Indexed 19 days ago

https://github.com/nuxt-content/mdc

Nuxt MDC (MarkDown Components) allows Markdown to interact deeply with Vue components, supporting named slots, inline components, and custom prose components. It provides tools like the <MDC> and <MDCRenderer> components for rendering, the parseMarkdown function for AST generation, and <MDCSlot> to remove unwanted HTML wrappers. The module supports custom prose component mapping, remark/rehype plugin integration, and can be used in standard Vue projects via specific Vite configuration.

Tokens
11.2K
Snippets
44
Records
51
Agent score
63%

What's inside @nuxtjs/mdc

  1. Use Custom Elements in MDC

    main

    To use custom elements (tags that are not Vue components) within MDC markdown, you must configure them in two places to ensure both the Vue compiler and the MDC runtime treat them correctly:

    1. MDC Runtime: Add the tags to mdc.components.customElements in nuxt.config.ts. This prevents MDC from trying to resolve them as Vue components.
    2. Vue Compiler: Add the tags to vue.compilerOptions.isCustomElement in nuxt.config.ts so the Vue compiler doesn't warn about unknown components.

    If you only provide mdc.components.customElements, MDC will automatically attempt to configure Vue for you.

    export default defineNuxtConfig({
      vue: {
        compilerOptions: {
          isCustomElement: (tag) => tag.startsWith('mjx')
        }
      },
      mdc: {
        components: {
          customElements: ['mjx-container']
        }
      }
    })
  2. How to handle indentation and slots in MDC components

    main

    When using components in Markdown (::my-component ... ::), the indentation of the content inside the container is critical for correct rendering.

    • Container Indentation: Indenting the entire block (the ::component lines and their content) is fine.
    • Internal Indentation: Do not indent slot markers (e.g., #slotName) relative to the opening ::component line. If the #slotName line is further indented, MDC may fail to detect the slot.
    • Default Slots: If you indent content within a default slot, MDC may parse it as a code block and wrap it in a <pre> tag.
    ::alert
    #slot1
    slot-content
    ::
  3. Render nested async components in `<MDCRenderer>`

    main

    The <MDCRenderer> component supports rendering nested asynchronous components. This is useful for rendering asynchronous MDC block components (e.g., via defineAsyncComponent) or components that internally use <MDCRenderer> to fetch and render markdown.

    To ensure the parent <MDCRenderer> waits for child async components to resolve:

    1. The child component must use an async setup() function with top-level await.
    2. The child component's template should be wrapped in a Vue <Suspense> component with the suspensible prop set to true.

    Warning: In Nuxt, if you use useAsyncData or useFetch with immediate: false inside a child component, the parent <MDCRenderer> will not wait for it, which can lead to hydration errors or missing content.

    <template>
      <Suspense suspensible>
        <pre>{{ data }}</pre>
      </Suspense>
    </template>
    
    <script setup>
    const { data } = await useAsyncData(..., { immediate: true })
    </script>
  4. Install and Setup Nuxt MDC

    main

    To use Nuxt MDC in your project, add the @nuxtjs/mdc dependency using the Nuxt CLI and then register it in your nuxt.config.ts file.

    npx nuxi@latest module add mdc
    export default defineNuxtConfig({
      modules: ['@nuxtjs/mdc']
    })
  5. Install @nuxtjs/mdc in a Nuxt project

    main

    To add MDC to your Nuxt project, use the Nuxt CLI to add the module, then register it in your configuration file.

    Note: @nuxtjs/mdc is being deprecated in favor of Comark. Existing markdown files are compatible with Comark without changes.

    npx nuxi@latest module add mdc
    export default defineNuxtConfig({
      modules: ['@nuxtjs/mdc']
    })
  6. Use Nuxt MDC in a standard Vue project

    main

    You can use <MDCRenderer> in a non-Nuxt Vue project by following these steps:

    1. Install the package: Install @nuxtjs/mdc via your package manager.
    2. Stub Nuxt imports: Since the package expects Nuxt-specific environment variables, you must stub #mdc-imports and #mdc-configs in your Vite configuration.
      • Create a stub-mdc-imports.js file (export an empty object).
      • Add an alias in vite.config.ts pointing to this file.
    3. Implement a Parser: Use createMarkdownParser from @nuxtjs/mdc/runtime to handle parsing and syntax highlighting (e.g., with Shiki).
    4. Render: Pass the resulting AST to the <MDCRenderer> component.
    // 1. Create stub-mdc-imports.js
    export default {}
    
    // 2. Update 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'),
        }
      }
    })
    
    // 3. Usage in Vue component
    import MDCRenderer from '@nuxtjs/mdc/runtime/components/MDCRenderer.vue'
    // ... use createMarkdownParser to get AST ...
    <template>
      <MDCRenderer v-if="ast?.body" :body="ast.body" :data="ast.data" />
    </template>
  7. Configure Prose Components

    main

    Prose components are specialized Vue components that replace standard HTML tags (e.g., <ProseP> replaces <p>). You can disable them or extend the mapping in your nuxt.config.ts under the mdc.components key.

    export default defineNuxtConfig({
      modules: ['@nuxtjs/mdc'],
      mdc: {
        components: {
          prose: false, // Disable predefined prose components
          map: {
            p: 'MyCustomPComponent'
          }
        }
      }
    })
  8. Understand HAST Node types and structure

    main

    HAST (Hypertext Abstract Syntax Tree) is composed of several node types. Understanding the hierarchy is essential for traversing or transforming the tree:

    • Node: The base interface for all nodes. Includes an optional data field for custom metadata.
    • Literal: A node that contains a plain-text value. Examples include Text and Comment nodes.
    • Parent: A node that contains other nodes in a children array.
    • Root: The top-level node of a tree (or a fragment). It is a Parent and should only be used as the root, not as a child of another node.
    • Element: Represents an HTML element. It includes a tagName (e.g., 'div'), properties (attributes), and children.
  9. Configure the MDC parsing pipeline

    main

    The MDC parser uses a unified processor pipeline. You can customize this via MDCParseOptions passed to parseMarkdown or createMarkdownParser.

    Key Configuration Areas:

    • remark: Configure remark plugins to extend Markdown parsing capabilities.
    • rehype: Configure rehype plugins to extend HTML transformation capabilities.
    • highlight: Configure syntax highlighting settings.
    • toc: Enable or disable Table of Contents generation, or provide custom Toc options.
    • configs: An array of MdcConfig objects that allow hooking into the unified lifecycle via pre, remark, rehype, or post hooks.
  10. Configure and customize Prose Components

    main

    MDC uses 'Prose Components' to replace standard HTML tags (like <p>, <h1>, etc.) with Vue components (like <ProseP>, <ProseH1>). This allows you to inject custom logic or styling into standard markdown elements.

    Customization Options

    In nuxt.config.ts, you can:

    • Disable prose components by setting prose: false.
    • Map specific HTML tags to your own global components using the map key.
    • Enable custom prose components by pointing Nuxt to a specific directory.

    Implementation

    To override a prose component, create a component with the same name (e.g., ProseP.vue) and place it in a directory registered via the components.path config, ensuring they are globally available.

    export default defineNuxtConfig({
      modules: ['@nuxtjs/mdc'],
      mdc: {
        components: {
          prose: false, // Disable predefined prose components
          map: {
            p: 'MyCustomPComponent' // add global like MyCustomPComponent.global.vue
          }
        }
      }
    })
  11. Understand Unist Node types and structures

    main

    Nuxt MDC uses the Unist (Universal Syntax Tree) specification for its AST. The following interfaces define the hierarchy of nodes:

    • Node: The base interface for all syntactic units. Every node must have a type (string). It can optionally include data (for custom metadata) and position (for source mapping).
    • Literal: An extension of Node representing a leaf node that contains a plain value (e.g., a text node). It adds a value: unknown property.
    • Parent: An extension of Node representing a node that contains other nodes. It adds a children: Node[] property.

    When building plugins or traversing the tree, use Literal for leaf values and Parent for container nodes.

  12. Configure the Nuxt MDC module

    main

    You can configure the @nuxtjs/mdc module in your nuxt.config.js using the mdc property. This allows you to extend the parser with remark/rehype plugins, configure heading anchor links, control syntax highlighting, and manage component mapping.

    Available configuration keys include:

    • remarkPlugins: Register/configure remark plugins.
    • rehypePlugins: Register/configure rehype plugins.
    • headings.anchorLinks: Enable/disable heading anchor links (e.g., { h1: true, h2: false }).
    • highlight: Boolean to control syntax highlighting.
    • components.prose: Boolean to enable/disable predefined mapping for Prose Components (like p, ul, code).
    • components.map: A map used in <MDCRenderer> to control rendered components.
    import { defineNuxtConfig } from 'nuxt/config'
    
    export default defineNuxtConfig({
      modules: ['@nuxtjs/mdc'],
      mdc: {
        remarkPlugins: {
          // e.g. 'remark-math': { src: 'remark-math', options: { ... } }
        },
        rehypePlugins: {
          // e.g. 'rehype-mathjax': { src: 'rehype-mathjax', options: { ... } }
        },
        headings: {
          anchorLinks: { h1: true }
        },
        highlight: false,
        components: {
          prose: false,
          map: {}
        }
      }
    })