{"id":47393418,"url":"https://github.com/unjs/md4x","last_synced_at":"2026-04-03T06:01:02.194Z","repository":{"id":339443764,"uuid":"1161643193","full_name":"unjs/md4x","owner":"unjs","description":"📄 Fast and small markdown parser and renderer","archived":false,"fork":false,"pushed_at":"2026-03-16T09:29:45.000Z","size":1965,"stargazers_count":346,"open_issues_count":7,"forks_count":11,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-28T13:35:58.550Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://md4x.unjs.io/#/playground","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/unjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"github":["pi0"]}},"created_at":"2026-02-19T10:51:41.000Z","updated_at":"2026-03-28T06:01:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/unjs/md4x","commit_stats":null,"previous_names":["pi0/md4x"],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/unjs/md4x","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unjs%2Fmd4x","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unjs%2Fmd4x/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unjs%2Fmd4x/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unjs%2Fmd4x/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/unjs","download_url":"https://codeload.github.com/unjs/md4x/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unjs%2Fmd4x/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31338149,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-03T04:42:29.251Z","status":"ssl_error","status_checked_at":"2026-04-03T04:42:12.667Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-03-20T02:00:28.227Z","updated_at":"2026-04-03T06:01:02.176Z","avatar_url":"https://github.com/unjs.png","language":"C","funding_links":["https://github.com/sponsors/pi0"],"categories":["Markdown editor"],"sub_categories":[],"readme":"# 📄 MD4X\n\n[![npm version][npm version]][npm link]\n![][wasm size]\n\nFast and Small markdown parser and renderer based on [mity/md4c](https://github.com/mity/md4c/).\n\n**[Online Playground](https://md4x.unjs.io/#/playground)**\n\n## Features\n\n- **Fast** — Written in C, **~6x** faster than markdown-it\n- **CLI** — Render local files, remote URLs, GitHub repos, npm packages\n- **Small** — **~100KB** gzip WASM binary works in Node.js and Browser\n- **Multi-format output** — HTML, JSON AST, ANSI terminal, plain text, markdown, metadata\n- **Streaming heal** — Fix incomplete markdown from LLM output in real-time\n- **Full CommonMark** — Passes the CommonMark spec\n- **GitHub Flavored Markdown** — Tables, task lists, strikethrough, autolinks, alerts\n- **Built-in YAML frontmatter** — Parsed via libyaml into structured data\n- **Extra extensions** — LaTeX math, wiki links, underline, inline attributes\n- **Comark (MDC) support** — Block and inline components with props, slots\n- **Universal JS** — Native Node.js addon (NAPI) + portable WASM for browsers, Deno, Bun, edge workers\n- **C library** — SAX-like streaming parser, zero-copy, no AST allocation overhead\n- **Zig package** — Consumable as a Zig dependency\n\n## Showcase\n\n- [pi0/mdshot](https://github.com/pi0/mdshot) — Render beautiful screenshots from Markdown.\n- [pi0/mdzilla](https://github.com/pi0/mdzilla) — Markdown browser for humans and agents.\n\n## CLI\n\n```sh\n# Local files\nnpx md4x README.md                          # ANSI output\nnpx md4x README.md -t html                  # HTML output\nnpx md4x README.md -t text                  # Plain text output (strip markdown)\nnpx md4x README.md -t ast                   # JSON AST output (comark)\nnpx md4x README.md -t meta                  # Metadata JSON output\nnpx md4x README.md -t markdown              # Clean markdown (strip MDC/frontmatter/HTML)\nnpx md4x README.md -t heal                  # Heal incomplete markdown\nnpx md4x README.md --heal                   # Heal before rendering (any format)\nnpx md4x README.md --heal -t json           # Heal + JSON AST output\n\n# Remote sources\nnpx md4x https://nitro.build/guide          # Fetch and render any URL\nnpx md4x gh:nitrojs/nitro                   # GitHub repo → README.md\nnpx md4x npm:vue@3                          # npm package at specific version\n\n# Stdin\necho \"# Hello\" | npx md4x -t text\ncat README.md | npx md4x  -t html\n\n# Output to file\nnpx md4x README.md -t meta -o README.json\n\n# Full HTML document\nnpx md4x README.md -t html -f --html-title=\"My Docs\"  # Wrap in full HTML with \u003chead\u003e\nnpx md4x README.md -t html -f --html-css=style.css    # Add CSS link\n```\n\n### Install from AUR\n\n```sh\nyay -S md4x # md4x-git\n```\n\n## JavaScript\n\nAvailable 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.).\n\nThe bare `md4x` import auto-selects NAPI on Node.js and WASM elsewhere.\n\n```js\nimport {\n  init,\n  renderToHtml,\n  renderToAST,\n  parseAST,\n  renderToAnsi,\n  renderToText,\n  renderToMarkdown,\n  renderToMeta,\n  parseMeta,\n  heal,\n} from \"md4x\";\n\n// await init(); // required for WASM, optional for NAPI\n\nconst html = renderToHtml(\"# Hello, **world**!\");\nconst json = renderToAST(\"# Hello, **world**!\"); // raw JSON string\nconst ast = parseAST(\"# Hello, **world**!\"); // parsed ComarkTree object\nconst ansi = renderToAnsi(\"# Hello, **world**!\");\nconst text = renderToText(\"# Hello, **world**!\"); // plain text (stripped)\nconst md = renderToMarkdown(\"# Hello, **world**!\"); // clean standard markdown\nconst metaJson = renderToMeta(\"# Hello, **world**!\"); // raw JSON string\nconst meta = parseMeta(\"# Hello, **world**!\"); // parsed meta\n\nconst healed = heal(\"**incomplete streaming mark\"); // \"**incomplete streaming mark**\"\n```\n\nBoth 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).\n\n#### NAPI (Node.js native)\n\nSynchronous, zero-overhead native addon. Best performance for server-side use.\n\n```js\nimport { renderToHtml } from \"md4x/napi\";\n```\n\n#### WASM (universal)\n\nWorks anywhere with WebAssembly support. Requires a one-time async initialization.\n\n```js\nimport { init, renderToHtml } from \"md4x/wasm\";\n\nawait init(); // call once before rendering\nconst html = renderToHtml(\"# Hello\");\n```\n\n`init()` accepts an optional options object with a `wasm` property (`ArrayBuffer`, `Response`, `WebAssembly.Module`, or `Promise\u003cResponse\u003e`). When called with no arguments, it loads the bundled `.wasm` file automatically.\n\n\u003cdetails\u003e\n\u003csummary\u003eBenchmarks\u003c/summary\u003e\n\n(source: [packages/md4x/bench](./packages/md4x/bench))\n\n```\nbun packages/md4x/bench/index.mjs\nclk: ~5.54 GHz\ncpu: AMD Ryzen 9 9950X3D 16-Core Processor\nruntime: bun 1.3.9 (x64-linux)\n\nbenchmark                      avg (min … max) p75 / p99    (min … top 1%)\nmd4x-napi                         3.32 µs/iter   3.34 µs   3.40 µs ▂▃▅█▅▂█▂▃▂▂\nmd4x-wasm                         5.76 µs/iter   5.82 µs   9.46 µs █▇▄▂▁▁▁▁▁▁▁\nmd4w                              5.77 µs/iter   5.77 µs   9.82 µs ▃█▄▂▁▁▁▁▁▁▁\nmarkdown-it                      21.41 µs/iter  20.98 µs  41.88 µs ▁█▃▁▁▁▁▁▁▁▁\nmarkdown-exit                    23.59 µs/iter  23.65 µs  41.84 µs ▁▄█▃▁▁▁▁▁▁▁\n\nsummary\n  md4x-napi\n   1.74x faster than md4x-wasm\n   1.74x faster than md4w\n   6.45x faster than markdown-it\n   7.11x faster than markdown-exit\n\nmd4x (napi) ast (medium)          6.91 µs/iter   6.94 µs   6.96 µs ▂▄█▄▄▂▂▄▅▁▂\nmd4x (wasm) ast (medium)          8.28 µs/iter   8.36 µs   8.40 µs ▆▃█▁▁█▆▃▃▆▃\n\nsummary\n  md4x (napi) ast (medium)\n   1.2x faster than md4x (wasm) ast (medium)\n\nmd4x (napi) parseAST (medium)    11.42 µs/iter  11.39 µs  11.77 µs ▅▃█▅▁▁▁▁▁▁▅\nmd4x (wasm) parseAST (medium)    12.64 µs/iter  12.71 µs  12.74 µs ▃▁▃▁▁▁▁▁██▃\nmd4w parseAST (medium)           11.79 µs/iter  11.94 µs  11.99 µs █▅▅▅▁▁▁▁██▅\nmarkdown-it parseAST (medium)    15.96 µs/iter  16.01 µs  16.19 µs ▅▅▅█▅▅▁▁█▅▅\nmarkdown-exit parseAST (medium)  18.42 µs/iter  18.62 µs  19.26 µs ▂▂▁█▂▁▂▁▂▁▂\n\nsummary\n  md4x (napi) parseAST (medium)\n   1.03x faster than md4w parseAST (medium)\n   1.11x faster than md4x (wasm) parseAST (medium)\n   1.4x faster than markdown-it parseAST (medium)\n   1.61x faster than markdown-exit parseAST (medium)\n```\n\nNote: markdown-it parser returns an array of tokens while md4x returns nested comark AST.\n\n\u003c/details\u003e\n\n### Code Highlighting\n\n`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.\n\n````js\nimport { renderToHtml } from \"md4x\";\nimport { createHighlighter } from \"shiki\";\n\nconst highlighter = await createHighlighter({\n  themes: [\"github-dark\"],\n  langs: [\"js\", \"ts\", \"html\", \"css\"],\n});\n\nconst html = renderToHtml(\"```js\\nconst x = 1;\\n```\", {\n  highlighter: (code, block) =\u003e {\n    if (!block.lang) return; // keep default for unknown languages\n    return highlighter.codeToHtml(code, {\n      lang: block.lang,\n      theme: \"github-dark\",\n    });\n  },\n});\n````\n\nCode block metadata from the info string is parsed automatically:\n\n````md\n```ts [app.ts] {1,3-5}\n// block.lang = \"ts\"\n// block.filename = \"app.ts\"\n// block.highlights = [1, 3, 4, 5]\n```\n````\n\n### Markdown Healing\n\n`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)).\n\n````js\nimport { heal } from \"md4x\";\n\nheal(\"**bold\"); // \"**bold**\"\nheal(\"*ita\"); // \"*ita*\"\nheal(\"~~strike\"); // \"~~strike~~\"\nheal(\"`code\"); // \"`code`\"\nheal(\"```js\\ncode\"); // \"```js\\ncode\\n```\"\nheal(\"[text](http:\"); // \"\"  (strips broken links)\n````\n\nAll render functions also accept a `{ heal: true }` option to heal input before rendering in a single pass:\n\n```js\nimport { renderToHtml, parseAST, renderToAnsi, renderToText } from \"md4x\";\n\n// Heal + render in one call — ideal for streaming LLM output\nrenderToHtml(\"# Hello **world\", { heal: true });\n// \"\u003ch1\u003eHello \u003cstrong\u003eworld\u003c/strong\u003e\u003c/h1\u003e\\n\"\n\nparseAST(\"# Hello **world\", { heal: true });\n// { nodes: [[\"h1\", {}, \"Hello \", [\"strong\", {}, \"world\"]]], ... }\n\nrenderToAnsi(\"# Hello **world\", { heal: true });\nrenderToText(\"# Hello **world\", { heal: true });\n\n// Combines with other options\nrenderToHtml(\"# Hello **world\", { heal: true, full: true });\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eBenchmarks\u003c/summary\u003e\n\n```\nbun packages/md4x/bench/heal.mjs\n\nbenchmark                      avg (min … max) p75 / p99    (min … top 1%)\nmd4x-napi heal (small)          702.85 ns/iter\nmd4x-wasm heal (small)            1.57 µs/iter\nremend heal (small)                3.71 µs/iter\n\nsummary\n  md4x-napi heal (small)\n   2.23x faster than md4x-wasm heal (small)\n   5.28x faster than remend heal (small)\n\nmd4x-napi heal (medium)          2.13 µs/iter\nmd4x-wasm heal (medium)          3.23 µs/iter\nremend heal (medium)             24.59 µs/iter\n\nsummary\n  md4x-napi heal (medium)\n   1.52x faster than md4x-wasm heal (medium)\n   11.55x faster than remend heal (medium)\n\nmd4x-napi heal (large)          95.09 µs/iter\nmd4x-wasm heal (large)         137.68 µs/iter\nremend heal (large)             10.95 ms/iter\n\nsummary\n  md4x-napi heal (large)\n   1.45x faster than md4x-wasm heal (large)\n   115.18x faster than remend heal (large)\n```\n\n\u003c/details\u003e\n\n## Zig Package\n\nMD4X can be consumed as a Zig package dependency via `build.zig.zon`.\n\n## Building\n\nRequires [Zig](https://ziglang.org/). No other external dependencies.\n\n```sh\nzig build                      # ReleaseFast (default)\nzig build -Doptimize=Debug     # Debug build\nzig build wasm                 # WASM target (~163K)\nzig build napi                 # Node.js NAPI addon\n```\n\n## C Library\n\nSAX-like streaming parser with no AST construction. Link against `libmd4x` and the renderer you need.\n\n#### HTML Renderer\n\n```c\n#include \"md4x.h\"\n#include \"md4x-html.h\"\n\nvoid output(const MD_CHAR* text, MD_SIZE size, void* userdata) {\n    fwrite(text, 1, size, (FILE*) userdata);\n}\n\nmd_html(input, input_size, output, stdout, MD_DIALECT_GITHUB, 0);\n```\n\n#### JSON Renderer\n\n```c\n#include \"md4x.h\"\n#include \"md4x-ast.h\"\n\nmd_ast(input, input_size, output, stdout, MD_DIALECT_GITHUB, 0);\n```\n\n#### ANSI Renderer\n\n```c\n#include \"md4x.h\"\n#include \"md4x-ansi.h\"\n\nmd_ansi(input, input_size, output, stdout, MD_DIALECT_GITHUB, 0);\n```\n\n#### Text Renderer\n\nStrips markdown formatting and produces plain text:\n\n```c\n#include \"md4x.h\"\n#include \"md4x-text.h\"\n\nmd_text(input, input_size, output, stdout, MD_DIALECT_GITHUB, 0);\n```\n\n#### Markdown Renderer\n\nConverts 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.\n\n```c\n#include \"md4x.h\"\n#include \"md4x-markdown.h\"\n\nmd_markdown(input, input_size, output, stdout, MD_DIALECT_ALL, 0);\n```\n\n#### Meta Renderer\n\nExtracts frontmatter and headings as a flat JSON object:\n\n```c\n#include \"md4x.h\"\n#include \"md4x-meta.h\"\n\nmd_meta(input, input_size, output, stdout, MD_DIALECT_GITHUB, 0);\n// {\"title\":\"Hello\",\"headings\":[{\"level\":1,\"text\":\"Hello\"}]}\n```\n\n#### Heal Utility\n\nFixes incomplete/streaming markdown by closing unclosed delimiters:\n\n```c\n#include \"md4x-heal.h\"\n\nmd_heal(input, input_size, output, stdout);\n```\n\n#### Low-Level Parser\n\nFor custom rendering, use the SAX-like parser directly:\n\n```c\n#include \"md4x.h\"\n\nint enter_block(MD_BLOCKTYPE type, void* detail, void* userdata) { return 0; }\nint leave_block(MD_BLOCKTYPE type, void* detail, void* userdata) { return 0; }\nint enter_span(MD_SPANTYPE type, void* detail, void* userdata) { return 0; }\nint leave_span(MD_SPANTYPE type, void* detail, void* userdata) { return 0; }\nint text(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata) { return 0; }\n\nMD_PARSER parser = {\n    .abi_version = 0,\n    .flags = MD_DIALECT_GITHUB,\n    .enter_block = enter_block,\n    .leave_block = leave_block,\n    .enter_span = enter_span,\n    .leave_span = leave_span,\n    .text = text,\n};\n\nmd_parse(input, input_size, \u0026parser, NULL);\n```\n\n## License\n\n[MIT](./LICENSE.md)\n\n[npm version]: https://badgen.net/npm/v/md4x?color=F0DB4F\n[npm link]: https://npmx.dev/package/md4x\n[wasm size]: https://badgen.net/https/md4x.unjs.io/_badges/wasm-size.json?1\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funjs%2Fmd4x","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Funjs%2Fmd4x","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funjs%2Fmd4x/lists"}