ironmark 1.8.0

Fast Markdown to HTML parser written in Rust with WebAssembly bindings
Documentation

ironmark

CI npm crates.io

Fast Markdown to HTML/AST parser written in Rust with zero third-party parsing dependencies. Fully compliant with CommonMark 0.31.2 (652/652 spec tests pass). Available as a Rust crate and as an npm package via WebAssembly, with both HTML and AST output APIs.

Options

Extensions (default true)

Option JS (camelCase) Rust (snake_case) Description
Hard breaks hardBreaks hard_breaks Every newline becomes <br />
Highlight enableHighlight enable_highlight ==text==<mark>
Strikethrough enableStrikethrough enable_strikethrough ~~text~~<del>
Underline enableUnderline enable_underline ++text++<u>
Tables enableTables enable_tables Pipe table syntax
Autolink enableAutolink enable_autolink Bare URLs & emails → <a>
Task lists enableTaskLists enable_task_lists - [ ] / - [x] checkboxes
Indented code enableIndentedCodeBlocks enable_indented_code_blocks 4-space indent → <pre><code>

Extensions (default false)

Option JS (camelCase) Rust (snake_case) Description
Wiki links enableWikiLinks enable_wiki_links [[page]]<a href="page">
LaTeX math enableLatexMath enable_latex_math $inline$ and $$display$$<span class="math-…">
Heading IDs enableHeadingIds enable_heading_ids Auto id= on headings from slugified text
Heading anchors enableHeadingAnchors enable_heading_anchors <a class="anchor"> inside each heading (implies IDs)
Permissive headings permissiveAtxHeaders permissive_atx_headers Allow #Heading without space after #

Security

Option JS (camelCase) Rust (snake_case) Default Description
Disable raw HTML disableRawHtml disable_raw_html false Escape all HTML blocks and inline HTML
No HTML blocks noHtmlBlocks no_html_blocks false Escape block-level HTML only (more granular than disableRawHtml)
No HTML spans noHtmlSpans no_html_spans false Escape inline HTML only
Tag filter tagFilter tag_filter false GFM tag filter: escape <script>, <iframe>, etc.
Max nesting max_nesting_depth 128 Limit blockquote/list nesting depth (DoS prevention)
Max input size max_input_size 0 (no limit) Truncate input beyond this byte count

In the WASM build, max_nesting_depth is fixed at 128 and max_input_size at 10 MB.

Dangerous URI schemes (javascript:, vbscript:, data: except data:image/…) are always stripped from link and image destinations, regardless of options.

Other options

Option JS (camelCase) Rust (snake_case) Default Description
Collapse whitespace collapseWhitespace collapse_whitespace false Collapse runs of spaces/tabs in text to one space

JavaScript / TypeScript

npm install ironmark
# or
pnpm add ironmark

Node.js

WASM is embedded and loaded synchronously — no init() needed:

import { parse } from "ironmark";

const html = parse("# Hello\n\nThis is **fast**.");

// safe mode for untrusted input
const safe = parse(userInput, { disableRawHtml: true });

AST Output

Use parseToAst() when you need the block-level document structure instead of rendered HTML.

import { parseToAst } from "ironmark";

const astJson = parseToAst("# Hello\n\n- [x] done");
const ast = JSON.parse(astJson);

parseToAst() returns a JSON string for portability across JS runtimes and WASM boundaries.

Browser / Bundler

Call init() once before using parse(). It's idempotent and optionally accepts a custom .wasm URL.

import { init, parse } from "ironmark";

await init();

const html = parse("# Hello\n\nThis is **fast**.");

Vite

import { init, parse } from "ironmark";
import wasmUrl from "ironmark/ironmark.wasm?url";

await init(wasmUrl);

const html = parse("# Hello\n\nThis is **fast**.");

Rust

cargo add ironmark
use ironmark::{parse, ParseOptions};

fn main() {
    // with defaults
    let html = parse("# Hello\n\nThis is **fast**.", &ParseOptions::default());

    // with custom options
    let html = parse("line one\nline two", &ParseOptions {
        hard_breaks: false,
        enable_strikethrough: false,
        ..Default::default()
    });

    // safe mode for untrusted input
    let html = parse("<script>alert(1)</script>", &ParseOptions {
        disable_raw_html: true,
        max_input_size: 1_000_000, // 1 MB
        ..Default::default()
    });
}

AST Output

parse_to_ast() returns the typed Rust AST (Block) directly:

use ironmark::{Block, ParseOptions, parse_to_ast};

fn main() {
    let ast = parse_to_ast("# Hello", &ParseOptions::default());

    match ast {
        Block::Document { children } => {
            println!("top-level blocks: {}", children.len());
        }
        _ => unreachable!("root nodes always Document"),
    }
}

Exported AST types:

  • Block
  • ListKind
  • TableData
  • TableAlignment

C / C++

The crate compiles to a static library (libironmark.a) that exposes two C functions. A header is provided at include/ironmark.h.

Build the library

cargo build --release
# output: target/release/libironmark.a

Link

# Linux
cc -o example example.c -L target/release -l ironmark -lpthread -ldl

# macOS
cc -o example example.c -L target/release -l ironmark \
   -framework CoreFoundation -framework Security

Usage

#include "include/ironmark.h"
#include <stdio.h>

int main(void) {
    char *html = ironmark_parse("# Hello\n\nThis is **fast**.");
    if (html) {
        printf("%s\n", html);
        ironmark_free(html);
    }
    return 0;
}

Memory contract: ironmark_parse returns a heap-allocated string. You must free it with ironmark_free. Passing any other pointer to ironmark_free is undefined behaviour. Both functions are null-safe: ironmark_parse(NULL) returns NULL; ironmark_free(NULL) is a no-op.

Parsing always uses the default ParseOptions (all extensions enabled, disable_raw_html off). Options are not yet configurable through the C API.

Benchmarks

Compares ironmark against pulldown-cmark, comrak, markdown-it, and markdown-rs. Results are available at ph1p.js.org/ironmark/#benchmark.

cargo bench                          # run all benchmarks
cargo bench --features bench-md4c   # include md4c (requires: brew install md4c)
pnpm bench                          # run + update playground data

Development

This project uses pnpm for package management.

Build from source

pnpm setup:wasm
pnpm build
Command Description
pnpm setup:wasm Install prerequisites
pnpm build Release WASM build
pnpm build:dev Debug WASM build
pnpm test Run Rust tests
pnpm check Format check
pnpm clean Remove build artifacts

Troubleshooting

wasm32-unknown-unknown target not found or wasm-bindgen not found — run pnpm setup:wasm to install all prerequisites.