MDC Syntax Logo
API Reference

Parse API

MDC Syntax provides functions to convert MDC (Markdown Components) content into an Abstract Syntax Tree (AST).

String Parsing

parse(source: string): ParseResult

Parses MDC content from a string and returns the complete parsed structure.

Parameters:

  • source - The markdown/MDC content as a string

Returns: ParseResult object containing:

  • body - The parsed MDC AST root
  • excerpt? - Optional excerpt (content before <!-- more --> comment)
  • data - Frontmatter data parsed from YAML
  • toc? - Table of contents

Example:

import { parse } from 'mdc-syntax'

const content = `---
title: Hello World
description: A simple example
---

# Hello World

This is a **markdown** document with *MDC* components.

::alert{type="info"}
This is an alert component
::
`

const result = parse(content)

console.log(result.body) // MDC AST
console.log(result.data) // { title: 'Hello World', description: 'A simple example' }
console.log(result.toc) // Table of contents

Stream Parsing

parseStream(stream: Readable | ReadableStream<Uint8Array>): Promise<ParseResult>

Asynchronously parses MDC content from a Node.js Readable stream or Web ReadableStream. The function waits for the entire stream to complete before parsing.

Parameters:

  • stream - A Node.js Readable stream or Web ReadableStream containing MDC content

Returns: Promise resolving to ParseResult

Example with Node.js stream:

import { createReadStream } from 'node:fs'
import { parseStream } from 'mdc-syntax/stream'

const stream = createReadStream('content.md')
const result = await parseStream(stream)

console.log(result.body)
console.log(result.data)
console.log(result.toc)

Example with Web stream (Fetch API):

import { parseStream } from 'mdc-syntax/stream'

const response = await fetch('https://example.com/content.md')
const result = await parseStream(response.body!)

console.log(result.body)

parseStreamIncremental(stream: Readable | ReadableStream<Uint8Array>): AsyncGenerator<IncrementalParseResult>

Parses MDC content incrementally as chunks arrive from the stream. This is ideal for real-time UI updates during streaming.

Features:

  • Yields results after each chunk is received
  • Automatically applies auto-close to handle incomplete syntax
  • Provides progress tracking through chunk information
  • Final result includes complete TOC

Parameters:

  • stream - A Node.js Readable stream or Web ReadableStream

Yields: IncrementalParseResult for each chunk:

  • chunk - The chunk that was just processed (string)
  • content - The accumulated content (string)
  • body - Current state of the parsed MDC AST
  • data - Frontmatter data (available once parsed)
  • isComplete - Boolean indicating if stream is complete
  • excerpt? - Optional excerpt
  • toc? - Table of contents (only in final result when isComplete: true)

Example:

import { parseStreamIncremental } from 'mdc-syntax/stream'

const response = await fetch('https://example.com/article.md')

for await (const result of parseStreamIncremental(response.body!)) {
  if (!result.isComplete) {
    // Update UI as chunks arrive
    console.log(`Received chunk: ${result.chunk.length} bytes`)
    console.log(`Current elements: ${result.body.value.length}`)
    renderPartialContent(result.body)
  }
  else {
    // Final result with complete TOC
    console.log('Stream complete!')
    console.log('TOC:', result.toc)
    renderFinalContent(result.body)
  }
}

Example with progress tracking:

import { parseStreamIncremental } from 'mdc-syntax/stream'

let totalBytes = 0
let chunkCount = 0

for await (const result of parseStreamIncremental(stream)) {
  if (!result.isComplete) {
    totalBytes += result.chunk.length
    chunkCount++

    console.log(`Progress: ${chunkCount} chunks, ${totalBytes} bytes`)
    console.log(`Parsed ${result.body.value.length} elements so far`)
  }
}

Types

ParseResult

interface ParseResult {
  body: MinimarkTree // The parsed MDC AST
  excerpt?: MinimarkTree // Optional excerpt (content before <!-- more -->)
  data: any // Frontmatter data
  toc?: any // Table of contents
}

IncrementalParseResult

