Comark vs markdown-it
TL;DR: markdown-it parses Markdown into a flat token stream and renders it to an HTML string. Comark parses with markdown-exit, a TypeScript rewrite of markdown-it that keeps its plugin API. Comark then converts the tokens into a tree document that renders natively in Vue, React, Svelte, and Angular, and to HTML and ANSI strings.
At a glance
| Comark | markdown-it | |
|---|---|---|
| Tokenizer | markdown-exit, a TypeScript rewrite of markdown-it | markdown-it |
| Output | MarkdownDocument tree, then HTML, framework components, or ANSI | HTML string from md.render(), flat tokens from md.parse() |
| Plugins | markdown-it parsing plugins, plus Comark plugins | markdown-it plugins with md.use() and the Ruler API |
| Renderer rules | Ignored, Comark has its own renderers | md.renderer.rules |
| Raw HTML | Parsed into nodes by default | Off by default (html: false) |
| Linkify | On by default | Off by default |
| Custom components in content | Yes, ::alert{type="info"} | No, only through plugins |
| Incomplete Markdown | autoClose completes the syntax | No handling |
| API | Async | Sync only |
Same tokenizer, different output
markdown-it has two steps. md.parse() returns a flat list of tokens, with _open and _close pairs. md.render() turns those tokens into an HTML string.
Comark replaces the second step. It converts the tokens into a tree of tuples, the document model:
import { parseMarkdown } from 'comark'
const document = await parseMarkdown('# Hello **World**')
// document.nodes:
// [['h1', { id: 'hello-world' }, 'Hello ', ['strong', {}, 'World']]]The document is JSON-safe. You can cache it, send it to a client, and render it with <MarkdownDocument> in Vue or React. Framework renderers create real components, not an HTML string that you insert with innerHTML.
Bring your markdown-it plugins
Comark accepts markdown-it plugins through markdownItPlugins in a Comark plugin. They're registered on the underlying markdown-exit instance:
import MarkdownIt from 'markdown-it'
import markdownItSub from 'markdown-it-sub'
const md = new MarkdownIt().use(markdownItSub)
const html = md.render('H~2~O is water')import { parseMarkdown } from 'comark'
import { defineComarkPlugin } from 'comark/parse'
import markdownItSub from 'markdown-it-sub'
const subscript = defineComarkPlugin(() => ({
name: 'subscript',
markdownItPlugins: [markdownItSub],
}))
const document = await parseMarkdown('H~2~O is water', { plugins: [subscript()] })Plugins that add inline rules, block rules, or core rules work, such as subscript, containers, and abbreviations. Plugins that only change md.renderer.rules have no effect, because Comark doesn't use the markdown-it renderer. Map the tag to a component instead. See markdown-it plugins for details.
Use Comark syntax in markdown-it
The reverse also works. If a tool already owns a markdown-it instance, such as VitePress, you can add Comark component and attribute syntax to it:
import { markdownItComponents } from 'comark/plugins/components'
import { markdownItAttributes } from 'comark/plugins/attributes'
md.use(markdownItComponents)
md.use(markdownItAttributes)See the components plugin and the VitePress example.
Defaults and security
markdown-it is safe by default. It disables raw HTML, and its validateLink rejects javascript:, vbscript:, file:, and non-image data: URLs.
Comark is open by default. Markdown link syntax with an unsafe URL doesn't become a link, but raw HTML is parsed into nodes and kept, including <script>. Attributes and component props are also kept as written. For untrusted input, add the security plugin with a blockedTags list, or set registerDefaultPlugins: false and register the plugins you need without html.
CommonMark differences
markdown-it follows the CommonMark spec. markdown-exit keeps that behavior, but Comark's conversion to its document model changes some output. For example, [foo] is a Comark span instead of a shortcut reference link, and tight and loose lists render the same way. autoClose is on by default, so *foo becomes emphasis. See Differences from CommonMark and GFM.
What markdown-it does well
- Safe defaults: raw HTML is off and link validation is built in.
- Plugin ecosystem: a very large number of plugins on npm. Comark can use the ones that add parsing rules.
- Maturity and speed: markdown-it is a mature, fast parser that many tools build on, such as VitePress and Markstream.
- Size: Comark adds a YAML parser, an HTML parser, and its own renderers, so it's larger.
If you need a synchronous HTML string from Markdown, markdown-it is the smaller tool.
FAQ
md.renderer.rules are ignored. Write a component for the tag instead. See markdown-it plugin compatibility.renderHtml() from @comark/html. The HTML is close to markdown-it output for standard Markdown, with the documented differences, such as heading IDs and language attributes on code blocks.streaming prop. See AI chat streaming.Comark vs marked
marked compiles Markdown to an HTML string with zero dependencies. Comark parses Markdown into a serializable document with components and streaming, then renders it to HTML, frameworks, or the terminal.
Comark vs remark
remark and unified transform Markdown through mdast and hast syntax trees with a large plugin ecosystem. Comark parses into a compact document with component syntax, streaming, and multi-framework renderers.