Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
Markstream Vue
https://github.com/simon-he95/markstream-vue
Admin
Markstream Vue provides fast, streaming-friendly Markdown rendering for Vue 3, featuring progressive
...
Tokens:
62,819
Snippets:
486
Trust Score:
9.7
Update:
4 months ago
Context
Skills
Chat
Benchmark
78.2
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# markstream-vue ## Introduction **markstream-vue** is a high-performance Vue 3 Markdown renderer optimized for streaming scenarios and large documents. It provides progressive rendering capabilities with native support for incomplete or frequently updated tokenized Markdown, making it ideal for real-time chat applications, AI-generated content streams, and live documentation editors. The library leverages a streaming-first architecture that minimizes re-rendering and provides efficient DOM updates, ensuring smooth performance even when rendering lengthy documents character by character. The component ecosystem includes Monaco-powered code blocks with streaming diff support, progressive Mermaid diagram rendering that updates as syntax becomes available, KaTeX math formula support with optional worker-based processing, inline HTML support with streaming-safe auto-closing, admonition blocks (note, warning, tip, danger), and a flexible node-based architecture allowing custom Vue components to be embedded directly in Markdown content. Built with TypeScript-first design, the library offers complete type definitions, IntelliSense support, and works out of the box in Vue 3 projects with zero configuration required. Optional peer dependencies (Monaco, Mermaid, KaTeX, Shiki) keep the core bundle lightweight while enabling rich features on demand. ## API Reference ### Installation and Setup Install the package with optional peer dependencies for specific features. ```bash # Core installation pnpm add markstream-vue # Optional: Enable all features pnpm add shiki stream-markdown stream-monaco mermaid katex ``` ```vue <!-- Basic setup in main.ts or App.vue --> <script setup lang="ts"> import MarkdownRender from 'markstream-vue' import 'markstream-vue/index.css' import 'katex/dist/katex.min.css' // If using math import 'mermaid/dist/mermaid.css' // If using diagrams const content = '# Hello World\n\nThis is **markstream-vue**!' </script> <template> <MarkdownRender :content="content" /> </template> ``` ### MarkdownRender Component Main component that renders Markdown AST content with built-in node components. ```vue <script setup lang="ts"> import MarkdownRender from 'markstream-vue' import { ref } from 'vue' const content = ref('') // Simulate streaming content const fullText = `# Streaming Demo This content appears **character by character**. ## Features - Progressive rendering - Minimal re-renders - Smooth performance \`\`\`typescript const example = 'code blocks stream too' console.log(example) \`\`\` ` let index = 0 const streamInterval = setInterval(() => { if (index < fullText.length) { content.value += fullText[index] index++ } else { clearInterval(streamInterval) } }, 30) </script> <template> <MarkdownRender :content="content" custom-id="demo" :typewriter="true" :viewport-priority="true" :code-block-stream="true" /> </template> <style> @import 'modern-css-reset'; @layer components { @import 'markstream-vue/index.css'; } [data-custom-id='demo'] { max-width: 800px; margin: 0 auto; } </style> ``` ### getMarkdown Parser Function Creates a configured markdown-it-ts instance with required plugins and custom options. ```typescript import { getMarkdown, parseMarkdownToStructure } from 'stream-markdown-parser' import type { GetMarkdownOptions } from 'stream-markdown-parser' // Basic usage const md = getMarkdown('editor-123') const result = md.render('# Hello World') console.log(result) // HTML output // With custom plugins and configuration const options: GetMarkdownOptions = { html: true, linkify: true, typographer: true, plugin: [ [require('markdown-it-container'), 'warning'], require('markdown-it-abbr') ], apply: [ (md) => { // Custom inline rule md.inline.ruler.before('escape', 'custom', (state, silent) => { // Custom parsing logic return false }) } ], i18n: { 'common.copy': 'Copy Code', 'common.expand': 'Expand' } } const customMd = getMarkdown('editor-custom', options) const html = customMd.render('# Custom Parser') ``` ### parseMarkdownToStructure Function Converts Markdown strings into AST nodes for direct rendering control. ```typescript import { getMarkdown, parseMarkdownToStructure } from 'stream-markdown-parser' import type { ParseOptions, ParsedNode } from 'stream-markdown-parser' const md = getMarkdown() const markdown = `# Title This is a paragraph with **bold** and *italic* text. \`\`\`javascript const x = 42 \`\`\` ` // Basic parsing const nodes: ParsedNode[] = parseMarkdownToStructure(markdown, md) console.log(nodes) // Output: Array of node objects with type, children, props, etc. // With transformation hooks const parseOptions: ParseOptions = { preTransformTokens(tokens) { // Modify tokens before processing return tokens.map(token => { if (token.type === 'fence' && token.info === 'typescript') { token.info = 'ts' // Normalize language } return token }) }, postTransformNodes(nodes) { // Modify nodes after parsing return nodes.map(node => { if (node.type === 'code_block') { node.props = { ...node.props, theme: 'dracula' } } return node }) } } const transformedNodes = parseMarkdownToStructure(markdown, md, parseOptions) ``` ```vue <!-- Rendering pre-parsed nodes --> <script setup lang="ts"> import MarkdownRender from 'markstream-vue' import { getMarkdown, parseMarkdownToStructure } from 'stream-markdown-parser' import { ref, watch } from 'vue' const rawContent = ref('# Hello\n\nInitial content') const nodes = ref([]) const md = getMarkdown() watch(rawContent, (newContent) => { nodes.value = parseMarkdownToStructure(newContent, md) }, { immediate: true }) // Now you can modify nodes before rendering function addMetadata() { nodes.value = nodes.value.map(node => ({ ...node, meta: { timestamp: Date.now() } })) } </script> <template> <MarkdownRender :nodes="nodes" custom-id="parsed" /> </template> ``` ### setCustomComponents Function Registers custom node renderers to override default components. ```vue <!-- CustomImageNode.vue --> <script setup lang="ts"> import { ref } from 'vue' interface Props { node: { type: 'image' props: { src: string alt?: string title?: string } } } const props = defineProps<Props>() const isOpen = ref(false) const loaded = ref(false) function openLightbox() { isOpen.value = true } </script> <template> <div class="custom-image-wrapper"> <img :src="node.props.src" :alt="node.props.alt" :title="node.props.title" @click="openLightbox" @load="loaded = true" class="cursor-pointer hover:opacity-90 transition-opacity" /> <Teleport to="body"> <div v-if="isOpen" class="lightbox" @click="isOpen = false"> <img :src="node.props.src" class="lightbox-image" /> </div> </Teleport> </div> </template> <style scoped> .lightbox { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.9); display: flex; align-items: center; justify-content: center; z-index: 9999; } .lightbox-image { max-width: 90vw; max-height: 90vh; } </style> ``` ```typescript // app.ts - Register custom components import { setCustomComponents, removeCustomComponents } from 'markstream-vue' import CustomImageNode from './CustomImageNode.vue' import CustomCodeBlock from './CustomCodeBlock.vue' import CustomLinkNode from './CustomLinkNode.vue' // Scoped registration (recommended) setCustomComponents('docs', { image: CustomImageNode, code_block: CustomCodeBlock, link: CustomLinkNode }) // Global registration (affects all MarkdownRender instances without custom-id) setCustomComponents(undefined, { image: CustomImageNode }) // Remove custom components when no longer needed removeCustomComponents('docs') ``` ```vue <!-- Using custom components in render --> <template> <MarkdownRender custom-id="docs" :content="markdownWithImages" /> </template> ``` ### CodeBlockNode Component Monaco-powered code block with streaming, diff markers, and interactive toolbar. ```vue <script setup lang="ts"> import { CodeBlockNode } from 'markstream-vue' import 'stream-monaco/esm/index.css' import { ref } from 'vue' const codeNode = ref({ type: 'code_block', lang: 'typescript', value: `interface User { id: number name: string email: string } function getUser(id: number): Promise<User> { return fetch(\`/api/users/\${id}\`) .then(res => res.json()) }`, props: { filename: 'user.ts', showLineNumbers: true, highlightLines: '1-4,6' } }) // Simulate streaming code updates let additions = [ '\n\n// Add error handling', '\nfunction getUserSafe(id: number): Promise<User | null> {', '\n return getUser(id).catch(() => null)', '\n}' ] function streamCode() { let i = 0 const interval = setInterval(() => { if (i < additions.length) { codeNode.value = { ...codeNode.value, value: codeNode.value.value + additions[i] } i++ } else { clearInterval(interval) } }, 500) } </script> <template> <div class="code-demo"> <button @click="streamCode">Stream Code Updates</button> <CodeBlockNode :node="codeNode" :monaco-options="{ fontSize: 14, minimap: { enabled: false }, scrollBeyondLastLine: false }" :auto-expand="true" :viewport-priority="true" :show-header="true" :show-copy-button="true" :show-expand-button="true" custom-id="demo" > <template #toolbar> <button class="custom-action">Run Code</button> </template> </CodeBlockNode> </div> </template> ``` ### MarkdownCodeBlockNode Component Lightweight syntax highlighting using Shiki instead of Monaco for SSR-friendly scenarios. ```vue <script setup lang="ts"> import { MarkdownCodeBlockNode } from 'markstream-vue' import { ref } from 'vue' const shikiNode = ref({ type: 'code_block', lang: 'vue', value: `<script setup lang="ts"> import { ref, computed } from 'vue' const count = ref(0) const doubled = computed(() => count.value * 2) </script> <template> <div> <p>Count: {{ count }}</p> <p>Doubled: {{ doubled }}</p> <button @click="count++">Increment</button> </div> </template>`, props: { filename: 'Counter.vue' } }) </script> <template> <MarkdownCodeBlockNode :node="shikiNode" theme="vitesse-dark" :word-wrap="true" :show-header="true" :show-copy-button="true" /> </template> ``` ### MermaidBlockNode Component Progressive Mermaid diagram rendering with streaming updates. ```vue <script setup lang="ts"> import { MermaidBlockNode } from 'markstream-vue' import 'mermaid/dist/mermaid.css' import { ref } from 'vue' const mermaidNode = ref({ type: 'mermaid', value: `graph TD A[Start] --> B{Decision}`, props: { id: 'diagram-1' } }) // Simulate progressive diagram building const fullDiagram = `graph TD A[Start] --> B{Decision} B -->|Yes| C[Process] B -->|No| D[Skip] C --> E[End] D --> E` let currentIndex = mermaidNode.value.value.length function expandDiagram() { const interval = setInterval(() => { if (currentIndex < fullDiagram.length) { mermaidNode.value = { ...mermaidNode.value, value: fullDiagram.slice(0, currentIndex + 1) } currentIndex++ } else { clearInterval(interval) } }, 50) } function handleRender(svg: string) { console.log('Mermaid rendered:', svg.length, 'chars') } </script> <template> <div> <button @click="expandDiagram">Build Diagram</button> <MermaidBlockNode :node="mermaidNode" theme="forest" :mermaid-options="{ securityLevel: 'strict', startOnLoad: false }" :is-strict="true" @render="handleRender" custom-id="diagrams" /> </div> </template> ``` ### MathBlockNode and MathInlineNode Components KaTeX-powered mathematical formula rendering for block and inline equations. ```vue <script setup lang="ts"> import { MathBlockNode, MathInlineNode } from 'markstream-vue' import 'katex/dist/katex.min.css' import { ref } from 'vue' const blockMathNode = ref({ type: 'math_block', value: '\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}', props: { displayMode: true } }) const inlineMathNode = ref({ type: 'math_inline', value: 'E = mc^2', props: { displayMode: false } }) const customMacros = { '\\RR': '\\mathbb{R}', '\\NN': '\\mathbb{N}', '\\ZZ': '\\mathbb{Z}' } </script> <template> <div class="math-content"> <p> The famous equation <MathInlineNode :node="inlineMathNode" /> relates energy and mass. </p> <MathBlockNode :node="blockMathNode" :display-mode="true" :macros="customMacros" :throw-on-error="false" /> <p> The set of real numbers <MathInlineNode :node="{ type: 'math_inline', value: '\\RR' }" :macros="customMacros" /> is uncountable. </p> </div> </template> ``` ### AdmonitionNode Component Styled alert/callout blocks with collapsible support for notes, warnings, tips, and danger messages. ```vue <script setup lang="ts"> import { AdmonitionNode } from 'markstream-vue' import { ref } from 'vue' const admonitionNode = ref({ type: 'admonition', kind: 'warning', title: 'Important Notice', children: [ { type: 'text', raw: 'This is a warning message with formatted content.' } ], collapsible: true, open: true }) </script> <template> <AdmonitionNode :node="admonitionNode" index-key="warning-1" custom-id="docs" /> </template> ``` Supported admonition types: `note`, `info`, `tip`, `warning`, `danger`, `caution`, `error`. ### HtmlInlineNode Component Renders inline HTML content with streaming support and auto-closing for incomplete tags. ```vue <script setup lang="ts"> import { HtmlInlineNode } from 'markstream-vue' import { ref } from 'vue' const htmlNode = ref({ type: 'html_inline', content: '<span style="color: red;">Styled text</span>', loading: false }) </script> <template> <HtmlInlineNode :node="htmlNode" /> </template> ``` The component handles streaming scenarios where HTML tags may be incomplete, showing loading states and auto-closing tags when appropriate. ### Global Configuration Functions Configure default options for math rendering and internationalization. ```typescript import { setDefaultMathOptions, setDefaultI18nMap, registerMarkdownPlugin, clearRegisteredMarkdownPlugins } from 'markstream-vue' import { setLanguageIconResolver } from 'markstream-vue' // Configure KaTeX defaults globally setDefaultMathOptions({ displayMode: false, throwOnError: false, errorColor: '#cc0000', macros: { '\\RR': '\\mathbb{R}', '\\CC': '\\mathbb{C}' }, trust: true }) // Configure i18n translations setDefaultI18nMap({ 'common.copy': 'Copy Code', 'common.copied': 'Copied!', 'common.expand': 'Expand', 'common.collapse': 'Collapse', 'common.preview': 'Preview' }) // Custom language icon resolver setLanguageIconResolver((lang: string) => { const icons: Record<string, string> = { typescript: '📘', javascript: '📙', python: '🐍', rust: '🦀', go: '🔵' } return icons[lang] || '📄' }) ``` ### Feature Enablers Enable optional heavy dependencies on demand. ```typescript import { enableKatex, enableMermaid, disableKatex, disableMermaid, isKatexEnabled, isMermaidEnabled } from 'markstream-vue' // Enable KaTeX math rendering enableKatex() // Enable Mermaid diagram rendering enableMermaid() // Check if features are enabled if (isKatexEnabled()) { console.log('KaTeX is ready') } if (isMermaidEnabled()) { console.log('Mermaid is ready') } // Disable features when no longer needed disableKatex() disableMermaid() ``` ### Vue Plugin Installation Register all components globally using Vue plugin system. ```typescript import { createApp } from 'vue' import { VueRendererMarkdown } from 'markstream-vue' import App from './App.vue' const app = createApp(App) // Install plugin with options app.use(VueRendererMarkdown, { getLanguageIcon: (lang: string) => { return lang === 'vue' ? '💚' : undefined }, mathOptions: { displayMode: false, throwOnError: false, macros: { '\\vec': '\\mathbf{#1}' } } }) app.mount('#app') ``` ```vue <!-- Now all components are globally available --> <template> <div> <MarkdownRender :content="docs" /> <CodeBlockNode :node="codeNode" /> <MermaidBlockNode :node="diagramNode" /> </div> </template> ``` ## Summary **markstream-vue** excels in scenarios requiring real-time Markdown rendering, such as AI chat interfaces with streaming responses, collaborative documentation editors, live preview panes in IDEs, and progressive content loading in blogs or wikis. The library's streaming-first architecture ensures smooth character-by-character updates without flickering or layout shifts, while viewport priority optimization defers expensive operations like Monaco editor initialization and Mermaid diagram rendering until elements enter the viewport. The comprehensive component system supports tables, formulas, emoji, checkboxes, footnotes, admonitions, inline HTML with auto-closing, and custom HTML blocks, making it suitable for technical documentation, academic papers, and feature-rich content management systems. Integration patterns include direct component usage for simple cases, pre-parsed node rendering for server-side processing or content transformation pipelines, custom component registration for brand-specific UI overrides, and worker-based parsing for offloading heavy computation. The library works seamlessly with Vue 3 ecosystems including Vite, Nuxt, and VitePress, with built-in SSR safety guards and optional client-only rendering for browser-dependent features. CSS architecture uses layered imports compatible with Tailwind and UnoCSS, while the custom-id scoping system prevents style conflicts in multi-instance applications. TypeScript-first design provides complete type safety across the entire API surface, making it easy to extend parsers, create custom node renderers, and integrate with existing Vue applications. Recent additions include HtmlInlineNode for streaming inline HTML with auto-closing support, AdmonitionNode for styled alert blocks with collapsible support, enhanced Mermaid security with strict mode, and improved math parsing with better mid-state handling.