Skip to content

v0.2.0 — SOTA streaming markdown for LLMs

Latest

Choose a tag to compare

@lognjais lognjais released this 10 May 17:48
· 3 commits to master since this release

Major architectural release. Drop-in replacement for react-markdown in streaming AI chat UIs, now with first-class Next.js / RSC support, lazy adapters for Shiki/KaTeX/Mermaid, and a hardened parser that survives adversarial input.

📦 npm · 📚 README · 🛡️ Threat model

Architecture

Single package, multiple sub-paths so consumers tree-shake what they don't use:

  • stream-md — default React entry, carries "use client"
  • stream-md/core — framework-agnostic, no React
  • stream-md/server — RSC-safe parser returning JSON-serializable Block[]
  • stream-md/next — Next.js helpers: <StreamMD>, <StreamMDServer>, <AssistantMarkdown> (Vercel AI SDK)
  • stream-md/strict — opt-in micromark adapter for full CommonMark + GFM
  • stream-md/shiki, stream-md/katex, stream-md/mermaid — lazy adapters
  • stream-md/plugins — public BlockPlugin + InlinePlugin API

Streaming correctness

  • Speculative inline closure: unclosed **bo renders as bold now with data-tentative="true", so when ** arrives there's nothing to repaint
  • Fixed: premature heading close, table-on-separator-row only, list nesting via indent stack, setext headings, indented code blocks, hard breaks, code-fence info-string attributes, CommonMark left/right flank rules for */_, balanced-paren link URLs, smallest-run inline code per spec, autolinks <https://...>/<user@host>, prefix-diff resilience (auto-reset on non-prefix replace)
  • Headline guarantee: streaming-equivalence property test — for any document, char-by-char streaming produces the same final AST as atomic parsing (verified by fast-check)

Security

  • URL sanitizer rejects javascript:, vbscript:, and unsafe data: schemes by default
  • Control-char-smuggled schemes (e.g. java\nscript:) blocked
  • Hard caps: 1 MB document length, 4-level inline recursion depth
  • External links get target="_blank" rel="noopener noreferrer" referrerPolicy="no-referrer"
  • Tables use class-based alignment — works under strict CSP (no 'unsafe-inline' styles)
  • See SECURITY.md for the threat model

Performance

  • Active code blocks render plain <code> while streaming; highlighter runs once on close
  • Block memoization actually fires now (overrides and plugin arrays are identity-stabilized)
  • useSyncExternalStore replaces side-effecting useMemo (StrictMode-safe, RSC-safe)
  • Parsed table/list cached on block.meta.parsed once on close — no re-parse per render
  • Bundle: 9.77 kB gz default, 6.51 kB gz core, 2.21 kB gz server

Highlighter

  • Set-based keyword/builtin lookup
  • Member-access keyword suppression (obj.return no longer highlights)
  • Real C/C++ tables (no longer aliased to Java)
  • JSX/TSX tag detection
  • Multi-line strings: Python """...""", JS template literals, JSDoc

Themes

Catppuccin (Mocha + Latte), Tokyo Night (+ Storm), GitHub (Dark + Light), Solarized (Dark + Light).

Tooling

  • Vitest + jsdom + RTL + fast-check (137 tests)
  • ESLint flat config + Prettier
  • size-limit budgets enforced in CI
  • GitHub Actions CI on Node 18/20/22
  • Changesets release workflow

Migration from react-markdown

```diff

  • import ReactMarkdown from 'react-markdown';
  • import { StreamMD } from 'stream-md';
  • import 'stream-md/styles.css';
  • {text}

```

Install

```bash
npm install stream-md
```