Skip to content

@sylphx/synth-md

High-performance Markdown parser - 26-42x faster than remark.

Installation

bash
npm install @sylphx/synth @sylphx/synth-md

Quick Start

typescript
import { parse } from '@sylphx/synth-md'

const tree = parse('# Hello **World**')

// Access nodes
for (const node of tree.nodes) {
  console.log(node.type, node.data)
}

Functions

parse

Parse Markdown synchronously:

typescript
import { parse } from '@sylphx/synth-md'

const tree = parse(markdown, {
  buildIndex: false,      // Build query index (slower)
  plugins: [],            // Plugins to apply
  useNodePool: true,      // Object pooling (default: true)
  useBatchTokenizer: false, // Batch processing for large docs
  batchSize: 16           // Batch size (1-128)
})

parseAsync

Parse with async plugin support:

typescript
import { parseAsync } from '@sylphx/synth-md'

const tree = await parseAsync(markdown, {
  plugins: [asyncPlugin]
})

createParser

Create a reusable parser instance:

typescript
import { createParser } from '@sylphx/synth-md'

const parser = createParser()
  .use(plugin1)
  .use(plugin2)

const tree1 = parser.parse(doc1)
const tree2 = parser.parse(doc2)

Node Types

Block Elements

TypeDescriptionData Fields
rootDocument root-
headingATX/Setext headingdepth: 1-6
paragraphParagraph-
codeBlockFenced/indented codelanguage?: string, meta?: string
blockquoteBlock quote-
listOrdered/unordered listordered: boolean, start?: number
listItemList itemchecked?: boolean (for task lists)
horizontalRuleThematic break-
htmlHTML blockvalue: string
tableGFM table-
tableRowTable row-
tableCellTable cell`align?: 'left'

Inline Elements

TypeDescriptionData Fields
textPlain textvalue: string
emphasisItalic-
strongBold-
codeInline codevalue: string
linkLinkurl: string, title?: string
imageImageurl: string, alt?: string, title?: string
strikethroughGFM strikethrough-
autolinkGFM autolinkurl: string
hardBreakHard line break-
softBreakSoft line break-

Parser Class

typescript
import { Parser } from '@sylphx/synth-md'

const parser = new Parser()

// Parse
const tree = parser.parse(markdown, options)

// Async parse
const tree = await parser.parseAsync(markdown, options)

// Register plugins
parser.use(plugin)

// Get query index (if built)
const index = parser.getIndex()

Built-in Plugins

typescript
import {
  addHeadingIds,
  tableOfContents,
  uppercaseHeadings,
  addCodeLineNumbers,
  removeComments,
  wrapParagraphs
} from '@sylphx/synth-md'

const tree = parse(markdown, {
  plugins: [addHeadingIds, tableOfContents]
})

// Access plugin data
console.log(tree.meta.data?.toc)

Streaming

parseStream

Parse from a stream:

typescript
import { parseStream } from '@sylphx/synth-md'
import { createReadStream } from 'fs'

const stream = createReadStream('large.md', { encoding: 'utf8' })
const tree = await parseStream(stream)

parseWithProgress

Parse with progress tracking:

typescript
import { parseWithProgress } from '@sylphx/synth-md'

const tree = await parseWithProgress(text, (progress) => {
  console.log(`${progress.percent}% complete`)
})

StreamingMarkdownParser

Manual streaming:

typescript
import { StreamingMarkdownParser } from '@sylphx/synth-md'

const parser = new StreamingMarkdownParser()

parser.on('node', (node) => console.log(node.type))
parser.on('end', (tree) => console.log('Done'))

parser.write('# Hello\n')
parser.write('\nWorld')
await parser.end()

Incremental Parsing

For real-time editing with 10-100x speedup:

typescript
import { IncrementalMarkdownParser, detectEdit } from '@sylphx/synth-md'

const parser = new IncrementalMarkdownParser()
parser.parse(originalText)

// After edit
const edit = detectEdit(originalText, newText)
const { tree, stats } = parser.update(newText, edit)

console.log(`Token reuse: ${stats.tokenReuseRate * 100}%`)
console.log(`Speedup: ${stats.speedup}x`)

Performance Options

typescript
// Maximum speed (default)
parse(text)

// With query index (slower build, faster queries)
parse(text, { buildIndex: true })

// For large documents (>10KB)
parse(text, { useBatchTokenizer: true, batchSize: 32 })

// Disable pooling (for debugging)
parse(text, { useNodePool: false })

Examples

Extract All Headings

typescript
const tree = parse(markdown)

const headings = tree.nodes
  .filter(n => n.type === 'heading')
  .map(n => ({
    level: n.data?.depth,
    text: getTextContent(tree, n)
  }))

Build Table of Contents

typescript
import { parse, traverse } from '@sylphx/synth-md'

function buildTOC(markdown: string) {
  const tree = parse(markdown)
  const toc = []

  traverse(tree, {
    heading: (ctx) => {
      toc.push({
        level: ctx.node.data?.depth,
        line: ctx.node.span?.start.line
      })
    }
  })

  return toc
}

Extract Code Blocks

typescript
const codeBlocks = tree.nodes
  .filter(n => n.type === 'codeBlock')
  .map(n => ({
    language: n.data?.language || 'text',
    code: tree.meta.source.slice(
      n.span!.start.offset,
      n.span!.end.offset
    )
  }))

CommonMark Compliance

Full CommonMark specification support:

  • ATX and Setext headings
  • Fenced and indented code blocks
  • Block quotes (nested)
  • Ordered and unordered lists (nested)
  • Links and images
  • Emphasis and strong
  • Inline code
  • Hard and soft line breaks
  • HTML blocks
  • Horizontal rules

GFM Extensions

  • Tables with alignment
  • Strikethrough
  • Task lists
  • Autolinks

Performance

DocumentRemarkSynthSpeedup
Small (100B)0.084ms0.0015ms56x
Medium (500B)0.448ms0.0078ms57x
Large (25KB)28.4ms0.392ms72x
Huge (250KB)58.8ms0.786ms75x

Released under the MIT License.