Files
bun.sh/docs/docs.json
Jarred Sumner 1bfe5c6b37 feat(md): Zig markdown parser with Bun.markdown API (#26440)
## Summary

- Port md4c (CommonMark-compliant markdown parser) from C to Zig under
`src/md/`
- Three output modes:
  - `Bun.markdown.html(input, options?)` — render to HTML string
- `Bun.markdown.render(input, callbacks?)` — render with custom
callbacks for each element
- `Bun.markdown.react(input, options?)` — render to a React Fragment
element, directly usable as a component return value
- React element creation uses a cached JSC Structure with
`putDirectOffset` for fast allocation
- Component overrides in `react()`: pass tag names as options keys to
replace default HTML elements with custom components
- GFM extensions: tables, strikethrough, task lists, permissive
autolinks, disallowed raw HTML tag filter
- Wire up `.md` as a bundler loader (via explicit `{ type: "md" }`)

## JavaScript API

### `Bun.markdown.html(input, options?)`

Renders markdown to an HTML string:

```js
const html = Bun.markdown.html("# Hello **world**");
// "<h1>Hello <strong>world</strong></h1>\n"

Bun.markdown.html("## Hello", { headingIds: true });
// '<h2 id="hello">Hello</h2>\n'
```

### `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:

```js
// Custom HTML with classes
const html = Bun.markdown.render("# Title\n\nHello **world**", {
  heading: (children, { level }) => `<h${level} class="title">${children}</h${level}>`,
  paragraph: (children) => `<p>${children}</p>`,
  strong: (children) => `<b>${children}</b>`,
});

// ANSI terminal output
const ansi = Bun.markdown.render("# Hello\n\n**bold**", {
  heading: (children) => `\x1b[1;4m${children}\x1b[0m\n`,
  paragraph: (children) => children + "\n",
  strong: (children) => `\x1b[1m${children}\x1b[22m`,
});

// Strip all formatting
const text = Bun.markdown.render("# Hello **world**", {
  heading: (children) => children,
  paragraph: (children) => children,
  strong: (children) => children,
});
// "Hello world"

// Return null to omit elements
const result = Bun.markdown.render("# Title\n\n![logo](img.png)\n\nHello", {
  image: () => null,
  heading: (children) => children,
  paragraph: (children) => children + "\n",
});
// "Title\nHello\n"
```

Parser options can be included alongside callbacks:

```js
Bun.markdown.render("Visit www.example.com", {
  link: (children, { href }) => `[${children}](${href})`,
  paragraph: (children) => children,
  permissiveAutolinks: true,
});
```

### `Bun.markdown.react(input, options?)`

Returns a React Fragment element — use it directly as a component return
value:

```tsx
// Use as a component
function Markdown({ text }: { text: string }) {
  return Bun.markdown.react(text);
}

// With custom components
function Heading({ children }: { children: React.ReactNode }) {
  return <h1 className="title">{children}</h1>;
}
const element = Bun.markdown.react("# Hello", { h1: Heading });

// Server-side rendering
import { renderToString } from "react-dom/server";
const html = renderToString(Bun.markdown.react("# Hello **world**"));
// "<h1>Hello <strong>world</strong></h1>"
```

#### React 18 and older

By default, `react()` uses `Symbol.for('react.transitional.element')` as
the `$$typeof` symbol, which is what React 19 expects. For React 18 and
older, pass `reactVersion: 18`:

```tsx
const el = Bun.markdown.react("# Hello", { reactVersion: 18 });
```

### Component Overrides

Tag names can be overridden in `react()`:

```tsx
Bun.markdown.react(input, {
  h1: MyHeading,      // block elements
  p: CustomParagraph,
  a: CustomLink,      // inline elements
  img: CustomImage,
  pre: CodeBlock,
  // ... h1-h6, p, blockquote, ul, ol, li, pre, hr, html,
  //     table, thead, tbody, tr, th, td,
  //     em, strong, a, img, code, del, math, u, br
});
```

Boolean values are ignored (not treated as overrides), so parser options
like `{ strikethrough: true }` don't conflict with component overrides.

### Options

```js
Bun.markdown.html(input, {
  tables: true,              // GFM tables (default: true)
  strikethrough: true,       // ~~deleted~~ (default: true)
  tasklists: true,           // - [x] items (default: true)
  headingIds: true,          // Generate id attributes on headings
  autolinkHeadings: true,    // Wrap heading content in <a> tags
  tagFilter: false,          // GFM disallowed HTML tags
  wikiLinks: false,          // [[wiki]] links
  latexMath: false,          // $inline$ and $$display$$
  underline: false,          // __underline__ (instead of <strong>)
  // ... and more
});
```

## Architecture

### Parser (`src/md/`)

The parser is split into focused modules using Zig's delegation pattern:

| Module | Purpose |
|--------|---------|
| `parser.zig` | Core `Parser` struct, state, and re-exported method
delegation |
| `blocks.zig` | Block-level parsing: document processing, line
analysis, block start/end |
| `containers.zig` | Container management: blockquotes, lists, list
items |
| `inlines.zig` | Inline parsing: emphasis, code spans, HTML tags,
entities |
| `links.zig` | Link/image resolution, reference links, autolink
rendering |
| `autolinks.zig` | Permissive autolink detection (www, url, email) |
| `line_analysis.zig` | Line classification: headings, fences, HTML
blocks, tables |
| `ref_defs.zig` | Reference definition parsing and lookup |
| `render_blocks.zig` | Block rendering dispatch (code, HTML, table
blocks) |
| `html_renderer.zig` | HTML renderer implementing `Renderer` VTable |
| `types.zig` | Shared types: `Renderer` VTable, `BlockType`,
`SpanType`, `TextType`, etc. |

### Renderer Abstraction

Parsing is decoupled from output via a `Renderer` VTable interface:

```zig
pub const Renderer = struct {
    ptr: *anyopaque,
    vtable: *const VTable,

    pub const VTable = struct {
        enterBlock: *const fn (...) void,
        leaveBlock: *const fn (...) void,
        enterSpan:  *const fn (...) void,
        leaveSpan:  *const fn (...) void,
        text:       *const fn (...) void,
    };
};
```

Four renderers are implemented:
- **`HtmlRenderer`** (`src/md/html_renderer.zig`) — produces HTML string
output
- **`JsCallbackRenderer`** (`src/bun.js/api/MarkdownObject.zig`) — calls
JS callbacks for each element, accumulates string output
- **`ParseRenderer`** (`src/bun.js/api/MarkdownObject.zig`) — builds
React element AST with `MarkedArgumentBuffer` for GC safety
- **`JSReactElement`** (`src/bun.js/bindings/JSReactElement.cpp`) — C++
fast path for React element creation using cached JSC Structure +
`putDirectOffset`

## Test plan

- [x] 792 spec tests pass (CommonMark, GFM tables, strikethrough,
tasklists, permissive autolinks, GFM tag filter, wiki links, coverage,
regressions)
- [x] 114 API tests pass (`html()`, `render()`, `react()`,
`renderToString` integration, component overrides)
- [x] 58 GFM compatibility tests pass

```
bun bd test test/js/bun/md/md-spec.test.ts       # 792 pass
bun bd test test/js/bun/md/md-render-api.test.ts  # 114 pass
bun bd test test/js/bun/md/gfm-compat.test.ts     # 58 pass
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: SUZUKI Sosuke <sosuke@bun.com>
Co-authored-by: robobun <robobun@oven.sh>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Kirill Markelov <kerusha.chubko@gmail.com>
Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
Co-authored-by: Alistair Smith <hi@alistair.sh>
2026-01-28 20:24:02 -08:00

635 lines
20 KiB
JSON

{
"$schema": "https://mintlify.com/docs.json",
"theme": "aspen",
"name": "Bun",
"seo": {
"metatags": {
"canonical": "https://bun.com/docs"
}
},
"colors": {
"light": "#ff73a8",
"primary": "#ff73a8",
"dark": "#ff73a8"
},
"background": {
"decoration": "gradient"
},
"favicon": "/logo/bun.png",
"icons": {
"library": "lucide"
},
"fonts": {
"heading": {
"family": "Inter Display Bold",
"source": "https://mintlify-assets.b-cdn.net/fonts/InterDisplay-Bold.woff2",
"format": "woff2"
}
},
"appearance": {
"default": "system"
},
"logo": {
"light": "/logo/logo-with-wordmark-dark.svg",
"dark": "/logo/logo-with-wordmark-light.svg"
},
"navbar": {
"links": [
{
"label": "Install Bun",
"href": "https://www.bun.com/docs/installation",
"icon": "download",
"primary": true
}
]
},
"contextual": {
"options": ["copy", "view", "chatgpt", "claude", "perplexity", "mcp", "cursor", "vscode"]
},
"styling": {
"codeblocks": {
"theme": {
"light": "github-light",
"dark": "dracula"
}
}
},
"navigation": {
"tabs": [
{
"tab": "Runtime",
"icon": "cog",
"groups": [
{
"group": "Get Started",
"icon": "terminal",
"pages": [
"/index",
"/installation",
"/quickstart",
"/typescript",
"/runtime/templating/init",
"/runtime/templating/create"
]
},
{
"group": "Core Runtime",
"icon": "cog",
"pages": ["/runtime/index", "/runtime/watch-mode", "/runtime/debugger", "/runtime/bunfig"]
},
{
"group": "File & Module System",
"icon": "file",
"pages": [
"/runtime/file-types",
"/runtime/module-resolution",
"/runtime/jsx",
"/runtime/auto-install",
"/runtime/plugins",
"/runtime/file-system-router"
]
},
{
"group": "HTTP server",
"icon": "server",
"pages": [
"/runtime/http/server",
"/runtime/http/routing",
"/runtime/http/cookies",
"/runtime/http/tls",
"/runtime/http/error-handling",
"/runtime/http/metrics"
]
},
{
"group": "Networking",
"icon": "globe",
"expanded": true,
"pages": [
"/runtime/networking/fetch",
"/runtime/http/websockets",
"/runtime/networking/tcp",
"/runtime/networking/udp",
"/runtime/networking/dns"
]
},
{
"group": "Data & Storage",
"icon": "database",
"pages": [
"/runtime/cookies",
"/runtime/file-io",
"/runtime/streams",
"/runtime/binary-data",
"/runtime/archive",
"/runtime/sql",
"/runtime/sqlite",
"/runtime/s3",
"/runtime/redis"
]
},
{
"group": "Concurrency",
"icon": "split",
"pages": ["/runtime/workers"]
},
{
"group": "Process & System",
"icon": "computer",
"pages": ["/runtime/environment-variables", "/runtime/shell", "/runtime/child-process"]
},
{
"group": "Interop & Tooling",
"icon": "puzzle",
"pages": ["/runtime/node-api", "/runtime/ffi", "/runtime/c-compiler", "/runtime/transpiler"]
},
{
"group": "Utilities",
"icon": "wrench",
"pages": [
"/runtime/secrets",
"/runtime/console",
"/runtime/yaml",
"/runtime/markdown",
"/runtime/json5",
"/runtime/jsonl",
"/runtime/html-rewriter",
"/runtime/hashing",
"/runtime/glob",
"/runtime/semver",
"/runtime/color",
"/runtime/utils"
]
},
{
"group": "Standards & Compatibility",
"icon": "badge-check",
"pages": ["/runtime/globals", "/runtime/bun-apis", "/runtime/web-apis", "/runtime/nodejs-compat"]
},
{
"group": "Contributing",
"icon": "heart",
"pages": [
"/project/roadmap",
"/project/benchmarking",
"/project/contributing",
"/project/building-windows",
"/project/bindgen",
"/project/license"
]
}
]
},
{
"tab": "Package Manager",
"icon": "box",
"groups": [
{
"group": "Core Commands",
"icon": "terminal",
"pages": ["/pm/cli/install", "/pm/cli/add", "/pm/cli/remove", "/pm/cli/update", "/pm/bunx"]
},
{
"group": "Publishing & Analysis",
"icon": "upload",
"pages": ["/pm/cli/publish", "/pm/cli/outdated", "/pm/cli/why", "/pm/cli/audit", "/pm/cli/info"]
},
{
"group": "Workspace Management",
"icon": "folders",
"pages": ["/pm/workspaces", "/pm/catalogs", "/pm/cli/link", "/pm/cli/pm"]
},
{
"group": "Advanced Configuration",
"icon": "settings",
"pages": [
"/pm/cli/patch",
"/pm/filter",
"/pm/global-cache",
"/pm/isolated-installs",
"/pm/lockfile",
"/pm/lifecycle",
"/pm/scopes-registries",
"/pm/overrides",
"/pm/security-scanner-api",
"/pm/npmrc"
]
}
]
},
{
"tab": "Bundler",
"icon": "combine",
"groups": [
{
"group": "Core",
"icon": "package",
"pages": ["/bundler/index"]
},
{
"group": "Development Server",
"icon": "monitor",
"pages": ["/bundler/fullstack", "/bundler/hot-reloading"]
},
{
"group": "Asset Processing",
"icon": "image",
"pages": ["/bundler/html-static", "/bundler/css", "/bundler/loaders"]
},
{
"group": "Single File Executable",
"icon": "binary",
"pages": ["/bundler/executables"]
},
{
"group": "Extensions",
"icon": "plug",
"pages": ["/bundler/plugins", "/bundler/macros"]
},
{
"group": "Optimization",
"icon": "zap",
"pages": ["/bundler/bytecode", "/bundler/minifier"]
},
{
"group": "Migration",
"icon": "arrow-right",
"pages": ["/bundler/esbuild"]
}
]
},
{
"tab": "Test Runner",
"icon": "flask-conical",
"groups": [
{
"group": "Getting Started",
"icon": "circle-play",
"pages": ["/test/index", "/test/writing-tests", "/test/configuration"]
},
{
"group": "Test Execution",
"icon": "zap",
"pages": ["/test/runtime-behavior", "/test/discovery"]
},
{
"group": "Test Features",
"icon": "sparkles",
"pages": ["/test/lifecycle", "/test/mocks", "/test/snapshots", "/test/dates-times"]
},
{
"group": "Specialized Testing",
"icon": "microscope",
"pages": ["/test/dom"]
},
{
"group": "Reporting",
"icon": "file-text",
"pages": ["/test/code-coverage", "/test/reporters"]
}
]
},
{
"tab": "Guides",
"icon": "map",
"groups": [
{
"group": "Overview",
"icon": "globe",
"pages": ["/guides/index"]
},
{
"group": "Deployment",
"icon": "rocket",
"pages": [
"/guides/deployment/vercel",
"/guides/deployment/railway",
"/guides/deployment/render",
"/guides/deployment/aws-lambda",
"/guides/deployment/digital-ocean",
"/guides/deployment/google-cloud-run"
]
},
{
"group": "Runtime & Debugging",
"icon": "bug",
"pages": [
"/guides/runtime/typescript",
"/guides/runtime/tsconfig-paths",
"/guides/runtime/vscode-debugger",
"/guides/runtime/web-debugger",
"/guides/runtime/heap-snapshot",
"/guides/runtime/build-time-constants",
"/guides/runtime/define-constant",
"/guides/runtime/cicd",
"/guides/runtime/codesign-macos-executable"
]
},
{
"group": "Utilities",
"icon": "wrench",
"pages": [
"/guides/util/upgrade",
"/guides/util/detect-bun",
"/guides/util/version",
"/guides/util/hash-a-password",
"/guides/util/javascript-uuid",
"/guides/util/base64",
"/guides/util/gzip",
"/guides/util/deflate",
"/guides/util/escape-html",
"/guides/util/deep-equals",
"/guides/util/sleep",
"/guides/util/file-url-to-path",
"/guides/util/path-to-file-url",
"/guides/util/which-path-to-executable-bin",
"/guides/util/import-meta-dir",
"/guides/util/import-meta-file",
"/guides/util/import-meta-path",
"/guides/util/entrypoint",
"/guides/util/main"
]
},
{
"group": "Ecosystem & Frameworks",
"icon": "puzzle",
"pages": [
"/guides/ecosystem/astro",
"/guides/ecosystem/discordjs",
"/guides/ecosystem/docker",
"/guides/ecosystem/drizzle",
"/guides/ecosystem/gel",
"/guides/ecosystem/elysia",
"/guides/ecosystem/express",
"/guides/ecosystem/hono",
"/guides/ecosystem/mongoose",
"/guides/ecosystem/neon-drizzle",
"/guides/ecosystem/neon-serverless-postgres",
"/guides/ecosystem/nextjs",
"/guides/ecosystem/nuxt",
"/guides/ecosystem/pm2",
"/guides/ecosystem/prisma",
"/guides/ecosystem/prisma-postgres",
"/guides/ecosystem/qwik",
"/guides/ecosystem/react",
"/guides/ecosystem/remix",
"/guides/ecosystem/tanstack-start",
"/guides/ecosystem/sentry",
"/guides/ecosystem/solidstart",
"/guides/ecosystem/ssr-react",
"/guides/ecosystem/stric",
"/guides/ecosystem/sveltekit",
"/guides/ecosystem/systemd",
"/guides/ecosystem/vite",
"/guides/ecosystem/upstash"
]
},
{
"group": "HTTP & Networking",
"icon": "globe",
"pages": [
"/guides/http/server",
"/guides/http/simple",
"/guides/http/fetch",
"/guides/http/hot",
"/guides/http/cluster",
"/guides/http/tls",
"/guides/http/proxy",
"/guides/http/stream-file",
"/guides/http/file-uploads",
"/guides/http/fetch-unix",
"/guides/http/stream-iterator",
"/guides/http/stream-node-streams-in-bun"
]
},
{
"group": "WebSocket",
"icon": "radio",
"pages": [
"/guides/websocket/simple",
"/guides/websocket/pubsub",
"/guides/websocket/context",
"/guides/websocket/compression"
]
},
{
"group": "Processes & System",
"icon": "cpu",
"pages": [
"/guides/process/spawn",
"/guides/process/spawn-stdout",
"/guides/process/spawn-stderr",
"/guides/process/argv",
"/guides/process/stdin",
"/guides/process/ipc",
"/guides/process/ctrl-c",
"/guides/process/os-signals",
"/guides/process/nanoseconds",
"/guides/runtime/shell",
"/guides/runtime/timezone",
"/guides/runtime/set-env",
"/guides/runtime/read-env"
]
},
{
"group": "Package Manager",
"icon": "package",
"pages": [
"/guides/install/add",
"/guides/install/add-dev",
"/guides/install/add-optional",
"/guides/install/add-peer",
"/guides/install/add-git",
"/guides/install/add-tarball",
"/guides/install/npm-alias",
"/guides/install/workspaces",
"/guides/install/custom-registry",
"/guides/install/registry-scope",
"/guides/install/azure-artifacts",
"/guides/install/jfrog-artifactory",
"/guides/install/trusted",
"/guides/install/yarnlock",
"/guides/install/from-npm-install-to-bun-install",
"/guides/install/git-diff-bun-lockfile",
"/guides/install/cicd"
]
},
{
"group": "Test Runner",
"icon": "flask-conical",
"pages": [
"/guides/test/run-tests",
"/guides/test/watch-mode",
"/guides/test/migrate-from-jest",
"/guides/test/mock-functions",
"/guides/test/spy-on",
"/guides/test/mock-clock",
"/guides/test/snapshot",
"/guides/test/update-snapshots",
"/guides/test/coverage",
"/guides/test/coverage-threshold",
"/guides/test/concurrent-test-glob",
"/guides/test/skip-tests",
"/guides/test/todo-tests",
"/guides/test/timeout",
"/guides/test/bail",
"/guides/test/rerun-each",
"/guides/test/testing-library",
"/guides/test/happy-dom",
"/guides/test/svelte-test"
]
},
{
"group": "Runtime & Debugging",
"icon": "bug",
"pages": [
"/guides/runtime/vscode-debugger",
"/guides/runtime/web-debugger",
"/guides/runtime/heap-snapshot",
"/guides/runtime/build-time-constants",
"/guides/runtime/define-constant",
"/guides/runtime/cicd",
"/guides/runtime/codesign-macos-executable"
]
},
{
"group": "Module System",
"icon": "box",
"pages": [
"/guides/runtime/import-json",
"/guides/runtime/import-toml",
"/guides/runtime/import-yaml",
"/guides/runtime/import-json5",
"/guides/runtime/import-html",
"/guides/util/import-meta-dir",
"/guides/util/import-meta-file",
"/guides/util/import-meta-path",
"/guides/util/entrypoint",
"/guides/util/main"
]
},
{
"group": "File System",
"icon": "folder",
"pages": [
"/guides/read-file/string",
"/guides/read-file/buffer",
"/guides/read-file/uint8array",
"/guides/read-file/arraybuffer",
"/guides/read-file/json",
"/guides/read-file/mime",
"/guides/read-file/exists",
"/guides/read-file/watch",
"/guides/read-file/stream",
"/guides/write-file/basic",
"/guides/write-file/blob",
"/guides/write-file/response",
"/guides/write-file/append",
"/guides/write-file/filesink",
"/guides/write-file/stream",
"/guides/write-file/stdout",
"/guides/write-file/cat",
"/guides/write-file/file-cp",
"/guides/write-file/unlink",
"/guides/runtime/delete-file",
"/guides/runtime/delete-directory"
]
},
{
"group": "Utilities",
"icon": "wrench",
"pages": [
"/guides/util/hash-a-password",
"/guides/util/javascript-uuid",
"/guides/util/base64",
"/guides/util/gzip",
"/guides/util/deflate",
"/guides/util/escape-html",
"/guides/util/deep-equals",
"/guides/util/sleep",
"/guides/util/file-url-to-path",
"/guides/util/path-to-file-url",
"/guides/util/which-path-to-executable-bin"
]
},
{
"group": "HTML Processing",
"icon": "file-code",
"pages": ["/guides/html-rewriter/extract-links", "/guides/html-rewriter/extract-social-meta"]
},
{
"group": "Binary Data",
"icon": "binary",
"pages": [
"/guides/binary/arraybuffer-to-string",
"/guides/binary/arraybuffer-to-buffer",
"/guides/binary/arraybuffer-to-blob",
"/guides/binary/arraybuffer-to-array",
"/guides/binary/arraybuffer-to-typedarray",
"/guides/binary/buffer-to-string",
"/guides/binary/buffer-to-arraybuffer",
"/guides/binary/buffer-to-blob",
"/guides/binary/buffer-to-typedarray",
"/guides/binary/buffer-to-readablestream",
"/guides/binary/blob-to-string",
"/guides/binary/blob-to-arraybuffer",
"/guides/binary/blob-to-typedarray",
"/guides/binary/blob-to-dataview",
"/guides/binary/blob-to-stream",
"/guides/binary/typedarray-to-string",
"/guides/binary/typedarray-to-arraybuffer",
"/guides/binary/typedarray-to-buffer",
"/guides/binary/typedarray-to-blob",
"/guides/binary/typedarray-to-dataview",
"/guides/binary/typedarray-to-readablestream",
"/guides/binary/dataview-to-string"
]
},
{
"group": "Streams",
"icon": "waves",
"pages": [
"/guides/streams/to-string",
"/guides/streams/to-json",
"/guides/streams/to-blob",
"/guides/streams/to-buffer",
"/guides/streams/to-arraybuffer",
"/guides/streams/to-typedarray",
"/guides/streams/to-array",
"/guides/streams/node-readable-to-string",
"/guides/streams/node-readable-to-json",
"/guides/streams/node-readable-to-blob",
"/guides/streams/node-readable-to-uint8array",
"/guides/streams/node-readable-to-arraybuffer"
]
}
]
},
{
"tab": "Reference",
"icon": "book",
"href": "https://bun.com/reference"
},
{
"tab": "Blog",
"icon": "newspaper",
"href": "https://bun.com/blog"
},
{
"tab": "Feedback",
"icon": "lightbulb",
"pages": ["/feedback"]
}
]
},
"footer": {
"socials": {
"x": "https://x.com/bunjavascript",
"github": "https://github.com/oven-sh/bun",
"discord": "https://bun.com/discord",
"youtube": "https://www.youtube.com/@bunjs"
}
}
}