Skip to content

Streaming Markdown Rendering Solutions โ€‹

This document provides a technical comparison of streaming Markdown rendering solutions: Incremark, ant-design-x, and markstream-vue. Each solution has its own design philosophy and strengths.

Full Pipeline Comparison โ€‹

ant-design-x Pipeline โ€‹

User Input (Streaming Markdown)
        โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  useTyping Hook                                         โ”‚
โ”‚    - Consumes plain text character by character          โ”‚
โ”‚    - Outputs text chunks with fade-in markers            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  useStreaming Hook                                       โ”‚
โ”‚    - Regex detects incomplete tokens (links, images...)  โ”‚
โ”‚    - Caches incomplete parts, outputs only complete MD   โ”‚
โ”‚    โ†“                                                    โ”‚
โ”‚  Parser (marked.js)                                      โ”‚
โ”‚    - Full parse: content โ†’ HTML string                   โ”‚
โ”‚    โ†“                                                    โ”‚
โ”‚  Renderer (html-react-parser)                            โ”‚
โ”‚    - HTML string โ†’ React components                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
        โ†“
    React DOM

Key Characteristics:

  • Full re-parse on each content change using marked.parse()
  • Typewriter animation operates on plain text strings
  • Uses HTML string as intermediate format

markstream-vue Pipeline โ€‹

User Input (Markdown String)
        โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Preprocessing                                           โ”‚
โ”‚    - Fix streaming edge cases with regex                 โ”‚
โ”‚    - "- *" โ†’ "- \*", strip dangling markers, etc.        โ”‚
โ”‚    โ†“                                                    โ”‚
โ”‚  markdown-it.parse()                                     โ”‚
โ”‚    - Full parse โ†’ Token array                            โ”‚
โ”‚    โ†“                                                    โ”‚
โ”‚  processTokens()                                         โ”‚
โ”‚    - Token โ†’ ParsedNode[] (custom AST)                   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Vue Component Rendering                                 โ”‚
โ”‚    - <transition> for fade-in animation                  โ”‚
โ”‚    - Node type โ†’ Component mapping                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
        โ†“
    Vue DOM

Key Characteristics:

  • Full re-parse on each content change using markdown-it.parse()
  • Preprocessing layer handles streaming edge cases
  • Uses Vue <transition> for typewriter effect

Incremark Pipeline โ€‹

User Input (Streaming Markdown Chunks)
        โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  IncremarkParser.append(chunk)                           โ”‚
โ”‚    - Incrementally updates buffer (only new parts)       โ”‚
โ”‚    - Detects stable boundaries (empty lines, headings)   โ”‚
โ”‚    - Stable parts โ†’ completedBlocks (parsed once)        โ”‚
โ”‚    - Unstable parts โ†’ pendingBlocks (re-parse)           โ”‚
โ”‚    โ†“                                                    โ”‚
โ”‚  Output: ParsedBlock[] with mdast AST                    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  BlockTransformer (Optional Middleware)                  โ”‚
โ”‚    - Typewriter effect: sliceAst() truncates AST         โ”‚
โ”‚    - Maintains TextChunk[] for fade-in animation         โ”‚
โ”‚    - Can be skipped for instant rendering                โ”‚
โ”‚    โ†“                                                    โ”‚
โ”‚  Output: DisplayBlock[] with truncated AST               โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Vue / React Component Rendering                         โ”‚
โ”‚    - AST node โ†’ Component mapping                        โ”‚
โ”‚    - TextChunks wrapped with fade-in animation           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
        โ†“
    Vue / React DOM

Key Characteristics:

  • Incremental parsing: only newly stable blocks are parsed
  • Typewriter animation operates at AST node level
  • Provides adapters for both Vue and React

Core Differences โ€‹

Dimensionant-design-xmarkstream-vueIncremark
ParsingFull re-parse (marked.js)Full re-parse (markdown-it)Incremental (micromark)
Per-chunk ComplexityO(n)O(n)O(k) where k = new block size
Total ComplexityO(n ร— chunks) โ‰ˆ O(nยฒ)O(n ร— chunks) โ‰ˆ O(nยฒ)O(n)
Edge Case HandlingRegex token detectionPreprocessing layerStable boundary detection
Typewriter AnimationText string levelVue TransitionPreserves Markdown structure
Output FormatHTML stringCustom ASTmdast (remark compatible)
FrameworkReactVueVue + React

Parsing Strategy โ€‹

Full Re-parse vs Incremental โ€‹

Full Re-parse (ant-design-x & markstream-vue):

