Skip to content

Vue 2 Components & API

markstream-vue2 provides the same powerful components as markstream-vue, but built for Vue 2. All components are compatible with Vue 2.6+ (with @vue/composition-api) and Vue 2.7+.

TypeScript exports

markstream-vue2 exports renderer and component prop interfaces:

ts
import type {
  CodeBlockNodeProps,
  InfographicBlockNodeProps,
  MermaidBlockNodeProps,
  NodeRendererProps,
  PreCodeNodeProps,
} from 'markstream-vue2'
import type { CodeBlockNode } from 'stream-markdown-parser'

Notes:

  • NodeRendererProps matches <MarkdownRender> props.
  • CodeBlockNodeProps, MermaidBlockNodeProps, InfographicBlockNodeProps, and PreCodeNodeProps all use CodeBlockNode for node (use language: 'mermaid' / language: 'infographic' to route specialized renderers).

Main Component: MarkdownRender

The primary component for rendering markdown content in Vue 2.

Props

MarkdownRender in Vue 2 mirrors the Vue 3 renderer props. In templates, use kebab-case.

Core props

PropTypeDefaultDescription
contentstring-Markdown content to render
nodesBaseNode[]-Pre-parsed AST nodes (typically ParsedNode[] from the parser)
custom-idstring-Identifier for scoping custom components and CSS ([data-custom-id="..."])
finalbooleanfalseMarks the input as end-of-stream; stops emitting streaming loading nodes
parse-optionsParseOptions-Parser options and token hooks (only when content is provided)
custom-html-tagsstring[]-HTML-like tags emitted as custom nodes (e.g. thinking)
custom-markdown-it(md: MarkdownIt) => MarkdownIt-Customize the internal MarkdownIt instance
debug-performancebooleanfalseLog parse/render timing and virtualization stats (dev only)
is-darkbooleanfalseTheme flag forwarded to heavy nodes; adds .dark to the root container
index-keynumber | string-Key prefix when rendering multiple instances in lists
typewriterbooleantrueEnable the non-code-node enter transition

Streaming & heavy-node toggles

PropDefaultDescription
render-code-blocks-as-prefalseRender non-Mermaid/Infographic code blocks as <pre><code>
code-block-streamtrueStream code block updates as content arrives
viewport-prioritytrueDefer heavy work (Monaco/Mermaid/KaTeX) until near viewport
defer-nodes-until-visibletrueRender heavy nodes as placeholders until visible (non-virtualized mode only)

Performance (virtualization & batching)

PropDefaultDescription
max-live-nodes320Max fully rendered nodes kept in DOM (set 0 to disable virtualization)
live-node-buffer60Overscan buffer around the focus range
batch-renderingtrueIncremental batch rendering when virtualization is disabled
initial-render-batch-size40Nodes rendered immediately before batching starts
render-batch-size80Nodes rendered per batch tick
render-batch-delay16Extra delay (ms) before each batch after rAF
render-batch-budget-ms6Time budget (ms) before adaptive batch sizes shrink
render-batch-idle-timeout-ms120Timeout (ms) for requestIdleCallback slices

Global code block options

PropTypeDescription
code-block-dark-themeanyMonaco dark theme object forwarded to every CodeBlockNode
code-block-light-themeanyMonaco light theme object forwarded to every CodeBlockNode
code-block-monaco-optionsRecord<string, any>Options forwarded to stream-monaco
code-block-min-widthstring | numberMin width forwarded to CodeBlockNode
code-block-max-widthstring | numberMax width forwarded to CodeBlockNode
code-block-propsRecord<string, any>Extra props forwarded to every CodeBlockNode
themesstring[]Theme list forwarded to stream-monaco

Events

  • @copy, @handleArtifactClick, @click, @mouseover, @mouseout

Usage

vue
<script>
import MarkdownRender from 'markstream-vue2'

export default {
  components: { MarkdownRender },
  data() {
    return {
      markdown: '# Hello Vue 2!'
    }
  },
  methods: {
  }
}
</script>

<template>
  <MarkdownRender
    custom-id="docs"
    :content="markdown"
    :max-live-nodes="150"
  />
</template>

Code Block Components

MarkdownCodeBlockNode

Lightweight code highlighting using Shiki.

