Streaming Markdown in a chat, token by token
Render Markdown incrementally as tokens arrive, powered by
streaming-markdown. It parses only the new text and
appends DOM nodes per chunk — no per-token re-parse, no innerHTML rebuild.
npm install @aparte/plugin-streaming-markdown @aparte/core streaming-markdown@aparte/core and streaming-markdown are peer dependencies.
import { setupStreamingMarkdownProvider } from '@aparte/plugin-streaming-markdown';
setupStreamingMarkdownProvider();Call it once at startup. It fills the aparteGlobalConfig.setStreamingMarkdownProvider seam, which the chat
bubble uses while a message is streaming. That is all you need: a reply renders the same whether it
streams or arrives complete — a conversation restored after a reload, a non-streaming provider’s
answer — because with no one-shot provider registered, core runs the whole string through this parser
too. Pairing it with a one-shot provider like marked is optional — register one
and finished and restored messages are rendered through it, at that renderer’s full fidelity.
Security
Section titled “Security”The streaming path writes DOM nodes directly, so it bypasses the one-shot HTML sanitiser. To keep the
same URL policy live, the plugin drops any href/src whose scheme fails core’s isSafeUrl as it
streams — a [x](javascript:…) token never becomes a clickable javascript: link, even if the scheme
is split across two chunks — and a link that resolves off-site (https://…, //host, a leading space, http:/host) gets target="_blank" rel="noopener noreferrer" live, the rule the sanitiser applies. At completion the settled message goes through the sanitiser in full: re-rendered by the one-shot provider when one is registered, re-sanitised in place otherwise.