interface IncrementalParseResult {
  chunk: string // The chunk just received
  content: string // The accumulated content
  body: MinimarkTree // Current parsed state
  data: any // Frontmatter data (once available)
  isComplete: boolean // Whether stream is finished
  excerpt?: MinimarkTree // Optional excerpt
  toc?: any // TOC (only in final result)
}

MinimarkTree

interface MinimarkTree {
  type: 'minimark'
  value: MinimarkNode[]
}

MinimarkNode

type MinimarkNode = MinimarkElement | MinimarkText | MinimarkComment

interface MinimarkElement {
  type: 'element'
  tag: string
  props: Record<string, any>
  children: MinimarkNode[]
}

interface MinimarkText {
  type: 'text'
  value: string
}

interface MinimarkComment {
  type: 'comment'
  value: string
}

Processing Multiple Files

import { createReadStream } from 'node:fs'
import { readdir } from 'node:fs/promises'
import { join } from 'node:path'
import { parseStream } from 'mdc-syntax/stream'

async function processMarkdownDirectory(dir: string) {
  const files = await readdir(dir)
  const mdFiles = files.filter(f => f.endsWith('.md'))

  const results = await Promise.all(
    mdFiles.map(async (file) => {
      const stream = createReadStream(join(dir, file))
      const result = await parseStream(stream)
      return { file, result }
    })
  )

  return results
}

// Usage
const results = await processMarkdownDirectory('./content')
console.log(`Processed ${results.length} files`)

Chunked Stream Processing

import { Readable } from 'node:stream'
import { parseStream } from 'mdc-syntax/stream'

// Create a stream from chunks
const chunks = [
  '# Hello World\n\n',
  'This is some content.\n\n',
  '::alert{type="info"}\n',
  'Important message\n',
  '::\n'
]

const stream = Readable.from(chunks)
const result = await parseStream(stream)

console.log(result.body)

Frontmatter Parsing

The parse functions automatically extract and parse YAML frontmatter:

const content = `---
title: My Document
tags:
  - javascript
  - markdown
author:
  name: John Doe
  email: john@example.com
---

# Content here
`

const result = parse(content)

console.log(result.data)
// {
//   title: 'My Document',
//   tags: ['javascript', 'markdown'],
//   author: { name: 'John Doe', email: 'john@example.com' }
// }

Table of Contents

The parse functions automatically generate a table of contents based on headings:

const content = `# Main Title

## Section 1

Some content here.

### Subsection 1.1

More content.

## Section 2

Final content.
`

const result = parse(content)

console.log(result.toc)
// {
//   title: 'Main Title',
//   depth: 2,
//   searchDepth: 2,
//   links: [
//     { id: 'section-1', text: 'Section 1', depth: 2, children: [
//       { id: 'subsection-11', text: 'Subsection 1.1', depth: 3 }
//     ]},
//     { id: 'section-2', text: 'Section 2', depth: 2 }
//   ]
// }

Excerpt Extraction

Content before the <!-- more --> comment is automatically extracted as an excerpt:

const content = `# Article Title

This is the introduction paragraph that will be used as an excerpt.

<!-- more -->

This is the full article content that won't appear in the excerpt.
`

const result = parse(content)

console.log(result.excerpt)
// MinimarkTree with only the content before <!-- more -->

Error Handling

import { parseStream } from 'mdc-syntax/stream'

try {
  const stream = createReadStream('content.md')
  const result = await parseStream(stream)
  console.log('Parsed successfully:', result.body)
}
catch (error) {
  console.error('Parse error:', error)
}

Performance Tips

  1. Use streaming for large files: For files larger than 1MB, use parseStream instead of reading the entire file into memory first.
  2. Use incremental parsing for real-time updates: When you need to update the UI during streaming (e.g., AI-generated content), use parseStreamIncremental.
  3. Batch process multiple files: Use Promise.all() to parse multiple files in parallel.
  4. Cache parsed results: If parsing the same content multiple times, cache the ParseResult to avoid redundant parsing.
// Good: Stream large files
const result = await parseStream(createReadStream('large-file.md'))

// Avoid: Loading entire large file into memory
const content = await readFile('large-file.md', 'utf-8') // Don't do this for large files
const result = parse(content)

See Also