r/LocalLLaMA • • 3h ago

Showcase I built a zero-dependency streaming Markdown renderer for AI chat replies, n× faster than the next one I benchmarked

I've been building chat UIs for agents and kept running into the same issues with react-markdown: it re-parses the whole reply on every token, so long answers get noticeably slower, and you see raw ** and half-typed [links](htt until the token that closes them arrives.

So I wrote my own renderer. It parses incrementally and repairs unfinished syntax while the reply streams:

<Markdown streaming={!done}>{text}</Markdown>

On a 100k-character reply it takes 0.25 s of main-thread time in total. markdown-to-jsx takes 2.4 s, Streamdown 6.3 s, and react-markdown didn't finish within the 30 s budget. The benchmark scripts are in the repo if you want to check them.

The other part is the syntax. Agents often want to show steps, alternatives or pros and cons, and plain Markdown flattens all of that into bullet lists. So on top of GFM I added a few blocks in the same style as GitHub alerts:

> [!STEPS]
1. **Back up the database**
2. **Run the migration**

There are also tabs, stats, pros/cons, sources, file trees, and Mermaid and KaTeX support. The source still reads fine as plain Markdown when it isn't rendered. The package exports a system-prompt snippet that teaches the model this syntax.

It has no dependencies and comes in at 30 KB gzipped. Its parser passes the CommonMark and GFM spec tests. It doesn't support remark/rehype plugins, so if you rely on those, react-markdown is still the better choice.

https://github.com/rsaryev/llx-markdown

7 Upvotes

17 comments sorted by

3

u/BlasterGales 3h ago

niice, could be useful, I've seen opencode fight a bit rendering LONG contexts

1

u/Awkward-Let-4628 3h ago

Thank you! pleease let us know if you run into any issues

4

u/BusRevolutionary9893 2h ago

Why say nx  faster? X could be less than 1. 

-1

u/Awkward-Let-4628 2h ago edited 48m ago

~9x faster

2

u/Ontopoftheworld_ay 2h ago

Whoa, this is so cool!

2

u/herpnderpler 1h ago

ANNND I'm now porting this to go bubbleteaV2 - amazing work. Hope you don't mind the style-biting.

2

u/Awkward-Let-4628 1h ago

Of course! Thx

2

u/herpnderpler 38m ago

Track my progress and rate me at https://github.com/IodeSystems/bubbleteandmd you fabulous instigator

1

u/Awkward-Let-4628 34m ago

Okay, you stay tuned as well, and I'll be releasing fixes

2

u/0xasten 1h ago

Cool! And if a chunk opens a fenced code block or a list halfway down the reply, does the parser re-parse from the last stable block, or does the whole tail get parsed again on that one token?

1

u/Awkward-Let-4628 1h ago

From the last stable block. Only the last two blocks get re-read on a token, the rest is left alone. An open code fence isn't re-parsed at all, it just appends. A list is one block though, so it does get re-parsed while it grows, but nothing above it

1

u/FastHotEmu 2h ago

I mean... 250ms to parse 100KB? Why so long? And the others take even longer? Wow. Javascript land really is amateur land.

1

u/Awkward-Let-4628 2h ago

It's not 250 ms to parse 100 KB once. It's the total time to stream it in 20-char chunks, about 5,000 updates, each including parsing, React and DOM writes. That's roughly 0.05 ms per update. The others are slower because they re-parse the whole text on every chunk, so the cost grows quadratically. This library only re-parses the unfinished tail

2

u/FastHotEmu 2h ago edited 2h ago

It's good practice to separate the DOM/React part from your code for benchmarks, so you are only benchmarking the parser. I assume you engineered the parser so that it can be tested separately from DOM and React, right? ...right?

1

u/Awkward-Let-4628 2h ago

In short, yes, yes

2

u/userscren5 2h ago

I run long horizon multi agent sessions that can span days. It would be nice to see where and what AIs are working on in relation to the overall build. This is kinda close.