>_ DevTrendsde

Sprache

Start

Sprachen

Bereiche

Frontend Backend Mobile DevOps AI / ML GameDev Blockchain Embedded Sicherheit
Vue

So rendern Sie Streaming-Markdown von neuronalen Netzen ohne Flackern und Einfrieren

Sobald Sie die Streaming-Ausgabe eines Sprachmodells mit dem Frontend verbinden, verwandelt ein gewöhnlicher Markdown-Renderer die Seite fast sofort in einen Albtraum. Standard-Bibliotheken wie markdown-it oder marked wurden für fertige statische Dokumente entwickelt. Wenn Sie ihnen einen rohen Token-Stream über SSE oder WebSocket zuführen, parsen sie bei jedem Chunk den gesamten Text erneut, zeichnen den DOM-Baum neu und bringen das Scrollen durcheinander.

An diesem Punkt beginnt die Benutzeroberfläche merklich zu flackern. Die Syntaxhervorhebung blinkt, unvollständige Code-Blöcke zerstören das Markup darunter, und ungeschlossene Formeln bleiben in einem endlosen Ladezustand hängen. Das markstream-vue-Repository löst genau dieses frustrierende Problem.

Star History Chart

Was unter der Haube der Bibliothek steckt

Das Projekt begann als spezialisierte Komponente für Vue 3, aber im Laufe der Zeit teilte der Autor die Architektur in einen Core stream-markdown-parser und Adapter für verschiedene Frameworks auf. Es gibt mittlerweile fertige Pakete für Vue 3, Nuxt, React, Next.js, Svelte 5, Angular und sogar das Legacy Vue 2.

Die Hauptaufgabe des Renderers ist es, das DOM bei häufigen Mikro-Updates des Textes stabil zu halten. Die Bibliothek parst den Stream inkrementell, versteht Zwischenzustände von ungeschlossenen Tags und aktualisiert nur die geänderten Knoten, während der Rest der Seite unberührt bleibt.

Betriebsmodi und Lastmanagement

Die Bibliothek verfügt über zwei grundlegend verschiedene Rendering-Ansätze, die über den mode-Prop umgeschaltet werden können.

Der mode="chat"-Modus ist für KI-Chats konzipiert. Darin gruppiert der Renderer eingehende Tokens in kleine Batches und gibt sie mit einem sanften Typewriter-Effekt aus. Gleichzeitig werden unnötige Opacity-Animationen deaktiviert, damit die Benutzeroberfläche nicht bei jedem neuen Wort wackelt.

Wenn Sie eine riesige generierte Longread oder Dokumentation anzeigen müssen, ist es besser, die Virtualisierung über mode="docs" zu aktivieren. Der Renderer behält ein festes Fenster von Elementen im aktiven DOM-Baum bei (standardmäßig etwa 220 Knoten). Dadurch bleibt der Speicherverbrauch des Browsers auf einem konstanten Niveau und verhindert Einfrierer beim Scrollen durch lange Konversationen.

<script setup lang="ts">
import { ref } from 'vue'
import MarkdownRender from 'markstream-vue'
import 'markstream-vue/index.css'

const message = ref('')
const isDone = ref(false)

// Получаем чанки через EventSource или fetch
const eventSource = new EventSource('/api/chat')

eventSource.onmessage = (event) => {
  message.value += event.data
}

eventSource.addEventListener('done', () => {
  isDone.value = true
  eventSource.close()
})
</script>

<template>
  <MarkdownRender
    mode="chat"
    :content="message"
    :final="isDone"
    smooth-streaming="auto"
    :fade="false"
  />
</template>

Der final-Prop ist entscheidend bei der Arbeit mit einem Stream. Solange final="false" true ist, toleriert der Parser gelassen Konstrukte, die mitten im Wort abgeschnitten werden. Sobald das Completion-Signal eintrifft, leert der Renderer den Streaming-Cache und bringt das Markup in seine endgültige Form.

Arbeit mit komplexen Blöcken

Normale Parser stolpern über Mermaid-Diagramme oder KaTeX-Formeln, wenn die Syntax noch nicht vollständig geschrieben wurde. In markstream ist dieser Moment bis ins kleinste Detail durchdacht.

Mermaid-Diagramme und Formeln

