2 min read

@document0/mdc

The MDC package processes .mdx / .md files into either an HTML string or a JSON AST, with frontmatter extraction, Shiki syntax highlighting, and table-of-contents generation — all without React.

This is the recommended content processor for Vue, Svelte, Astro, and any non-React framework. For React/Next.js, see @document0/mdx.

Install

npm install @document0/mdc shiki

processMdcToHtml

Parses markdown source directly into an HTML string. The simplest path — use this when you want to render with v-html or inject into a template.

import { processMdcToHtml } from "@document0/mdc";

const { html, frontmatter, toc } = await processMdcToHtml(source, options);

Parameters

ParameterTypeDescription
sourcestringRaw markdown/MDX source content
optionsMdcProcessorOptionsProcessing options (see below)

Return value

interface ProcessedMdcHtml {
  html: string;
  frontmatter: Record<string, unknown>;
  toc: TocEntry[];
}

processMdc

Parses markdown source into a JSON AST (MdcRoot). Use this when you want programmatic control over rendering — for example, mapping AST nodes to Vue or Svelte components.

import { processMdc } from "@document0/mdc";

const { body, frontmatter, toc } = await processMdc(source, options);

Return value

interface ProcessedMdc {
  body: MdcRoot;
  frontmatter: Record<string, unknown>;
  toc: TocEntry[];
}

The body is a tree of MdcNode objects (see types below) that you can walk and render however you like.


Options

Both processMdc and processMdcToHtml accept the same options:

interface MdcProcessorOptions {
  highlighter?: HighlighterGeneric<BundledLanguage, BundledTheme>;
  defaultLanguage?: string;
  themes?: RehypeShikiThemes;
  remarkPlugins?: unknown[];
  rehypePlugins?: unknown[];
}
OptionDefaultDescription
highlighterShiki highlighter instance. Omit to skip syntax highlighting
defaultLanguage"plaintext"Language for code blocks with no language specified
themes{ light: "github-light", dark: "github-dark" }Shiki dual-theme config
remarkPlugins[]Additional remark plugins
rehypePlugins[]Additional rehype plugins

Setting up Shiki

Create the highlighter once and reuse it (initialisation is expensive):

import { createHighlighter } from "shiki";

let highlighter: Awaited<ReturnType<typeof createHighlighter>> | null = null;

export async function getHighlighter() {
  if (highlighter) return highlighter;
  highlighter = await createHighlighter({
    themes: ["github-dark", "github-light"],
    langs: ["typescript", "javascript", "bash", "json", "vue", "css"],
  });
  return highlighter;
}

Then pass it to either processor:

const highlighter = await getHighlighter();
const { html, toc } = await processMdcToHtml(raw, { highlighter });

Types

TocEntry

interface TocEntry {
  id: string;    // heading anchor id
  text: string;  // heading text content
  depth: number; // 1–6
}

MdcNode

The JSON AST node returned by processMdc:

interface MdcNode {
  type: "element" | "text";
  tag?: string;                    // HTML tag name (e.g. "h2", "p", "pre")
  props?: Record<string, unknown>; // element attributes
  children?: MdcNode[];
  value?: string;                  // text content (for type: "text")
}

MdcRoot

interface MdcRoot {
  type: "root";
  children: MdcNode[];
}

Built-in plugins

rehypeShiki

Applied automatically when you pass a highlighter. Replaces fenced code blocks with Shiki-highlighted HTML output.

rehypeStripShikiStyle

Also applied automatically. Strips inline style attributes from Shiki output so your CSS themes take over.

Both are re-exported if you need them in a custom unified pipeline:

import { rehypeShiki, rehypeStripShikiStyle } from "@document0/mdc";

MDC vs MDX

@document0/mdc@document0/mdx
OutputHTML string or JSON ASTCompiled JS module
Peer depsNonereact, react-dom, @mdx-js/mdx
Best forVue, Svelte, any non-React frameworkReact / Next.js
Custom componentsStyle with CSS or render JSON ASTJSX component map via run()

Both packages share the same Shiki integration, frontmatter extraction, and TOC generation. Choose based on your framework.