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

https://github.com/nberlette/comrak-wasm

TypeScript and WebAssembly bindings for Comrak, the Markdown-to-HTML renderer written in Rust.
https://github.com/nberlette/comrak-wasm

bun comrak deno jsr markdown markdown-parsing-rendering markdown-renderer markdown-to-html node rust typescript wasm webassembly

Last synced: 2 months ago
JSON representation

TypeScript and WebAssembly bindings for Comrak, the Markdown-to-HTML renderer written in Rust.

Awesome Lists containing this project

README

          

# [`@nick/comrak`]

High-performance Markdown to HTML converter powered by WebAssembly.

---

## Overview

`@nick/comrak` is a fast and efficient Markdown to HTML converter written in
Rust, compiled to WebAssembly, and wrapped with a high-level TypeScript API. It
renders HTML, CommonMark, and CommonMark XML, and mirrors the configurability of
the upstream `comrak` crate.

## Usage

Convert Markdown to HTML with a single function call:

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const markdown = "# Hello, **world**!";
const html = markdownToHTML(markdown);

assert.strictEqual(html, "

Hello, world!

\n");
```

Render any format you need:

```ts
import assert from "node:assert";
import {
markdownToCommonMark,
markdownToHTML,
markdownToXML,
} from "@nick/comrak";

const md = "# Hello, **world**!";

assert.strictEqual(
markdownToHTML(md),
"

Hello, world!

\n",
);
assert.strictEqual(
markdownToXML(md),
'\n' +
'\n' +
'\n' +
' \n' +
' Hello, \n' +
" \n" +
' world\n' +
"
\n" +
' !\n' +
" \n" +
"\n",
);
assert.strictEqual(markdownToCommonMark(md), "# Hello, **world**\\!\n");
```

Parse once and render anywhere:

```ts
import assert from "node:assert";
import {
type Options,
parseMarkdown,
renderCommonMark,
renderHTML,
renderXML,
} from "@nick/comrak";

const options = { extension: { tasklist: true } } satisfies Options;
const ast = parseMarkdown("# Hello, **world**!\n\n- [x] Done\n", options);

assert.strictEqual(
renderHTML(ast, options),
"

Hello, world!

\n" +
'
    \n
  • Done
  • \n
\n',
);
assert.strictEqual(
renderXML(ast, options),
'\n' +
'\n' +
'\n' +
' \n' +
' Hello, \n' +
" \n" +
' world\n' +
"
\n" +
' !\n' +
" \n" +
' \n' +
' \n' +
" \n" +
' Done\n' +
" \n" +
" \n" +
" \n" +
"\n",
);
assert.strictEqual(
renderCommonMark(ast, options),
"# Hello, **world**\\!\n\n- [x] Done\n",
);
```

Add plugins for custom rendering:

````ts
import assert from "node:assert";
import { markdownToHTML, Options } from "@nick/comrak";

const options = Options.default();
options.plugins.render.codefenceSyntaxHighlighter = {
highlight: (code, lang) => `highlighted:${lang ?? "none"}:${code.trim()}`,
pre: () => '

',

code: () => '',
};

const html = markdownToHTML("```ts\nlet x = 1;\n```\n", options);
assert.strictEqual(
html,
'

highlighted:ts:let x = 1;
\n',
);
````

```ts
import assert from "node:assert";
import { markdownToHTML, Options } from "@nick/comrak";

const options = Options.default();
options.render.sourcepos = true;
options.plugins.render.headingAdapter = {
enter: ({ level, content }, sourcepos) => {
const attrs = [`data-level="${level}"`, `data-text="${content}"`];
if (sourcepos) {
const { start, end } = sourcepos;
attrs.push(
`data-sourcepos="${start.line}:${start.column}-${end.line}:${end.column}"`,
);
}
return ``;
},
exit: ({ level }) => ``,
};

const html = markdownToHTML("# Hello!\n\n## Subheading\n", options);
assert.strictEqual(
html,
'

Hello!

\n' +
'

Subheading

',
);
```

---

## Install

Install via your preferred package manager:

```sh
deno add jsr:@nick/comrak
```

```sh
pnpm add jsr:@nick/comrak
```

```sh
yarn add jsr:@nick/comrak
```

```sh
bunx jsr add @nick/comrak
```

```sh
npx jsr add @nick/comrak
```

> [!IMPORTANT]
>
> Support for the `jsr:` protocol is available in PNPM v10.2+ and Yarn v4.2+. If
> you're using an older version of these package managers, you can use the `dlx`
> command instead.

```sh
pnpm dlx jsr add @nick/comrak
```

```sh
yarn dlx jsr add @nick/comrak
```

### [npm]

This package is also distributed on [npm] as [`comrak`][npm].

```sh
deno add npm:comrak
```

```sh
pnpm add comrak
```

```sh
yarn add comrak
```

```sh
bun add comrak
```

```sh
npm i comrak
```

---

## API

- `markdownToHTML(markdown, options?)` Render Markdown to HTML.
- `markdownToXML(markdown, options?)` Render Markdown to CommonMark XML.
- `markdownToCommonMark(markdown, options?)` Render Markdown back to CommonMark.
- `parseMarkdown(markdown, options?)` Parse Markdown into an AST.
- `renderHTML(ast, options?)` Render an AST to HTML.
- `renderXML(ast, options?)` Render an AST to CommonMark XML.
- `renderCommonMark(ast, options?)` Render an AST to CommonMark text.
- `Options.default()` Get a fresh, fully-populated options object.
- `HeadingAdapter` / `SyntaxHighlighterAdapter` Plug custom heading rendering or
code fence highlighting into Comrak.

---

## Options

The `Options` interface mirrors the Rust crate's configuration surface, spanning
[extensions], [parsing], [rendering], and [plugins].

[extensions]: #extensionoptions
[parsing]: #parseoptions
[rendering]: #renderoptions
[plugins]: #plugins

### `ExtensionOptions`

- **`autolink?: boolean`** Enables the autolink extension (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("Hello www.github.com.\n", {
extension: { autolink: true },
});

assert.strictEqual(
html,
'

Hello www.github.com.

\n',
);
```

- **`descriptionLists?: boolean`** Enables description lists (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("Term\n\n: Definition", {
extension: { descriptionLists: true },
});

assert.strictEqual(
html,
"

\n
Term
\n
\n

Definition

\n
\n
\n",
);
```

- **`footnotes?: boolean`** Enables footnotes support (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("Hi[^x].\n\n[^x]: A greeting.\n", {
extension: { footnotes: true },
});

assert.strictEqual(
html,
'

Hi1.

\n' +
'\n
    \n
  1. \n

    A greeting.

    \n
  2. \n
\n\n',
);
```

- **`inlineFootnotes?: boolean`** Enables inline footnotes (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML, Options } from "@nick/comrak";

const options = Options.default();
options.extension.footnotes = true;
options.extension.inlineFootnotes = true;

const html = markdownToHTML("Hi^[An inline note].\n", options);
assert.strictEqual(
html,
'

Hi1.

\n' +
'\n
    \n
  1. \n

    An inline note

    \n
  2. \n
\n\n',
);
```

- **`frontMatterDelimiter?: string | null`** Processes front matter. Defaults to
`"---"`.

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("---\nlayout: post\n---\nText\n", {
extension: { frontMatterDelimiter: "---" },
});

assert.strictEqual(html, "

Text

\n");
```

- **`headerIDs?: string | null`** Generates header IDs with an optional prefix
(default: `""`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("# README\n", {
extension: { headerIDs: "user-content-" },
});

assert.strictEqual(
html,
'


README

\n',
);
```

- **`table?: boolean`** Enables table support (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("| a | b |\n|---|---|\n| c | d |\n", {
extension: { table: true },
});

assert.strictEqual(
html,
"\n\n\na\nb\n\n\n\n\nc\nd\n\n\n\n",
);
```

- **`tagfilter?: boolean`** Filters disallowed raw HTML tags (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("Hello .\n\n", {
extension: { tagfilter: true },
render: { unsafe: true },
});

assert.strictEqual(html, "

Hello <xmp>.

\n<xmp>\n");
```

- **`tasklist?: boolean`** Enables task list items (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("* [x] Done\n* [ ] Not done\n", {
extension: { tasklist: true },
});

assert.strictEqual(
html,
'

    \n
  • Done
  • \n
  • Not done
  • \n
\n',
);
```

- **`multilineBlockQuotes?: boolean`** Enables multiline block quotes (default:
`false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML(">>>\nparagraph\n>>>", {
extension: { multilineBlockQuotes: true },
});

assert.strictEqual(html, "

\n

paragraph

\n
\n");
```

- **`alerts?: boolean`** Enables GitHub-style alerts (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("> [!note]\n> Something of note", {
extension: { alerts: true },
});

assert.strictEqual(
html,
'

\n

Note

\n

Something of note

\n
\n',
);
```

- **`mathDollars?: boolean`** Enables math using dollar syntax (default:
`false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("$1 + 2$ and $$x = y$$", {
extension: { mathDollars: true },
});

assert.strictEqual(
html,
'

1 + 2 and x = y

\n',
);
```

- **`mathCode?: boolean`** Enables math using code syntax (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("$`1 + 2`$", {
extension: { mathCode: true },
});

assert.strictEqual(
html,
'

1 + 2

\n',
);
```

- **`wikilinksTitleBeforePipe?: boolean`** Enables wikilinks where the title is
before the pipe (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("[[link label|url]]", {
extension: { wikilinksTitleBeforePipe: true },
});

assert.strictEqual(
html,
'

link label

\n',
);
```

- **`wikilinksTitleAfterPipe?: boolean`** Enables wikilinks where the title is
after the pipe (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("[[url|link label]]", {
extension: { wikilinksTitleAfterPipe: true },
});

assert.strictEqual(
html,
'

link label

\n',
);
```

- **`underline?: boolean`** Enables underlines using double underscores
(default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("__underlined text__", {
extension: { underline: true },
});

assert.strictEqual(html, "

underlined text

\n");
```

- **`strikethrough?: boolean`** Enables strikethrough formatting (default:
`false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("Hello ~world~ there.\n", {
extension: { strikethrough: true },
});

assert.strictEqual(html, "

Hello world there.

\n");
```

- **`superscript?: boolean`** Enables superscript formatting (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("e = mc^2^.\n", {
extension: { superscript: true },
});

assert.strictEqual(html, "

e = mc2.

\n");
```

- **`subscript?: boolean`** Enables subscript text using single tildes (default:
`false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("H~2~O", {
extension: { subscript: true },
});

assert.strictEqual(html, "

H2O

\n");
```

- **`spoiler?: boolean`** Enables spoilers using double vertical bars (default:
`false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("Darth Vader is ||Luke's father||", {
extension: { spoiler: true },
});

assert.strictEqual(
html,
'

Darth Vader is Luke\'s father

\n',
);
```

- **`greentext?: boolean`** Requires a space after `>` for blockquotes (default:
`false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML(">implying implications", {
extension: { greentext: true },
});

assert.strictEqual(html, "

>implying implications

\n");
```

- **`shortcodes?: boolean`** Replaces `:emoji:` shortcodes (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("Happy Friday! :smile:", {
extension: { shortcodes: true },
});

assert.strictEqual(html, "

Happy Friday! 😄

\n");
```

- **`imageURLRewriter?: URLRewriter | null`** Rewrites image URLs (default:
`null`).

```ts
import assert from "node:assert";
import { markdownToHTML, Options } from "@nick/comrak";

const options = Options.default();
options.extension.imageURLRewriter = (url: string) =>
`https://cdn.example.com/images/${encodeURIComponent(url)}`;

const html = markdownToHTML("![alt text](image.png)", options);
assert.strictEqual(
html,
'

alt text

\n',
);
```

- **`linkURLRewriter?: URLRewriter | null`** Rewrites link URLs (default:
`null`).

```ts
import assert from "node:assert";
import { markdownToHTML, Options } from "@nick/comrak";

const options = Options.default();
options.extension.linkURLRewriter = (url: string) =>
`https://safe.example.com/norefer?url=${encodeURIComponent(url)}`;

const html = markdownToHTML(
"[my link](http://unsafe.example.com/bad)",
options,
);
assert.strictEqual(
html,
'

my link

\n',
);
```

- **`cjkFriendlyEmphasis?: boolean`** Recognizes emphasis in CJK contexts
(default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("**この文は重要です。**但这句话并不重要。", {
extension: { cjkFriendlyEmphasis: true },
});

assert.strictEqual(
html,
"

この文は重要です。但这句话并不重要。

\n",
);
```

- **`subtext?: boolean`** Enables block-scoped subtext (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML, Options } from "@nick/comrak";

const options = Options.default();
options.extension.subtext = true;

const html = markdownToHTML("-# subtext", options);
assert.strictEqual(html, "

subtext

\n");
```

- **`highlight?: boolean`** Enables highlighting using `==` (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML, Options } from "@nick/comrak";

const options = Options.default();
options.extension.highlight = true;

const html = markdownToHTML("Hey, ==this is important==!", options);
assert.strictEqual(html, "

Hey, this is important!

\n");
```

### `ParseOptions`

- **`defaultInfoString?: string | null`** Default info string for fenced code
blocks (default: `null`).

````ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

assert.strictEqual(
markdownToHTML("```\nfn hello();\n```\n"),
"

fn hello();\n
\n",
);

assert.strictEqual(
markdownToHTML("```\nfn hello();\n```\n", {
parse: { defaultInfoString: "rust" },
}),
'

fn hello();\n
\n',
);
````

- **`smart?: boolean`** Enable smart punctuation conversion (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

assert.strictEqual(
markdownToHTML("'Hello,' \"world\" ..."),
"

'Hello,' "world" ...

\n",
);

assert.strictEqual(
markdownToHTML("'Hello,' \"world\" ...", { parse: { smart: true } }),
"

‘Hello,’ “world” …

\n",
);
```

- **`relaxedTasklistMatching?: boolean`** Allow symbols beyond `x`/`X` for
tasklists (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const markdown =
"* [x] Done\n* [ ] Not done\n* [-] Also done\n* [ ] Also not done\n";

const relaxed = markdownToHTML(markdown, {
extension: { tasklist: true },
parse: { relaxedTasklistMatching: true },
});
assert.strictEqual(
relaxed,
'

    \n
  • Done
  • \n
  • Not done
  • \n
  • Also done
  • \n
  • Also not done
  • \n
\n',
);

const strict = markdownToHTML(markdown, {
extension: { tasklist: true },
parse: { relaxedTasklistMatching: false },
});
assert.strictEqual(
strict,
'

    \n
  • Done
  • \n
  • Not done
  • \n
  • [-] Also done
  • \n
  • Also not done
  • \n
\n',
);
```

- **`tasklistInTable?: boolean`** Parse tasklist items inside tables (default:
`false`).

```ts
import assert from "node:assert";
import { markdownToHTML, Options } from "@nick/comrak";

const options = Options.default();
options.extension.table = true;
options.extension.tasklist = true;

const markdown = "| val |\n| - |\n| [ ] |\n";
assert.strictEqual(
markdownToHTML(markdown, options),
"\n\n\nval\n\n\n\n\n[ ]\n\n\n\n",
);

options.parse.tasklistInTable = true;
assert.strictEqual(
markdownToHTML(markdown, options),
'\n\n\nval\n\n\n\n\n\n \n\n\n\n',
);
```

- **`relaxedAutolinks?: boolean`** Detect links inside brackets and allow all
URL schemes (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("[https://foo.com]", {
extension: { autolink: true },
parse: { relaxedAutolinks: true },
});

assert.strictEqual(
html,
'

[https://foo.com]

\n',
);
```

- **`ignoreSetext?: boolean`** Ignore setext headings in input (default:
`false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("setext heading\n---", {
parse: { ignoreSetext: true },
});

assert.strictEqual(html, "

setext heading

\n
\n");
```

- **`brokenLinkCallback?: BrokenLinkCallback | null`** Handle undefined link
references (default: `null`).

```ts
import assert from "node:assert";
import { markdownToHTML, Options } from "@nick/comrak";

const options = Options.default();
options.parse.brokenLinkCallback = (ref) => ({
url: "https://img.shields.io/badge/placeholder-lightgrey.svg",
title: `Placeholder Badge (original: ${ref.original})`,
});

const html = markdownToHTML("![Build Status][undefined-badge]\n", options);
assert.strictEqual(
html,
'

Build Status

\n',
);
```

- **`leaveFootnoteDefinitions?: boolean`** Keep footnote definitions in place
within the AST (default: `false`).

```ts
import assert from "node:assert";
import { Options, parseMarkdown, renderCommonMark } from "@nick/comrak";

const options = Options.default();
options.extension.footnotes = true;
options.parse.leaveFootnoteDefinitions = true;

const ast = parseMarkdown("Hi[^x].\n\n[^x]: A greeting.\n", options);
const cm = renderCommonMark(ast, options);

assert.strictEqual(cm, "Hi[^x].\n\n[^x]:\n A greeting.\n");
```

- **`escapedCharSpans?: boolean`** Keep escaped characters as spans in the AST
(default: `false`). Enabling `render.escapedCharSpans` also enables this.

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("Notify user \\@example", {
render: { escapedCharSpans: true },
});

assert.strictEqual(
html,
"

Notify user @example

\n",
);
```

### `RenderOptions`

- **`escape?: boolean`** Escape raw HTML instead of clobbering it (default:
`false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

assert.strictEqual(
markdownToHTML("italic text"),
"

italic text

\n",
);

assert.strictEqual(
markdownToHTML("italic text", { render: { escape: true } }),
"

<i>italic text</i>

\n",
);
```

- **`githubPreLang?: boolean`** Use GitHub-style `

` for fenced

code blocks (default: `false`).

````ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

assert.strictEqual(
markdownToHTML("```rust\nfn hello();\n```\n", {
render: { githubPreLang: true },
}),
'

fn hello();\n
\n',
);
````

- **`hardbreaks?: boolean`** Convert soft line breaks to hard breaks (default:
`false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

assert.strictEqual(
markdownToHTML("Hello.\nWorld.\n"),
"

Hello.\nWorld.

\n",
);

assert.strictEqual(
markdownToHTML("Hello.\nWorld.\n", { render: { hardbreaks: true } }),
"

Hello.
\nWorld.

\n",
);
```

- **`unsafe?: boolean`** Allow rendering of raw HTML and potentially dangerous
links (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const markdown = "\nalert('xyz');\n\n\n" +
"Possibly annoying.\n\n" +
"[Dangerous](javascript:alert(document.cookie)).\n\n" +
"[Safe](http://commonmark.org).";

assert.strictEqual(
markdownToHTML(markdown),
"\n" +
"

Possibly annoying.

\n" +
'

Dangerous.

\n' +
'

Safe.

\n',
);

assert.strictEqual(
markdownToHTML(markdown, { render: { unsafe: true } }),
"\nalert('xyz');\n\n" +
"

Possibly annoying.

\n" +
'

Dangerous.

\n' +
'

Safe.

\n',
);
```

- **`width?: number`** Wrap column for CommonMark output (default: `0`).

```ts
import assert from "node:assert";
import { markdownToCommonMark } from "@nick/comrak";

const markdown =
"Hello, **world**!\n\nNew line of text that should wrap when width is set.\n";

assert.strictEqual(
markdownToCommonMark(markdown),
"Hello, **world**\\!\n\nNew line of text that should wrap when width is set.\n",
);

assert.strictEqual(
markdownToCommonMark(markdown, { render: { width: 20 } }),
"Hello, **world**\\!\n\nNew line of text\nthat should wrap\nwhen width is set.\n",
);
```

- **`fullInfoString?: boolean`** Use the full info string for fenced code blocks
(default: `false`).

````ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("```rust extra info\nfn hello();\n```\n", {
render: { fullInfoString: true },
});

assert.strictEqual(
html,
'

fn hello();\n
\n',
);
````

- **`listStyle?: "dash" | "plus" | "star"`** List marker style for CommonMark
output (default: `"dash"`).

```ts
import assert from "node:assert";
import { markdownToCommonMark } from "@nick/comrak";

assert.strictEqual(
markdownToCommonMark("* Item\n* Item\n", { render: { listStyle: "star" } }),
"* Item\n* Item\n",
);

assert.strictEqual(
markdownToCommonMark("* Item\n* Item\n", { render: { listStyle: "dash" } }),
"- Item\n- Item\n",
);
```

- **`sourcepos?: boolean`** Include source position attributes (default:
`false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("Hello *world*!", {
render: { sourcepos: true },
});

assert.strictEqual(
html,
'

Hello world!

\n',
);
```

- **`escapedCharSpans?: boolean`** Wrap escaped characters in `` tags
(default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("Notify user \\@example", {
render: { escapedCharSpans: true },
});

assert.strictEqual(
html,
"

Notify user @example

\n",
);
```

- **`ignoreEmptyLinks?: boolean`** Ignore empty links in input (default:
`false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("[]()", {
render: { ignoreEmptyLinks: true },
});

assert.strictEqual(html, "

[]()

\n");
```

- **`gfmQuirks?: boolean`** Enable GFM quirks in HTML output (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("****abcd****", {
render: { gfmQuirks: true },
});

assert.strictEqual(html, "

abcd

\n");
```

- **`preferFenced?: boolean`** Prefer fenced code blocks in CommonMark output
(default: `false`).

````ts
import assert from "node:assert";
import { markdownToCommonMark } from "@nick/comrak";

assert.strictEqual(
markdownToCommonMark(" indented code\n"),
" indented code\n",
);

assert.strictEqual(
markdownToCommonMark(" indented code\n", {
render: { preferFenced: true },
}),
"```\nindented code\n```\n",
);
````

- **`figureWithCaption?: boolean`** Render images as `` elements with
captions (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML(
'![image](https://example.com/image.png "this is an image")',
{ render: { figureWithCaption: true } },
);

assert.strictEqual(
html,
'

imagethis is an image

\n',
);
```

- **`tasklistClasses?: boolean`** Add task list CSS classes (default: `false`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const html = markdownToHTML("- [ ] Foo", {
extension: { tasklist: true },
render: { tasklistClasses: true },
});

assert.strictEqual(
html,
'

    \n
  • Foo
  • \n
\n',
);
```

- **`olWidth?: number`** Minimum marker width when rendering ordered lists
(default: `0`).

```ts
import assert from "node:assert";
import { markdownToHTML } from "@nick/comrak";

const markdown = "1. one\n10. ten\n";

assert.strictEqual(
markdownToHTML(markdown, { render: { olWidth: 0 } }),
"

    \n
  1. one
  2. \n
  3. ten
  4. \n
\n",
);

assert.strictEqual(
markdownToHTML(markdown, { render: { olWidth: 3 } }),
"

    \n
  1. one
  2. \n
  3. ten
  4. \n
\n",
);
```

- **`experimentalMinimizeCommonmark?: boolean`** Minimize escapes in CommonMark
output (default: `false`).

```ts
import assert from "node:assert";
import { markdownToCommonMark } from "@nick/comrak";

assert.strictEqual(
markdownToCommonMark("Hello, **world**!\n"),
"Hello, **world**\\!\n",
);

assert.strictEqual(
markdownToCommonMark("Hello, **world**!\n", {
render: { experimentalMinimizeCommonmark: true },
}),
"Hello, **world**!\n",
);
```

### Plugins

- **`render.codefenceSyntaxHighlighter`** Plug in custom syntax highlighting.
See the [plugin example](#usage) above for a minimal adapter that injects
custom `

`/`` tags and HTML.

- **`render.headingAdapter`** Customize heading rendering. The heading adapter
example in the [usage section](#usage) demonstrates adding data attributes and
propagating source positions.

---

## Further Reading

### Compatibility

This package is designed to work seamlessly across multiple JavaScript runtimes.
While [the original `comrak` module] required the Deno runtime, this fork boasts
wide compatibility that's been verified to run on Deno, Node.js, Bun, Cloudflare
Workers, and even web browsers. Theoretically, it should be fully compatible by
any JavaScript runtime that supports WebAssembly.

### Under the Hood

`@nick/comrak` is built using a combination of Rust, WebAssembly, and
TypeScript. Internally, this project wraps the [`comrak`] crate by
[Talya Connor] with the necessary Rust to generate a WebAssembly module.

#### Compilation

The WebAssembly is built using [`@deno/wasmbuild`], and inlined into a
JavaScript file for portability and the best compatibility. The [API] that is
exposed as a thin TypeScript wrapper around the generated JavaScript bindings,
consisting mostly of type definitions.

#### Compression

The [brotli] compression algorithm is used to compress the WebAssembly binary
during the build step, making it ~75% smaller and a **lot** quicker to load.
Decompression is performed immediately on import with [`debrotli`].

#### Performance Gains

When all is said and done, this results in a **_very_** snappy experience for
users, with minimal overhead during initial load and fast Markdown rendering
times. It also reduces the amount of data transferred over the network, making
it ideal for use in web applications and serverless environments.

### Motivation

The original `comrak` module was published on the Deno registry, which cannot be
used as a dependency in other JavaScript runtimes. JSR has forbidden the use of
HTTPS-based dependencies in all of its packages (yup, including deno.land),
thereby rendering the original `comrak` module incompatible with JSR. And thus,
`@nick/comrak` was born, on an overcast Saturday evening in late March of 2025.

---

**[MIT] · Made with ❤️ by [Nicholas Berlette]**

[github] · [issues] · [jsr] · [npm] · [docs]


---

[MIT]: https://nick.mit-license.org "MIT License. Copyright (c) Nicholas Berlette. All rights reserved."
[API]: https://jsr.io/@nick/comrak/doc "View the API documentation for @nick/comrak"
[docs]: https://jsr.io/@nick/comrak/doc "View the API documentation for @nick/comrak"
[GitHub]: https://github.com/nberlette/comrak-wasm#readme "Give this project a star on GitHub! ⭐"
[issues]: https://github.com/nberlette/comrak-wasm/issues "Report an issue or suggest a feature on GitHub"
[JSR]: https://jsr.io/@nick/comrak "View the @nick/comrak package on JSR: The JavaScript Registry"
[npm]: https://npmjs.com/package/comrak "View the comrak package on npm"
[Nicholas Berlette]: https://github.com/nberlette "Follow @nberlette on GitHub for more cool projects!"
[Talya Connor]: https://kivikakk.ee "View Talya Connor's Personal Website"
[`@nick/comrak`]: https://jsr.io/@nick/comrak "View the @nick/comrak package on the JavaScript Registry"
[`comrak`]: https://github.com/kivikakk/comrak "View the comrak GitHub repository"
[the original `comrak` module]: https://deno.land/x/comrak "View the original comrak module on Deno"
[`@deno/wasmbuild`]: https://jsr.io/@deno/wasmbuild "View the @deno/wasmbuild GitHub repository"
[`debrotli`]: https://npmjs.com/package/debrotli "View the debrotli package on npm"
[brotli]: https://github.com/google/brotli "View the Brotli GitHub repository"