feat(md): Zig markdown parser with Bun.markdown API - #26440
Merged
Merged
Conversation
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.
Summary
src/md/Bun.markdown.html(input, options?)— render to HTML stringBun.markdown.render(input, callbacks?)— render with custom callbacks for each elementBun.markdown.react(input, options?)— render to a React Fragment element, directly usable as a component return valueputDirectOffsetfor fast allocationreact(): pass tag names as options keys to replace default HTML elements with custom components.mdas a bundler loader (via explicit{ type: "md" })JavaScript API
Bun.markdown.html(input, options?)Renders markdown to an HTML string:
Bun.markdown.render(input, callbacks?)Renders markdown with custom JavaScript callbacks for each element. Each callback receives children as a string and optional metadata, and returns a string:
Parser options can be included alongside callbacks:
Bun.markdown.react(input, options?)Returns a React Fragment element — use it directly as a component return value:
React 18 and older
By default,
react()usesSymbol.for('react.transitional.element')as the$$typeofsymbol, which is what React 19 expects. For React 18 and older, passreactVersion: 18:Component Overrides
Tag names can be overridden in
react():Boolean values are ignored (not treated as overrides), so parser options like
{ strikethrough: true }don't conflict with component overrides.Options
Architecture
Parser (
src/md/)The parser is split into focused modules using Zig's delegation pattern:
parser.zigParserstruct, state, and re-exported method delegationblocks.zigcontainers.ziginlines.ziglinks.zigautolinks.zigline_analysis.zigref_defs.zigrender_blocks.zightml_renderer.zigRendererVTabletypes.zigRendererVTable,BlockType,SpanType,TextType, etc.Renderer Abstraction
Parsing is decoupled from output via a
RendererVTable interface:Four renderers are implemented:
HtmlRenderer(src/md/html_renderer.zig) — produces HTML string outputJsCallbackRenderer(src/bun.js/api/MarkdownObject.zig) — calls JS callbacks for each element, accumulates string outputParseRenderer(src/bun.js/api/MarkdownObject.zig) — builds React element AST withMarkedArgumentBufferfor GC safetyJSReactElement(src/bun.js/bindings/JSReactElement.cpp) — C++ fast path for React element creation using cached JSC Structure +putDirectOffsetTest plan
html(),render(),react(),renderToStringintegration, component overrides)🤖 Generated with Claude Code