Fast and Small markdown parser and renderer based on mity/md4c.
[!TIP]
Online Playground
# 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
# 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 <head>
npx md4x README.md -t html -f --html-css=style.css # Add CSS link
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.
import {
init,
renderToHtml,
renderToAST,
parseAST,
renderToAnsi,
renderToText,
renderToMeta,
parseMeta,
} 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 metaJson = renderToMeta("# Hello, **world**!"); // raw JSON string
const meta = parseMeta("# Hello, **world**!"); // parsed meta
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).
Synchronous, zero-overhead native addon. Best performance for server-side use.
import { renderToHtml } from "md4x/napi";
Works anywhere with WebAssembly support. Requires a one-time async initialization.
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<Response>). When called with no arguments, it loads the bundled .wasm file automatically.
(source: 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.
MD4X can be consumed as a Zig package dependency via build.zig.zon.
Requires Zig. No other external dependencies.
zig build # ReleaseFast (default)
zig build -Doptimize=Debug # Debug build
zig build wasm # WASM target (~163K)
zig build napi # Node.js NAPI addon
SAX-like streaming parser with no AST construction. Link against libmd4x and the renderer you need.
#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);
#include "md4x.h"
#include "md4x-ast.h"
md_ast(input, input_size, output, stdout, MD_DIALECT_GITHUB, 0);
#include "md4x.h"
#include "md4x-ansi.h"
md_ansi(input, input_size, output, stdout, MD_DIALECT_GITHUB, 0);
Strips markdown formatting and produces plain text:
#include "md4x.h"
#include "md4x-text.h"
md_text(input, input_size, output, stdout, MD_DIALECT_GITHUB, 0);
Extracts frontmatter and headings as a flat JSON object:
#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"}]}
For custom rendering, use the SAX-like parser directly:
#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);
We use cookies
We use cookies to analyze traffic and improve your experience. You can accept or reject analytics cookies.