View Source MDEx (MDEx v0.1.18)

MDEx logo

A fast 100% CommonMark-compatible GitHub Flavored Markdown parser and formatter for Elixir.

Hex Version Hex Docs MIT

Features

Check out some samples at https://mdex-c31.pages.dev

Installation

Add :mdex dependecy:

def deps do
  [
    {:mdex, "~> 0.1"}
  ]
end

Usage

Mix.install([{:mdex, "~> 0.1"}])
MDEx.to_html("# Hello")
#=> "<h1>Hello</h1>\n"

And you can change how the markdown is parsed and formatted by passing options to MDEx.to_html/2 to enable more features:

GitHub Flavored Markdown with emojis

MDEx.to_html(
  ~S"""
  # GitHub Flavored Markdown :rocket:

  - [x] Task A
  - [x] Task B
  - [ ] Task C

  | Feature | Status |
  | ------- | ------ |
  | Fast | :white_check_mark: |
  | GFM  | :white_check_mark: |

  Check out the spec at https://github.github.com/gfm/
  """,
  extension: [
    strikethrough: true,
    tagfilter: true,
    table: true,
    autolink: true,
    tasklist: true,
    footnotes: true,
    shortcodes: true,
  ],
  parse: [
    smart: true,
    relaxed_tasklist_matching: true,
    relaxed_autolinks: true
  ],
  render: [
     github_pre_lang: true,
     escape: true
  ]
) |> IO.puts()
#=> <p>GitHub Flavored Markdown 🚀</p>
#=> <ul>
#=>   <li><input type="checkbox" checked="" disabled="" /> Task A</li>
#=>   <li><input type="checkbox" checked="" disabled="" /> Task B</li>
#=>   <li><input type="checkbox" disabled="" /> Task C</li>
#=> </ul>
#=> <table>
#=>   <thead>
#=>     <tr>
#=>       <th>Feature</th>
#=>       <th>Status</th>
#=>     </tr>
#=>   </thead>
#=>   <tbody>
#=>     <tr>
#=>       <td>Fast</td>
#=>       <td>✅</td>
#=>     </tr>
#=>     <tr>
#=>       <td>GFM</td>
#=>       <td>✅</td>
#=>     </tr>
#=>   </tbody>
#=> </table>
#=> <p>Check out the spec at <a href="https://github.github.com/gfm/">https://github.github.com/gfm/</a></p>

Code Syntax Highlighting

MDEx.to_html(~S"""
```elixir
String.upcase("elixir")
```
""",
features: [syntax_highlight_theme: "catppuccin_latte"]
) |> IO.puts()
#=> <pre class=\"autumn highlight\" style=\"background-color: #282C34; color: #ABB2BF;\">
#=>   <code class=\"language-elixir\" translate=\"no\">
#=>     <span class=\"namespace\" style=\"color: #61AFEF;\">String</span><span class=\"operator\" style=\"color: #C678DD;\">.</span><span class=\"function\" style=\"color: #61AFEF;\">upcase</span><span class=\"\" style=\"color: #ABB2BF;\">(</span><span class=\"string\" style=\"color: #98C379;\">&quot;elixir&quot;</span><span class=\"\" style=\"color: #ABB2BF;\">)</span>
#=>   </code>
#=> </pre>

Demo and Samples

A livebook and a script are available to play with and experiment with this library, or you can check out all available samples at https://mdex-c31.pages.dev

Used By

Using it and want your project listed here? Please send a PR!

Benchmark

A simple script is available to compare existing libs:

Name              ips        average  deviation         median         99th %
cmark         22.82 K      0.0438 ms    ±16.24%      0.0429 ms      0.0598 ms
mdex           3.57 K        0.28 ms     ±9.79%        0.28 ms        0.33 ms
md             0.34 K        2.95 ms    ±10.56%        2.90 ms        3.62 ms
earmark        0.25 K        4.04 ms     ±4.50%        4.00 ms        4.44 ms

Comparison:
cmark         22.82 K
mdex           3.57 K - 6.39x slower +0.24 ms
md             0.34 K - 67.25x slower +2.90 ms
earmark        0.25 K - 92.19x slower +4.00 ms

Motivation

  • earmark is extensible but can't parse all kinds of documents and is slow to convert hundreds of markdowns.
  • md is very extensible but the doc says "If one needs to perfectly parse the common markdown, Md is probably not the correct choice" which is probably the cause for failing to parse many documents.
  • markdown is not precompiled and has not received updates in a while.
  • cmark is a fast CommonMark parser but it requires compiling the C library, is hard to extend, and was archieved on Apr 2024

Note that MDEx is the only one that syntax highlights out-of-the-box which contributes to make it slower than cmark.

To finish, a friendly reminder that all libs have their own strengths and trade-offs.

Looking for help with your Elixir project?

DockYard logo

At DockYard we are ready to help you build your next Elixir project. We have a unique expertise in Elixir and Phoenix development that is unmatched and we love to write about Elixir.

Have a project in mind? Get in touch!

Acknowledgements

Summary

Functions

Convert markdown to HTML.

Convert markdown to HTML with custom opts.

Functions

@spec to_html(String.t()) :: String.t()

Convert markdown to HTML.

Examples

iex> MDEx.to_html("# MDEx")
"<h1>MDEx</h1>\n"

iex> MDEx.to_html("Implemented with:\n1. Elixir\n2. Rust")
"<p>Implemented with:</p>\n<ol>\n<li>Elixir</li>\n<li>Rust</li>\n</ol>\n"
@spec to_html(
  String.t(),
  keyword()
) :: String.t()

Convert markdown to HTML with custom opts.

Options

Accepts all available Comrak Options as keyword lists and an extra :features option:

Features Options

  • :sanitize (default false) - sanitize output using ammonia. Recommended if passing render: [unsafe_: true]
  • :syntax_highlight_theme (default "onedark") - syntax highlight code fences using autumn themes, you should pass the filename without special chars and without extension, for example you should pass syntax_highlight_theme: "adwaita_dark" to use the Adwaita Dark theme
  • :syntax_highlight_inline_style (default true) - embed styles in the output for each generated token. You'll need to serve CSS themes if inline styles are disabled to properly highlight code

Examples

iex> MDEx.to_html("# MDEx")
"<h1>MDEx</h1>\n"

iex> MDEx.to_html("Implemented with:\n1. Elixir\n2. Rust")
"<p>Implemented with:</p>\n<ol>\n<li>Elixir</li>\n<li>Rust</li>\n</ol>\n"

iex> MDEx.to_html("Hello ~world~ there", extension: [strikethrough: true])
"<p>Hello <del>world</del> there</p>\n"

iex> MDEx.to_html("<marquee>visit https://https://beaconcms.org</marquee>", extension: [autolink: true], render: [unsafe_: true])
"<p><marquee>visit <a href=\"https://https://beaconcms.org\">https://https://beaconcms.org</a></marquee></p>\n"

iex> MDEx.to_html("# Title with <script>console.log('dangerous script')</script>", render: [unsafe_: true], features: [sanitize: true])
"<h1>Title with </h1>\n"