An open API service indexing awesome lists of open source software.

https://github.com/ph1p/ironmark

A blazing fast markdown to HTML/AST parser
https://github.com/ph1p/ironmark

javascript markdown rust typescript wasm

Last synced: 3 months ago
JSON representation

A blazing fast markdown to HTML/AST parser

Awesome Lists containing this project

README

          

# ironmark

[![CI](https://github.com/ph1p/ironmark/actions/workflows/ci.yml/badge.svg)](https://github.com/ph1p/ironmark/actions/workflows/ci.yml) [![npm](https://img.shields.io/npm/v/ironmark)](https://www.npmjs.com/package/ironmark) [![crates.io](https://img.shields.io/crates/v/ironmark)](https://crates.io/crates/ironmark)

Fast Markdown to HTML/AST parser written in Rust with **zero third-party** parsing dependencies. Fully compliant with [CommonMark 0.31.2](https://spec.commonmark.org/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 `
` |
| Highlight | `enableHighlight` | `enable_highlight` | `==text==` → `` |
| Strikethrough | `enableStrikethrough` | `enable_strikethrough` | `~~text~~` → `` |
| Underline | `enableUnderline` | `enable_underline` | `++text++` → `` |
| Tables | `enableTables` | `enable_tables` | Pipe table syntax |
| Autolink | `enableAutolink` | `enable_autolink` | Bare URLs & emails → `` |
| Task lists | `enableTaskLists` | `enable_task_lists` | `- [ ]` / `- [x]` checkboxes |
| Indented code | `enableIndentedCodeBlocks` | `enable_indented_code_blocks` | 4-space indent → `

` |

### Extensions (default `false`)

| Option | JS (`camelCase`) | Rust (`snake_case`) | Description |
| ------------------- | ---------------------- | ------------------------ | ------------------------------------------------------ |
| Wiki links | `enableWikiLinks` | `enable_wiki_links` | `[[page]]` → `
` |
| LaTeX math | `enableLatexMath` | `enable_latex_math` | `$inline$` and `$$display$$` → `` |
| Heading IDs | `enableHeadingIds` | `enable_heading_ids` | Auto `id=` on headings from slugified text |
| Heading anchors | `enableHeadingAnchors` | `enable_heading_anchors` | `
` 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 ``, `<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

```bash
npm install ironmark
# or
pnpm add ironmark
```

### Node.js

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

```ts
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.

```ts
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.

```ts
import { init, parse } from "ironmark";

await init();

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

#### Vite

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

await init(wasmUrl);

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

## Rust

```bash
cargo add ironmark
```

```rust
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)", &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:

```rust
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

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

### Link

```sh
# 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

```c
#include "include/ironmark.h"
#include

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](https://ph1p.js.org/ironmark/#benchmark).

```bash
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](https://pnpm.io/) for package management.

### Build from source

```bash
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.