vue-stream-markdown

repository·main·Indexed 42 days ago

https://github.com/jinghaihan/vue-stream-markdown

A specialized Markdown renderer for Vue 3 applications optimized for streaming content, such as LLM outputs. It uses the Comark incremental parser to prevent UI jitter by handling incomplete Markdown nodes and providing visual loading states. Supports GFM, KaTeX math, Mermaid diagrams, and multilingual animations (CJK and English). Version 2.0.0-beta.5 introduces a modular architecture with optional extensions for code highlighting, math, and diagrams.

Tokens
79.6K
Snippets
258
Records
336
Agent score
53%

What's inside vue-stream-markdown

  1. Overview of vue-stream-markdown

    main

    vue-stream-markdown is a Markdown renderer specifically optimized for streaming scenarios (such as LLM responses). It uses the Comark incremental parser to handle incomplete Markdown nodes, providing loading states for images, tables, and code blocks to prevent visual jitter during the streaming process.

    Key Capabilities

    • Streaming Optimization: Smoothly handles incomplete syntax with visual loading states.
    • Extensible Rendering: Supports syntax highlighting (via @stream-markdown/code), diagramming (Mermaid/Beautiful Mermaid), and mathematical typesetting (KaTeX).
    • Customization: Allows mapping custom tags directly to Vue components and replacing any built-in UI elements.
    • Security: Includes built-in content hardening, URL validation, and protocol blocking to protect against malicious Markdown.
    • Styling: Uses scoped styles under .stream-markdown with semantic data-stream-markdown attributes, following a design system inspired by Shadcn UI. It is self-contained and does not require Tailwind CSS or UnoCSS.
  2. Supported Typography and Markdown Styles

    main

    The vue-stream-markdown component supports a wide range of standard and GitHub Flavored Markdown (GFM) styles. When rendering Markdown content, the following elements are automatically styled for optimal readability and responsiveness:

    • Headings: All six levels (# to ######) with responsive font sizes and consistent vertical spacing.
    • Text Formatting: Bold (**), Italic (_), and Strikethrough (~~).
    • Inline Code: Monospace font with a subtle background.
    • Links: Styled with underlines, hover/focus states, and accessible color contrast.
    • Lists: Both Unordered (-) and Ordered (1.) lists, including support for nested levels with proper indentation.
    • Blockquotes: Styled with a left accent border and subtle background.
    • Code Blocks: Syntax highlighting is provided via Shiki.
    • Images: Responsive sizing with aspect ratio preservation.
    • Tables: Styled with borders and hover states.
    • Horizontal Rules: Visual separators (---).
    • Paragraphs: Automatic wrapping with optimized line height and spacing.
  3. Extend rendering with specialized packages

    main

    The core library handles basic markdown, but advanced features are provided via separate extension packages. To support specific content types, you must include the corresponding package:

    • Code Highlighting: For syntax highlighting in code blocks.
    • Math: For rendering mathematical notation.
    • Mermaid: For rendering diagrams.
    • Beautiful Mermaid: For enhanced Mermaid diagram rendering.

    Note: Some integrations (like ECharts) may be custom implementations within specific environments like the Playground and are not part of the core library extensions.

  4. Understand the Rendering Pipeline

    main

    The rendering pipeline follows a single rule: callers must always provide the complete Markdown source currently available. The pipeline operates in two modes:

    1. Streaming Mode (mode="streaming"): The pipeline uses Markmend completion to repair unfinished syntax (like partial links, tables, or code fences) in the changing tail of the document. This ensures partial structures remain readable as text arrives.
    2. Static Mode (mode="static"): Completion is skipped, and the source is parsed as-is.

    The Workflow: Complete source $\rightarrow$ Rendering mode $\rightarrow$ Markmend completion (if streaming) $\rightarrow$ Incremental Comark parsing $\rightarrow$ Stable Vue nodes $\rightarrow$ Rich renderers (if configured).

  5. Understand streaming vs static parsing in Vue Stream Markdown

    main

    The library supports two modes of operation:

    1. Streaming Mode: During active streaming, the underlying parser (Markmend) completes supported unfinished syntax (e.g., closing an open code block or list item) to ensure the UI renders correctly as chunks arrive.
    2. Static Mode: Parses the original source without attempting to complete unfinished syntax. This is typically used when the stream has finished or for pre-rendered content.

    Use streaming mode when you want to provide a real-time, growing response experience (like an LLM output).

  6. How incremental parsing and stable Vue output work

    main

    To ensure performance and visual stability during streaming:

    • Incremental Parsing: The Markdown instance uses a long-lived Comark parser. Instead of re-parsing the entire document from scratch, it reuses the stable source prefix and only parses the changed tail. If a parse fails, the last successfully rendered document remains visible.
    • Stable Vue Output: The Comark document is rendered directly to Vue nodes. Top-level blocks maintain stable identities, meaning growing content at the end of a stream does not cause earlier paragraphs, controls, or interactive components to be replaced or re-rendered unnecessarily.
    • Synchronous vs. Asynchronous Rendering: Simple semantic elements render synchronously. Complex features (math, diagrams, code highlighting) use dedicated Vue components only when required by the document content.
  7. How the Comark parser works in streaming mode

    main

    Vue Stream Markdown utilizes a long-lived Comark parser instance for each component. Instead of re-parsing the entire string from scratch, you pass the complete source to the content prop. The parser retains incremental state and reuses the stable prefix between updates, which is optimized for streaming content.

    If you need to use the parser independently of Vue, you can use createMarkmendParser() from @markmend/parser. For completion-only logic without the full parser, use @markmend/core.

    import { createMarkmendParser } from '@markmend/parser'
    
    const parser = createMarkmendParser()
    // Pass the full source; the parser handles incremental updates internally
    const result = await parser.parse('[Documentation](', 'streaming')
    
    // result.document: The parsed document
    // result.completion: Information about the syntax being completed
    // e.g., { type: 'link', phase: 'destination' }
  8. Security considerations for HTML rendering

    main

    The library includes a built-in Comark security plugin that validates links and image URLs using hardenOptions.

    Key Security Behaviors:

    • Event Handlers: Vue event-handler sources (e.g., onclick) are not executed as template expressions. Instead, parsed attributes are passed as ordinary VNode attributes.
    • Custom Components: You are responsible for the security of custom components. Only register components you trust. If a custom component interprets attributes as executable code or injects raw HTML, that component is responsible for its own security boundary.
  9. How Mermaid extensions fallback works

    main

    You can use both @stream-markdown/beautiful-mermaid and @stream-markdown/mermaid simultaneously to get the best of both worlds. The selection order is deterministic:

    1. Beautiful Mermaid renders diagram types it supports.
    2. Standard Mermaid renders any unsupported Beautiful Mermaid diagram types.
    3. If no configured extension supports the diagram, the source code block remains visible as a standard code block.

    This setup allows you to have polished styling for common diagrams while maintaining full syntax coverage for complex ones.

    import { beautifulMermaid } from '@stream-markdown/beautiful-mermaid'
    import { mermaid } from '@stream-markdown/mermaid'
    
    const extensions = {
      beautifulMermaid: beautifulMermaid(),
      mermaid: mermaid(),
    }