vue
<script>
import { MarkdownCodeBlockNode } from 'markstream-vue2'

export default {
  components: { MarkdownCodeBlockNode },
  data() {
    return {
      codeNode: {
        type: 'code_block',
        language: 'javascript',
        code: 'const hello = "world"',
        raw: 'const hello = "world"'
      }
    }
  },
  methods: {
    handleCopy() {
      alert('Code copied!')
    }
  }
}
</script>

<template>
  <div class="markstream-vue">
    <MarkdownCodeBlockNode
      :node="codeNode"
      :show-copy-button="true"
      @copy="handleCopy"
    >
      <template #header-right>
        <span class="lang-badge">{{ codeNode.language }}</span>
      </template>
    </MarkdownCodeBlockNode>
  </div>
</template>

CodeBlockNode

Feature-rich Monaco-powered code blocks.

vue
<script>
import { CodeBlockNode } from 'markstream-vue2'

export default {
  components: { CodeBlockNode },
  data() {
    return {
      codeNode: {
        type: 'code_block',
        language: 'typescript',
        code: 'const greeting: string = "Hello"',
        raw: 'const greeting: string = "Hello"'
      }
    }
  },
  methods: {
    handleCopy(code) {
      console.log('Code copied:', code)
    },
    handlePreviewCode(artifact) {
      console.log('Preview code:', artifact)
    }
  }
}
</script>

<template>
  <div class="markstream-vue">
    <CodeBlockNode
      :node="codeNode"
      :monaco-options="{ fontSize: 14, theme: 'vs-dark' }"
      :stream="true"
      @copy="handleCopy"
      @preview-code="handlePreviewCode"
    />
  </div>
</template>

Math Components

MathBlockNode

Renders block-level math formulas with KaTeX.

vue
<script>
import { MathBlockNode } from 'markstream-vue2'

export default {
  components: { MathBlockNode },
  data() {
    return {
      mathNode: {
        type: 'math_block',
        content: '\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}',
        raw: '\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}'
      }
    }
  }
}
</script>

<template>
  <div class="markstream-vue">
    <MathBlockNode
      :node="mathNode"
    />
  </div>
</template>

MathInlineNode

Renders inline math formulas.

vue
<script>
import { MathInlineNode } from 'markstream-vue2'

export default {
  components: { MathInlineNode },
  data() {
    return {
      inlineMathNode: {
        type: 'math_inline',
        content: 'E = mc^2',
        raw: 'E = mc^2'
      }
    }
  }
}
</script>

<template>
  <div class="markstream-vue">
    <p>
      The formula is:
      <MathInlineNode :node="inlineMathNode" />
    </p>
  </div>
</template>

Mermaid Diagrams

MermaidBlockNode

Progressive Mermaid diagram rendering.

vue
<script>
import { MermaidBlockNode } from 'markstream-vue2'

export default {
  components: { MermaidBlockNode },
  data() {
    return {
      mermaidNode: {
        type: 'code_block',
        language: 'mermaid',
        code: `graph TD
    A[Start] --> B{Is it working?}
    B -->|Yes| C[Great!]`,
        raw: ''
      }
    }
  },
  methods: {
    onExport(ev) {
      console.log('Mermaid SVG:', ev.svgString)
    }
  }
}
</script>

<template>
  <div class="markstream-vue">
    <MermaidBlockNode
      :node="mermaidNode"
      :is-strict="true"
      @export="onExport"
    />
  </div>
</template>

Utility Functions

setCustomComponents

Register custom node renderers for specific markdown nodes.

js
import { setCustomComponents } from 'markstream-vue2'

// Define custom component
const CustomHeading = {
  name: 'CustomHeading',
  props: ['node', 'indexKey', 'customId'],
  render(h) {
    const level = this.node.level || 1
    return h(`h${level}`, {
      class: 'custom-heading',
      attrs: { 'data-custom-id': this.customId }
    }, this.node.children.map(c => c.content))
  }
}

// Register globally
setCustomComponents('docs', {
  heading: CustomHeading
})

getMarkdown

Get a configured markdown-it instance.

js
import { getMarkdown } from 'markstream-vue2'

const md = getMarkdown('my-msg-id', {
  html: true,
  linkify: true,
  typographer: true
})

