Parse API
parse(source, options?)
Parses Comark content from a string and returns the complete parsed structure.
Parameters:
source- The markdown/Comark content as a stringoptions?- Parser options including plugins
Returns: ComarkTree object containing:
nodes- The parsed Comark AST nodesfrontmatter- Frontmatter data parsed from YAMLmeta- Additional metadata from plugins (e.g.,toc,summary)
Example:
import { parse } from 'comark'
const content = `---
title: Hello World
---
This is a simple example
`
const result = await parse(content)
console.log(result){
"nodes": [
["p", {}, "This is a simple example"]
],
"frontmatter": {
"title": "Hello World"
},
"meta": {}
}Frontmatter
The parse function automatically extracts and parses YAML frontmatter:
const content = `---
title: My Document
tags:
- javascript
- markdown
author:
name: John Doe
email: john@example.com
---
# Content here
`
const result = await parse(content)
console.log(result.frontmatter){
"title": "My Document",
"tags": ["javascript", "markdown"],
"author": { "name": "John Doe", "email": "john@example.com" }
}Table of Contents
The parse function automatically generates 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 = await parse(content)
console.log(result.meta.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 }
]
}HTML Parsing
HTML tags embedded in Comark content are parsed into AST nodes by default and can be mixed freely with Comark components and markdown syntax.
const content = `
<div class="note">
::alert{type="info"}
Hello <strong class="text-red-500">world</strong>
::
</div>
`
const result = await parse(content)
console.log(result.nodes)[
["div", { "class": "note" },
["alert", { "type": "info" },
"Hello ",
["strong", { "class": "text-red-500" }, "world"]
]
]
]To disable HTML parsing and treat tags as plain text, set html: false:
const result = await parse(content, { html: false })Summary
Content before the <!-- more --> comment is extracted as a summary when using the summary plugin:
import { parse } from 'comark'
import summary from 'comark/plugins/summary'
const content = `# Article Title
This is the introduction paragraph that will be used as a summary.
<!-- more -->
This is the full article content that won't appear in the summary.
`
const result = await parse(content, {
plugins: [summary()]
})
console.log(result.meta.summary)
// ComarkNode[] with only the content before <!-- more -->createParse(options?)
Creates a reusable parser function with pre-configured options. Unlike parse() which creates a new parser instance on each call, createParse() returns a parser function that can be called multiple times with the same configuration.
Parameters:
options?- Parser options (same asparse())
Returns: An async parser function (source: string) => Promise<ComarkTree>
Example:
import { createParse } from 'comark'
import highlight from 'comark/plugins/highlight'
import emoji from 'comark/plugins/emoji'
import toc from 'comark/plugins/toc'
// Create a parser with specific configuration
const parse = createParse({
autoUnwrap: true,
autoClose: true,
plugins: [
highlight({
themes: { light: 'github-light', dark: 'github-dark' }
}),
emoji(),
toc()
]
})
// Reuse the parser for multiple documents
const doc1 = await parse('# Document 1\n\nContent...')
const doc2 = await parse('# Document 2\n\nMore content...')
const doc3 = await parse('# Document 3\n\nEven more...')Use Cases
Here are some use cases for createParse():
Static Site Generator
Use Promise.all to parse all files in parallel. Since createParse() initializes the parser once, the returned function is safe to call concurrently.
import { createParse } from 'comark'
import { readdir, readFile, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import { renderHTML } from '@comark/html'
import highlight from 'comark/plugins/highlight'
import toc from 'comark/plugins/toc'
import emoji from 'comark/plugins/emoji'
async function buildSite(contentDir: string, outDir: string) {
// Create parser once with all desired plugins
const parse = createParse({
plugins: [
highlight({
themes: { light: 'github-light', dark: 'github-dark' }
}),
toc({ depth: 3 }),
emoji()
]
})
const files = await readdir(contentDir)
const mdFiles = files.filter(f => f.endsWith('.md'))
// Parse all files in parallel with the same parser instance
await Promise.all(
mdFiles.map(async (file) => {
const content = await readFile(join(contentDir, file), 'utf-8')
const tree = await parse(content)
const html = await renderHTML(tree)
await writeFile(join(outDir, file.replace('.md', '.html')), html)
})
)
console.log(`Built ${mdFiles.length} pages`)
}
await buildSite('./content', './dist')API Server
import { createParse } from 'comark'
import security from 'comark/plugins/security'
// Create parser once when server starts
const parse = createParse({
plugins: [
security() // Sanitize user-generated content
]
})
// Reuse parser for every request
app.post('/api/markdown', async (req, res) => {
try {
const tree = await parse(req.body.content)
res.json({ success: true, tree })
} catch (error) {
res.status(400).json({ error: 'Invalid markdown' })
}
})Benchmark
Using createParse() has several benefits over calling parse() multiple times:
- Performance: Parser and plugins are initialized once, not on every parse
- Consistency: All documents parsed with the same configuration
- Memory efficiency: Single parser instance handles multiple documents
- Ideal for batch processing: Perfect when parsing many files
import { parse, createParse } from 'comark'
import highlight from 'comark/plugins/highlight'
const content = '```js\nconsole.log("hello")\n```'
// ❌ Slow: Creates new parser + highlighter for each parse
console.time('parse x1000')
for (let i = 0; i < 1000; i++) {
await parse(content, {
plugins: [highlight()]
})
}
console.timeEnd('parse x1000')
// → ~8000ms (parser + highlighter recreated 1000 times)
// ✅ Fast: Reuses same parser + highlighter instance
console.time('createParse x1000')
const parse = createParse({
plugins: [highlight()]
})
for (let i = 0; i < 1000; i++) {
await parse(content)
}
console.timeEnd('createParse x1000')
// → ~800ms (10x faster! parser + highlighter created once)Options
Both parse() and createParse() accept the same ParseOptions:
| Option | Type | Default | Description |
|---|---|---|---|
autoUnwrap | boolean | true | Remove unnecessary <p> wrappers from single-element containers |
autoClose | boolean | true | Auto-close incomplete markdown syntax |
unwrap | boolean | string | string[] | false | Remove wrapper tags from the tree, hoisting their children (MDC unwrap behaviour). true unwraps p; a comma/whitespace-separated string or array unwraps the listed tags; '*' matches any tag. Tags apply sequentially (each descends one level), and adjacent text is merged into a single string. |
html | boolean | true | Parse embedded HTML tags into AST nodes |
linkify | boolean | true | Auto-convert URL-like text into links. Set false to disable |
headingIds | boolean | true | Auto-generate id attributes for h1–h6 headings. Set false to disable |
plugins | ComarkPlugin[] | [] | Array of plugins to apply |
Inline rendering
Use unwrap: 'p' (or just unwrap: true) to render markdown without the
wrapping <p>, which is handy for buttons, badges, and other inline hosts.
Adjacent text is merged into a single string, matching MDC's unwrap:
await parse('Hello **world**', { unwrap: 'p' })
// nodes: ['Hello ', ['strong', {}, 'world']]
await parse('a\n\nb', { unwrap: true })
// nodes: ['ab'] (paragraphs merged, no separator)Tags are applied sequentially, each descending one level into the result of
the previous one — so a space-separated (or comma-separated) list peels nested
wrappers. '*' matches any tag:
// Unwrap the <ul>, then the <li> inside it
await parse('- Buy milk', { unwrap: 'ul li' })
// nodes: ['Buy milk']On the framework components this is exposed as an unwrap shorthand prop:
<UButton><Comark :markdown="text" unwrap /></UButton>
<UButton><Comark :markdown="text" unwrap="ul li" /></UButton>
<UButton><Comark :markdown="text" :options="{ unwrap: 'p' }" /></UButton>