https://github.com/unjs/md4x
๐ Fast and small markdown parser and renderer
https://github.com/unjs/md4x
Last synced: 4 months ago
JSON representation
๐ Fast and small markdown parser and renderer
- Host: GitHub
- URL: https://github.com/unjs/md4x
- Owner: unjs
- License: other
- Created: 2026-02-19T10:51:41.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-03-16T09:29:45.000Z (4 months ago)
- Last Synced: 2026-03-28T13:35:58.550Z (4 months ago)
- Language: C
- Homepage: https://md4x.unjs.io/#/playground
- Size: 1.87 MB
- Stars: 346
- Watchers: 1
- Forks: 11
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.md
- Agents: AGENTS.md
Awesome Lists containing this project
- awesome-web-editor - md4x - Fast and small markdown parser and renderer. (Markdown editor)
README
# ๐ MD4X
[![npm version][npm version]][npm link]
![][wasm size]
Fast and Small markdown parser and renderer based on [mity/md4c](https://github.com/mity/md4c/).
**[Online Playground](https://md4x.unjs.io/#/playground)**
## Features
- **Fast** โ Written in C, **~6x** faster than markdown-it
- **CLI** โ Render local files, remote URLs, GitHub repos, npm packages
- **Small** โ **~100KB** gzip WASM binary works in Node.js and Browser
- **Multi-format output** โ HTML, JSON AST, ANSI terminal, plain text, markdown, metadata
- **Streaming heal** โ Fix incomplete markdown from LLM output in real-time
- **Full CommonMark** โ Passes the CommonMark spec
- **GitHub Flavored Markdown** โ Tables, task lists, strikethrough, autolinks, alerts
- **Built-in YAML frontmatter** โ Parsed via libyaml into structured data
- **Extra extensions** โ LaTeX math, wiki links, underline, inline attributes
- **Comark (MDC) support** โ Block and inline components with props, slots
- **Universal JS** โ Native Node.js addon (NAPI) + portable WASM for browsers, Deno, Bun, edge workers
- **C library** โ SAX-like streaming parser, zero-copy, no AST allocation overhead
- **Zig package** โ Consumable as a Zig dependency
## Showcase
- [pi0/mdshot](https://github.com/pi0/mdshot) โ Render beautiful screenshots from Markdown.
- [pi0/mdzilla](https://github.com/pi0/mdzilla) โ Markdown browser for humans and agents.
## CLI
```sh
# Local files
npx md4x README.md # ANSI output
npx md4x README.md -t html # HTML output
npx md4x README.md -t text # Plain text output (strip markdown)
npx md4x README.md -t ast # JSON AST output (comark)
npx md4x README.md -t meta # Metadata JSON output
npx md4x README.md -t markdown # Clean markdown (strip MDC/frontmatter/HTML)
npx md4x README.md -t heal # Heal incomplete markdown
npx md4x README.md --heal # Heal before rendering (any format)
npx md4x README.md --heal -t json # Heal + JSON AST output
# Remote sources
npx md4x https://nitro.build/guide # Fetch and render any URL
npx md4x gh:nitrojs/nitro # GitHub repo โ README.md
npx md4x npm:vue@3 # npm package at specific version
# Stdin
echo "# Hello" | npx md4x -t text
cat README.md | npx md4x -t html
# Output to file
npx md4x README.md -t meta -o README.json
# Full HTML document
npx md4x README.md -t html -f --html-title="My Docs" # Wrap in full HTML with
npx md4x README.md -t html -f --html-css=style.css # Add CSS link
```
### Install from AUR
```sh
yay -S md4x # md4x-git
```
## JavaScript
Available as a native Node.js addon (NAPI) for maximum performance, or as a portable WASM module that works in any JavaScript runtime (Node.js, Deno, Bun, browsers, edge workers, etc.).
The bare `md4x` import auto-selects NAPI on Node.js and WASM elsewhere.
```js
import {
init,
renderToHtml,
renderToAST,
parseAST,
renderToAnsi,
renderToText,
renderToMarkdown,
renderToMeta,
parseMeta,
heal,
} from "md4x";
// await init(); // required for WASM, optional for NAPI
const html = renderToHtml("# Hello, **world**!");
const json = renderToAST("# Hello, **world**!"); // raw JSON string
const ast = parseAST("# Hello, **world**!"); // parsed ComarkTree object
const ansi = renderToAnsi("# Hello, **world**!");
const text = renderToText("# Hello, **world**!"); // plain text (stripped)
const md = renderToMarkdown("# Hello, **world**!"); // clean standard markdown
const metaJson = renderToMeta("# Hello, **world**!"); // raw JSON string
const meta = parseMeta("# Hello, **world**!"); // parsed meta
const healed = heal("**incomplete streaming mark"); // "**incomplete streaming mark**"
```
Both NAPI and WASM export a unified API with `init()`. For WASM, `init()` must be called before rendering. For NAPI, it is optional (the native binding loads lazily on first render call).
#### NAPI (Node.js native)
Synchronous, zero-overhead native addon. Best performance for server-side use.
```js
import { renderToHtml } from "md4x/napi";
```
#### WASM (universal)
Works anywhere with WebAssembly support. Requires a one-time async initialization.
```js
import { init, renderToHtml } from "md4x/wasm";
await init(); // call once before rendering
const html = renderToHtml("# Hello");
```
`init()` accepts an optional options object with a `wasm` property (`ArrayBuffer`, `Response`, `WebAssembly.Module`, or `Promise`). When called with no arguments, it loads the bundled `.wasm` file automatically.
Benchmarks
(source: [packages/md4x/bench](./packages/md4x/bench))
```
bun packages/md4x/bench/index.mjs
clk: ~5.54 GHz
cpu: AMD Ryzen 9 9950X3D 16-Core Processor
runtime: bun 1.3.9 (x64-linux)
benchmark avg (min โฆ max) p75 / p99 (min โฆ top 1%)
md4x-napi 3.32 ยตs/iter 3.34 ยตs 3.40 ยตs โโโ
โโ
โโโโโโ
md4x-wasm 5.76 ยตs/iter 5.82 ยตs 9.46 ยตs โโโโโโโโโโโ
md4w 5.77 ยตs/iter 5.77 ยตs 9.82 ยตs โโโโโโโโโโโ
markdown-it 21.41 ยตs/iter 20.98 ยตs 41.88 ยตs โโโโโโโโโโโ
markdown-exit 23.59 ยตs/iter 23.65 ยตs 41.84 ยตs โโโโโโโโโโโ
summary
md4x-napi
1.74x faster than md4x-wasm
1.74x faster than md4w
6.45x faster than markdown-it
7.11x faster than markdown-exit
md4x (napi) ast (medium) 6.91 ยตs/iter 6.94 ยตs 6.96 ยตs โโโโโโโโโ
โโ
md4x (wasm) ast (medium) 8.28 ยตs/iter 8.36 ยตs 8.40 ยตs โโโโโโโโโโโ
summary
md4x (napi) ast (medium)
1.2x faster than md4x (wasm) ast (medium)
md4x (napi) parseAST (medium) 11.42 ยตs/iter 11.39 ยตs 11.77 ยตs โ
โโโ
โโโโโโโ
md4x (wasm) parseAST (medium) 12.64 ยตs/iter 12.71 ยตs 12.74 ยตs โโโโโโโโโโโ
md4w parseAST (medium) 11.79 ยตs/iter 11.94 ยตs 11.99 ยตs โโ
โ
โ
โโโโโโโ
markdown-it parseAST (medium) 15.96 ยตs/iter 16.01 ยตs 16.19 ยตs โ
โ
โ
โโ
โ
โโโโ
โ
markdown-exit parseAST (medium) 18.42 ยตs/iter 18.62 ยตs 19.26 ยตs โโโโโโโโโโโ
summary
md4x (napi) parseAST (medium)
1.03x faster than md4w parseAST (medium)
1.11x faster than md4x (wasm) parseAST (medium)
1.4x faster than markdown-it parseAST (medium)
1.61x faster than markdown-exit parseAST (medium)
```
Note: markdown-it parser returns an array of tokens while md4x returns nested comark AST.
### Code Highlighting
`renderToHtml` supports a `highlighter` option for custom syntax highlighting of fenced code blocks. The highlighter receives the raw code (HTML-unescaped) and block metadata (language, filename, highlighted lines), and returns a replacement HTML string or `undefined` to keep the default.
````js
import { renderToHtml } from "md4x";
import { createHighlighter } from "shiki";
const highlighter = await createHighlighter({
themes: ["github-dark"],
langs: ["js", "ts", "html", "css"],
});
const html = renderToHtml("```js\nconst x = 1;\n```", {
highlighter: (code, block) => {
if (!block.lang) return; // keep default for unknown languages
return highlighter.codeToHtml(code, {
lang: block.lang,
theme: "github-dark",
});
},
});
````
Code block metadata from the info string is parsed automatically:
````md
```ts [app.ts] {1,3-5}
// block.lang = "ts"
// block.filename = "app.ts"
// block.highlights = [1, 3, 4, 5]
```
````
### Markdown Healing
`heal()` fixes incomplete markdown from streaming LLM output โ closing unclosed bold, italic, strikethrough, inline code, code blocks, links, and more. Useful for rendering partial markdown in real-time as tokens arrive (inspired by [streamdown/remend](https://github.com/vercel/streamdown/tree/main/packages/remend)).
````js
import { heal } from "md4x";
heal("**bold"); // "**bold**"
heal("*ita"); // "*ita*"
heal("~~strike"); // "~~strike~~"
heal("`code"); // "`code`"
heal("```js\ncode"); // "```js\ncode\n```"
heal("[text](http:"); // "" (strips broken links)
````
All render functions also accept a `{ heal: true }` option to heal input before rendering in a single pass:
```js
import { renderToHtml, parseAST, renderToAnsi, renderToText } from "md4x";
// Heal + render in one call โ ideal for streaming LLM output
renderToHtml("# Hello **world", { heal: true });
// "
Hello world
\n"
parseAST("# Hello **world", { heal: true });
// { nodes: [["h1", {}, "Hello ", ["strong", {}, "world"]]], ... }
renderToAnsi("# Hello **world", { heal: true });
renderToText("# Hello **world", { heal: true });
// Combines with other options
renderToHtml("# Hello **world", { heal: true, full: true });
```
Benchmarks
```
bun packages/md4x/bench/heal.mjs
benchmark avg (min โฆ max) p75 / p99 (min โฆ top 1%)
md4x-napi heal (small) 702.85 ns/iter
md4x-wasm heal (small) 1.57 ยตs/iter
remend heal (small) 3.71 ยตs/iter
summary
md4x-napi heal (small)
2.23x faster than md4x-wasm heal (small)
5.28x faster than remend heal (small)
md4x-napi heal (medium) 2.13 ยตs/iter
md4x-wasm heal (medium) 3.23 ยตs/iter
remend heal (medium) 24.59 ยตs/iter
summary
md4x-napi heal (medium)
1.52x faster than md4x-wasm heal (medium)
11.55x faster than remend heal (medium)
md4x-napi heal (large) 95.09 ยตs/iter
md4x-wasm heal (large) 137.68 ยตs/iter
remend heal (large) 10.95 ms/iter
summary
md4x-napi heal (large)
1.45x faster than md4x-wasm heal (large)
115.18x faster than remend heal (large)
```
## Zig Package
MD4X can be consumed as a Zig package dependency via `build.zig.zon`.
## Building
Requires [Zig](https://ziglang.org/). No other external dependencies.
```sh
zig build # ReleaseFast (default)
zig build -Doptimize=Debug # Debug build
zig build wasm # WASM target (~163K)
zig build napi # Node.js NAPI addon
```
## C Library
SAX-like streaming parser with no AST construction. Link against `libmd4x` and the renderer you need.
#### HTML Renderer
```c
#include "md4x.h"
#include "md4x-html.h"
void output(const MD_CHAR* text, MD_SIZE size, void* userdata) {
fwrite(text, 1, size, (FILE*) userdata);
}
md_html(input, input_size, output, stdout, MD_DIALECT_GITHUB, 0);
```
#### JSON Renderer
```c
#include "md4x.h"
#include "md4x-ast.h"
md_ast(input, input_size, output, stdout, MD_DIALECT_GITHUB, 0);
```
#### ANSI Renderer
```c
#include "md4x.h"
#include "md4x-ansi.h"
md_ansi(input, input_size, output, stdout, MD_DIALECT_GITHUB, 0);
```
#### Text Renderer
Strips markdown formatting and produces plain text:
```c
#include "md4x.h"
#include "md4x-text.h"
md_text(input, input_size, output, stdout, MD_DIALECT_GITHUB, 0);
```
#### Markdown Renderer
Converts extended markdown (MDC/Comark) to clean, standard markdown. Strips frontmatter, HTML comments, raw HTML, and inline attributes. Converts block/inline components to HTML tags, wiki links to regular links.
```c
#include "md4x.h"
#include "md4x-markdown.h"
md_markdown(input, input_size, output, stdout, MD_DIALECT_ALL, 0);
```
#### Meta Renderer
Extracts frontmatter and headings as a flat JSON object:
```c
#include "md4x.h"
#include "md4x-meta.h"
md_meta(input, input_size, output, stdout, MD_DIALECT_GITHUB, 0);
// {"title":"Hello","headings":[{"level":1,"text":"Hello"}]}
```
#### Heal Utility
Fixes incomplete/streaming markdown by closing unclosed delimiters:
```c
#include "md4x-heal.h"
md_heal(input, input_size, output, stdout);
```
#### Low-Level Parser
For custom rendering, use the SAX-like parser directly:
```c
#include "md4x.h"
int enter_block(MD_BLOCKTYPE type, void* detail, void* userdata) { return 0; }
int leave_block(MD_BLOCKTYPE type, void* detail, void* userdata) { return 0; }
int enter_span(MD_SPANTYPE type, void* detail, void* userdata) { return 0; }
int leave_span(MD_SPANTYPE type, void* detail, void* userdata) { return 0; }
int text(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata) { return 0; }
MD_PARSER parser = {
.abi_version = 0,
.flags = MD_DIALECT_GITHUB,
.enter_block = enter_block,
.leave_block = leave_block,
.enter_span = enter_span,
.leave_span = leave_span,
.text = text,
};
md_parse(input, input_size, &parser, NULL);
```
## License
[MIT](./LICENSE.md)
[npm version]: https://badgen.net/npm/v/md4x?color=F0DB4F
[npm link]: https://npmx.dev/package/md4x
[wasm size]: https://badgen.net/https/md4x.unjs.io/_badges/wasm-size.json?1