Chunk 1: "# Hello"        โ†’ Parse entire content
Chunk 2: "# Hello\nWorld" โ†’ Parse entire content again
Chunk 3: "# Hello\nWorld\n\n- item" โ†’ Parse entire content again

Each new chunk triggers a complete re-parse of all accumulated content.

Incremental Parsing (Incremark):

Chunk 1: "# Hello"     โ†’ Parse โ†’ Block 1 (heading) โœ“ Done
Chunk 2: "\n\nWorld"   โ†’ Parse โ†’ Block 2 (paragraph) โ† Only this part
Chunk 3: "\n\n- item"  โ†’ Parse โ†’ Block 3 (list) โ† Only this part

Completed blocks are cached and not re-parsed. Only the pending portion is processed.

Complexity Analysis โ€‹

In streaming scenarios with many chunks:

  • Full re-parse: O(n) ร— number of chunks
  • Incremental: O(k) per chunk, where k is the new block size

For typical AI responses (10-50 blocks), both approaches perform acceptably. The difference becomes more noticeable with larger documents or high-frequency chunk arrivals.


Streaming Edge Case Handling โ€‹

All solutions must handle incomplete Markdown syntax during streaming. Each takes a different approach:

ApproachHow It WorksTrade-offs
IncremarkDetects stable boundaries before parsingClean structure; may buffer some content
ant-design-xRegex patterns detect incomplete tokensImmediate output; requires regex maintenance
markstream-vuePreprocesses content before parsingWorks with any parser; many edge cases to handle

Typewriter Animation โ€‹

SolutionLevelMechanism
IncremarkAST nodessliceAst() truncates AST, TextChunk[] tracks fade-in
ant-design-xText stringCharacter-by-character text slicing
markstream-vueComponentVue <transition> on component mount

Each approach has its trade-offs:

  • AST-level allows structural awareness during animation
  • Text-level is simpler and framework-agnostic
  • Component-level integrates naturally with Vue's reactivity system

Rendering Optimization โ€‹

markstream-vue provides additional rendering optimizations:

FeatureDescription
VirtualizationOnly renders visible nodes in DOM
Batch renderingUses requestIdleCallback for progressive rendering
Viewport priorityDefers off-screen node rendering

Considerations for streaming scenarios:

In typical AI streaming use cases:

  • Content arrives gradually (natural batching)
  • User focus is at the bottom (watching new content)
  • Typical response size is 10-50 blocks
  • Typewriter effect provides progressive rendering

Virtualization provides significant benefits when:

  • Browsing historical content (not actively streaming)
  • Rendering very long documents (100+ blocks)
  • Users scroll quickly through content

Summary โ€‹

Each Solution's Focus โ€‹

ant-design-x โ€‹

Focus: Complete AI Chat UI Solution

  • Provides Bubble, Sender, Conversations components
  • Deep integration with Ant Design ecosystem
  • Built-in support for special blocks like <thinking>
  • Quick to set up for Ant Design users

Suited for: Teams building AI chat interfaces within the Ant Design ecosystem

markstream-vue โ€‹

Focus: Feature-rich Markdown Rendering

  • Virtualization for large document handling
  • Batch rendering with adaptive performance
  • Comprehensive edge case preprocessing
  • Extensive customization options

Suited for: Vue applications with large documents or chat history browsing needs

Incremark โ€‹

Focus: Efficient Incremental Parsing

  • Incremental parsing: Completed blocks are never re-parsed, reducing CPU work significantly in long streaming sessions
  • Cross-framework: Same core library works for both Vue and React, reducing maintenance cost for multi-framework teams
  • remark ecosystem compatible: Standard mdast output allows using remark plugins for syntax extensions
  • Structural typewriter: Animation preserves Markdown structure, with plugin system for custom behaviors (e.g., show images immediately)

Suited for: Applications with long streaming content, multi-framework teams, or those needing remark plugin compatibility


Quick Reference โ€‹

Your PriorityConsider
Ant Design ecosystem integrationant-design-x
Large document virtualizationmarkstream-vue
Vue-only applicationmarkstream-vue
Long streaming sessions / many chunksIncremark
Vue + React in same codebaseIncremark
Need remark pluginsIncremark

Conclusion โ€‹

Each solution addresses streaming Markdown with different priorities:

  • ant-design-x provides a complete AI chat UI solution tightly integrated with Ant Design
  • markstream-vue offers rich features and rendering optimizations for Vue applications
  • Incremark focuses on parsing efficiency and cross-framework flexibility

The choice depends on your specific requirements: ecosystem fit, document size, framework needs, and performance priorities.

Released under the MIT License.