Comark vs Markstream
TL;DR: Markstream is a family of streaming Markdown renderers for AI chat, with packages for Vue 3, Vue 2, React, Svelte, and Angular. Comark solves the same problem with one parser and one serializable document for every renderer. It also adds component syntax with props and slots, plus HTML and terminal output.
At a glance
| Comark | Markstream | |
|---|---|---|
| Frameworks | Vue 3, React, Svelte 5, Angular 17+, Nuxt | Vue 3, Vue 2, React, Svelte 5, Angular 20+, Nuxt |
| String output | HTML and ANSI renderers | No, framework renderers only |
| Renderer maturity | All packages share one pre-1.0 version, none labeled alpha or beta | Vue stable, React beta, Svelte beta or experimental, Angular alpha |
| Incomplete Markdown | autoClose completes the syntax | Loading nodes until the final prop is set |
| Components in content | ::flight-card{from="CDG"} with props and slots | HTML-like custom tags such as <thinking> |
| Parser output | MarkdownDocument with tuple nodes, JSON-safe | ParsedNode[] from stream-markdown-parser |
| Raw HTML by default | Kept as written, add the security plugin with blockedTags for untrusted input | Safe HTML policy on by default |
| Long documents | Incremental parse in streaming mode | Virtualization, batching, and workers |
| Diagrams | Mermaid plugin | Progressive Mermaid, D2, and AntV Infographic |
Same problem, two designs
Both projects render Markdown that an LLM sends token by token. Both are based on markdown-it: Markstream uses markdown-it-ts, and Comark uses markdown-exit, a TypeScript rewrite of markdown-it.
The design differs after parsing. Markstream gives each framework a renderer package with chat-oriented presets, such as mode="chat". Comark parses into one document model that every renderer consumes the same way.
<script setup lang="ts">
import MarkdownRender from 'markstream-vue'
import 'markstream-vue/index.css'
defineProps<{ content: string, isDone: boolean }>()
</script>
<template>
<MarkdownRender mode="chat" :content="content" :final="isDone" />
</template><script setup lang="ts">
import { Markdown } from '@comark/vue'
defineProps<{ content: string, isStreaming: boolean }>()
</script>
<template>
<Suspense>
<Markdown :value="content" :streaming="isStreaming" caret />
</Suspense>
</template>Incomplete Markdown: completed syntax or loading states
Markstream marks unclosed constructs as mid-state loading nodes. Your components can show a placeholder until the closing syntax arrives or the final prop is set.
Comark completes the syntax instead. On every parse, autoClose closes unterminated bold, inline code, links, tables, and components. An open code fence renders as a growing code block. In streaming mode, Comark also drops a half-typed opener at the end and reuses the output of completed blocks. Each frame is a complete document, so your components receive normal props and children.
Components with props and slots
Markstream renders custom components through HTML-like tags. You register a component for a tag such as <thinking>, and the parser emits a node with the tag body and raw attributes.
Comark makes components part of the Markdown syntax. A model or an author can write typed props, nested Markdown, and named slots:
::flight-card{from="CDG" to="JFK" price="342"}
Departs at 8:15 AM, nonstop.
#footer
Prices include taxes.
::The parser produces data: ['flight-card', { from: 'CDG', to: 'JFK', price: '342' }, ...]. The renderer maps the tag to a component you registered, so nothing in the Markdown executes code. See component syntax and generative UI.
One document, every renderer
parseMarkdown() returns a JSON-safe MarkdownDocument. You can parse on the server, cache the result, and render it in any framework:
import { parseMarkdown } from 'comark'
import { renderHtmlFromDocument } from '@comark/html'
const document = await parseMarkdown(markdown)
const html = await renderHtmlFromDocument(document) // emails, RSS, static pagesThe same document renders with <MarkdownDocument> in Vue, React, and Svelte. It renders with <comark-markdown-document> in Angular and with renderAnsiFromDocument() in a terminal. Markstream can also pre-parse on a server or in a worker and pass nodes to a renderer. It has no HTML string or terminal output.
Security defaults
Markstream applies htmlPolicy="safe" by default. It blocks tags such as script, iframe, and form, removes on* attributes, and filters dangerous URL protocols, also during streaming.
Comark doesn't create a link from Markdown link syntax with a javascript:, vbscript:, or data:text/html URL. Everything else is kept as written. Raw HTML, attributes, and component props can contain <script> tags, on* handlers, and javascript: URLs.
For LLM output or user content, add the security plugin. It removes event handlers and unsafe URLs, and drops the tags that you list in blockedTags:
import security from '@comark/vue/plugins/security'
const plugins = [
security({ blockedTags: ['script', 'iframe', 'embed', 'form', 'object', 'style'] }),
]What Markstream does well
- Framework reach: Markstream supports Vue 2.6 and 2.7 through a compatibility port. Comark has no Vue 2 renderer.
- Long responses: virtualization with
max-live-nodes, render batching, and worker-based parsing keep long documents responsive. Comark has no virtualization. - Chat built-ins: progressive Mermaid in a worker, KaTeX in a worker, D2, AntV Infographic, typewriter and fade effects, and enhanced code and diff blocks.
- Safe by default: the safe HTML policy protects untrusted output without extra setup.
If you build a Vue chat UI with very long responses, or you must support Vue 2, Markstream is a strong choice.
FAQ
blockedTags for untrusted input.markdownItPlugins, as long as they add parsing rules. Plugins that only change md.renderer output have no effect. See markdown-it plugins.<Markdown> and set streaming while part.state === 'streaming'. See AI chat streaming and the Nuxt AI SDK example.Comark vs Markdoc
Comark and Markdoc both treat Markdown as data with runtime parsing. Comark adds streaming auto-close, a more compact AST, and renderers for Vue, Svelte, Angular, and ANSI.
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.