perf: lex block tokens only and reuse parsed blocks while streaming - #608
Open
withkarann wants to merge 1 commit into
Open
perf: lex block tokens only and reuse parsed blocks while streaming#608withkarann wants to merge 1 commit into
withkarann wants to merge 1 commit into
Conversation
parseMarkdownIntoBlocks only needs the raw text of each block token, but Lexer.lex also runs the inline tokenizer over every block. Call blockTokens directly instead. While a document streams, text is only appended, so blocks that ended before the last two blocks cannot change. Keep the previous parse and re-lex just the tail of the document when the new input extends the previous one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
|
@withkarann is attempting to deploy a commit to the Vercel Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
parseMarkdownIntoBlocksruns on every streamed token. Two things made it slower than it needs to be:Lexer.lex, which also runs marked's inline tokenizer. Only the raw block text is used, so it now callsblockTokensdirectly.The boundary needs care. A block that ends with a blank line can still absorb the next block when that block's first line changes (
2is a paragraph,2.is a list item), and a lone#ends the paragraph above it while#xcontinues it. So blocks are reused only up to the last one that ends with a blank line and is followed by at least two more blocks.Type of Change
Changes Made
lib/parse-blocks.tsx: callblockTokensinstead ofLexer.lex; keep the last parse in one cache entry and re-lex only the tail when the input extends the previous input. Anything else falls back to a full parse.__tests__/parse-blocks-incremental.test.tsx: streams 40 tricky documents plus 400 seeded random ones, one, five, and varying characters at a time. Every prefix must equal a fresh full parse.__benchmarks__/parse-blocks.bench.ts: adds a case that streams text after a long document.Testing
pnpm vitest bench --run __benchmarks__/parse-blocks.bench.ts, main vs this branch, Node 24 on Apple Silicon:Code-only and HTML-only inputs have no inline content, so they do not change. The React re-render bench moves from 135 to 147 ops/s because it is dominated by React work.
Checklist
pnpm changeset)Additional Notes
The cache is a single module-level entry. Two documents streaming at once fall back to full parses, which is the current behavior.
One known difference: marked drops a duplicate link definition (a second
[x]: urlfor the same label) from a full parse, so the streamed tail keeps text the full parse loses. Neither renders anything for it.