const tokens = md.parse('# Hello World')

parseMarkdownToStructure

Parse markdown string to AST structure.

js
import { getMarkdown, parseMarkdownToStructure } from 'markstream-vue2'

const md = getMarkdown()
const nodes = parseMarkdownToStructure('# Title\n\nContent here...', md)

// Use with MarkdownRender
// <MarkdownRender :nodes="nodes" />

enableKatex / enableMermaid

(Re)enable feature loaders for KaTeX and Mermaid. Default loaders are already on; call these only if you disabled them earlier or want to override the loader (for example, using a CDN build).

js
import { enableKatex, enableMermaid } from 'markstream-vue2'

// Enable KaTeX loader
enableKatex()

// Enable Mermaid loader
enableMermaid()

Custom Component API

Props Interface

All custom node components receive these props:

ts
interface NodeComponentProps {
  node: ParsedNode // The parsed node data
  indexKey: number | string // Unique key for the node
  customId?: string // Custom ID for scoping
  isDark?: boolean // Forwarded theme flag (from MarkdownRender)
  typewriter?: boolean // Forwarded typewriter flag (non-code nodes only)
  loading?: boolean // Streaming/loading state (from node.loading)
}

Example Custom Component

vue
<script>
export default {
  name: 'CustomParagraph',
  props: {
    node: {
      type: Object,
      required: true
    },
    indexKey: {
      type: [Number, String],
      default: 0
    },
    customId: {
      type: String,
      default: ''
    }
  },
  computed: {
    tag() {
      return 'p'
    },
    classes() {
      return [
        'custom-paragraph',
        `custom-paragraph-${this.indexKey}`
      ]
    },
    attrs() {
      return {
        'data-custom-id': this.customId,
        'data-node-type': this.node.type
      }
    }
  }
}
</script>

<template>
  <component
    :is="tag"
    :class="classes"
    v-bind="attrs"
  >
    <slot>
      <template v-for="(child, i) in node.children">
        <span v-if="child.type === 'text'" :key="i">
          {{ child.content }}
        </span>
        <!-- Handle other node types... -->
      </template>
    </slot>
  </component>
</template>

<style scoped>
.custom-paragraph {
  line-height: 1.7;
  color: #333;
}
</style>

Streaming Support

markstream-vue2 supports streaming markdown content with the loading state on nodes:

vue
<script>
import MarkdownRender from 'markstream-vue2'

export default {
  components: { MarkdownRender },
  data() {
    return {
      streamingContent: '',
      fullContent: `# Streaming Demo

This content streams in **small chunks**.

\`\`\`javascript
console.log('Streaming...')
\`\`\`
`
    }
  },
  mounted() {
    this.startStreaming()
  },
  methods: {
    startStreaming() {
      let i = 0
      const interval = setInterval(() => {
        if (i < this.fullContent.length) {
          this.streamingContent += this.fullContent[i]
          i++
        }
        else {
          clearInterval(interval)
        }
      }, 30)
    }
  }
}
</script>

<template>
  <div>
    <MarkdownRender :content="streamingContent" />
  </div>
</template>

TypeScript Support

markstream-vue2 includes full TypeScript definitions. For Vue 2.6.x, configure your tsconfig.json:

json
{
  "compilerOptions": {
    "types": ["@vue/composition-api", "markstream-vue2"],
    "moduleResolution": "node",
    "esModuleInterop": true
  }
}

For Vue 2.7+, types are included automatically:

ts
import type { ParsedNode } from 'markstream-vue2'
import MarkdownRender from 'markstream-vue2'

// Your component with proper typing
import { defineComponent, ref } from 'vue'

export default defineComponent({
  components: { MarkdownRender },
  setup() {
    const markdown = ref('# Hello')
    const nodes = ref<ParsedNode[]>([])

    return { markdown, nodes }
  }
})

Differences from Vue 3 Version

The Vue 2 version maintains API compatibility with the Vue 3 version with these considerations:

  1. Composition API: Requires Vue 2.7+ or @vue/composition-api for Vue 2.6.x
  2. Slots: Use Vue 2 scoped slot syntax
  3. Event names: Use kebab-case for event names in templates
  4. v-model: No changes needed, works the same way

Next Steps