Schwere Abhängigkeiten wie mermaid und katex sind nicht im Haupt-Bundle enthalten. Sie installieren sie als Peer-Dependencies und aktivieren sie durch den Aufruf von Funktionen:

import { enableKatex, enableMermaid } from 'markstream-vue'
import 'katex/dist/katex.min.css'

enableMermaid()
enableKatex()

Mermaid-Diagramme werden progressiv geparst. Wenn das Diagramm noch vom Modell geschrieben wird, zeigt der Renderer einen sauberen Placeholder anstelle eines Syntaxfehlers in der Konsole. Für KaTeX können Sie das Formel-Parsing über CDN auf einen separaten Web Worker auslagern, sodass schwere mathematische Ausdrücke den Haupt-Interface-Thread überhaupt nicht blockieren.

Code-Blöcke und Diffs

In Version 2.0 haben die Entwickler den schweren Monaco-Editor zugunsten einer Integration mit stream-diffs fallengelassen. Jetzt können Sie interaktive Datei-Diffs direkt im Stream anzeigen, zwischen hellem und dunklem Theme wechseln und die Blockhöhen konfigurieren.

<template>
  <MarkdownRender
    :content="content"
    :is-dark="true"
    :code-block-props="{
      theme: { light: 'vitesse-light', dark: 'vitesse-dark' }
    }"
  />
</template>

Eigene Vue-Komponenten innerhalb von Markdown

Manchmal geben Modelle nicht-standardmäßige Tags aus, zum Beispiel <thinking> für eine Reasoning-Chain oder benutzerdefinierte Shortcodes für das Aufrufen von Buttons und Widgets. Sie können diese abfangen und durch vollständige Vue-Komponenten ersetzen:

import { setCustomComponents } from 'markstream-vue'

setCustomComponents('chat-scope', {
  CALLOUT: () => import('./components/Callout.vue'),
  THINKING: () => import('./components/ThinkingAccordion.vue'),
})

Im Template müssen Sie nur denselben Identifier angeben:

<MarkdownRender
  :content="message"
  custom-id="chat-scope"
  :custom-html-tags="['thinking']"
/>

Serverseitiges Rendering und State-Transfer

Wenn Sie eine App mit Nuxt oder Next.js erstellen, müssen Sie den Parser nicht beim Client von Grund auf ausführen. Das Dokument kann auf dem Server in eine Struktur aus typisierten Knoten geparst werden:

import { getMarkdown, parseMarkdownToStructure } from 'markstream-vue'

const md = getMarkdown()
const nodes = parseMarkdownToStructure(rawMarkdown, md, { final: true })

Die Client-Komponente akzeptiert fertige Knoten über den :nodes="nodesFromServer"-Prop, was eine schnelle Hydration ohne Layout-Mismatches ermöglicht. Wenn Sie nach dem initialen Seitenaufbau weiter streamen müssen, nimmt der Client einfach den Buffer auf und parst neue Teile weiter.

Praktische Szenarien

Die Bibliothek deckt mehrere häufige Frontend-Entwicklungsaufgaben auf einmal ab:

  • Dialog-Interfaces mit großen Sprachmodellen, bei denen es wichtig ist, Flackern und Bildschirmwackeln zu eliminieren.
  • Code-Review- und Patch-Generierungssysteme mit Diff-Anzeige direkt während der Generierung.
  • Knowledge Bases und Changelog-Panels mit dynamischem Abschnittsladen und interaktiven Komponenten.
  • Technische Dokumentationsseiten mit Formeln und komplexen Diagrammen.

Zusammenfassung

Wenn Ihr Projekt statische Markdown-Dateien aus einem lokalen Ordner anzeigt, wird eine bewährte markdown-it es ohne unnötige Komplikationen erledigen. Aber wenn Sie mit einem Live-Token-Stream von einem LLM arbeiten, ein Chat-Interface ins Web bringen oder es leid sind, gegen Lag beim Rendern langer Antworten anzukämpfen, verdient die Bibliothek definitiv einen Platz in Ihren Dependencies.

Sie eliminiert Dutzende nicht-offensichtliche Streaming-Probleme und spart eine Menge Zeit für das Schreiben eigener Workarounds um Parser herum. Für einen schnellen Start können Sie den offiziellen Online-Playground ausprobieren oder eine Testumgebung über StackBlitz deployen.

Ähnliche Projekte