//md4xbypi0

md4x

πŸ“„ Fast and small markdown parser and renderer

8
0
8
2
C

πŸ“„ MD4X

npm version
md4x.wasm gzip size

Fast and Small markdown parser and renderer based on mity/md4c.

[!TIP]
Online Playground

CLI

# 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

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.

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).

NAPI (Node.js native)

Synchronous, zero-overhead native addon. Best performance for server-side use.

import { renderToHtml } from "md4x/napi";

WASM (universal)

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.

Benchmarks

(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.

Zig Package

MD4X can be consumed as a Zig package dependency via build.zig.zon.

Building

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

C Library

SAX-like streaming parser with no AST construction. Link against libmd4x and the renderer you need.

HTML Renderer

#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

#include "md4x.h"
#include "md4x-ast.h"

md_ast(input, input_size, output, stdout, MD_DIALECT_GITHUB, 0);

ANSI Renderer

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

#include "md4x.h"
#include "md4x-text.h"

md_text(input, input_size, output, stdout, MD_DIALECT_GITHUB, 0);

Meta Renderer

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"}]}

Low-Level Parser

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);

License

MIT

[beta]v0.14.0