v2.0.9

Markdown to HTML: NanoMD vs marked

Last updated


This page renders the same Markdown with NanoMD and marked 18.1.1 side by side, then compares parse speed.

Differences


ItemNanoMD Parse().html()marked marked.parse()
Output structureClassed elements such as <span class="md-h1"> and <span class="md-line">, styled by parser.css inside .nanomd; see HTML OutputPlain <h1>, <p> and other standard tags with no styles
==mark==, H~2~O, x^2^<mark>, <sub>, <sup>Left as text; ~2~ becomes <del>
> [!NOTE] alertsmd-alert blockquote with a titlePlain blockquote with the [!NOTE] text
Footnotes [^1]Reference link and a footnote sectionLeft as text
Bare URLLink with target="_blank"Link
Event attributes in raw HTML (onload)RemovedKept and executed
javascript: linkNot converted to a linkKept as href
JSON ASTParse() returns a tree before html(); see AST NodesToken list from marked.lexer()

XSS Protection


Markdown often comes from users, so the renderer decides whether a comment or note can run script in the page. NanoMD sanitizes its own output; marked does not sanitize its output and asks you to pass the HTML through DOMPurify or another sanitizer.
InputNanoMDmarked without a sanitizer
[click](javascript:console.log('clicked'))Not linked; the Markdown stays visible as plain text, so nothing can be clicked and no content is lost<a href="javascript:...">; clicking runs the script
<img src="..." onload="...">onload removed, the image is keptonload kept and runs when the image loads

Only http(s), mailto:, tel:, relative paths and #anchor become links. In the example below, the [click](javascript:...) line shows up as text on the NanoMD side, and only the marked side prints onload ran to the log. See Security for every escaping and sanitizing rule.

Speed


The documentation pages of this site are joined into one document and repeated to each size, then rendered to HTML in headless Chrome 155 on macOS. Each case warms up 5 runs, then repeats for at least 1.5 seconds and shows the average.
SizeNanoMD Parse().html()marked marked.parse()marked / NanoMD
6.1 KB0.097 ms0.127 ms1.30×
42.2 KB0.87 ms1.08 ms1.24×
405.2 KB9.15 ms10.2 ms1.11×
1.00 MB23.1 ms26.3 ms1.14×
2.00 MB44.6 ms53.9 ms1.21×
4.00 MB88.3 ms101.7 ms1.15×

The two outputs differ in structure, so the numbers show the same order of magnitude rather than identical work: NanoMD also builds the AST and sanitizes HTML, and its output is about 16% larger.

Examples


Same Markdown, Two Renderers


The left side is NanoMD, the right side is marked. The image's onload runs only on the marked side and prints to the log below.
  1. <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:1rem">
  2. <section><h4>NanoMD</h4><div id="nano" class="nanomd"></div></section>
  3. <section><h4>marked</h4><div id="marked"></div></section>
  4. </div>
  5. <script type="text/markdown" id="md">
  6. # Release Notes
  7. **Bold**, *italic*, ~~deleted~~, ==marked==, H~2~O, x^2^ and `code`.
  8. > [!NOTE]
  9. > GitHub alert blockquote.
  10. - [x] Task done
  11. - [ ] Task pending
  12. - Nested item
  13. | Left | Center | Right |
  14. |:---|:---:|---:|
  15. | a | b | c |
  16. Footnote reference[^1] and https://nanomd.pardn.io bare URL.
  17. ~~~js
  18. console.log("hello");
  19. ~~~
  20. <img src="/static/image/dog-7128749_640.jpg" width="120" onload="console.log('onload ran')">
  21. [click](javascript:console.log('clicked'))
  22. [^1]: Footnote definition.
  23. </script>
  24. <script type="module">
  25. import { marked } from "https://cdn.jsdelivr.net/npm/marked@18.1.1/lib/marked.esm.js";
  26. const md = document.getElementById("md").textContent;
  27. document.getElementById("nano").innerHTML = MDParser.Parse(md).html();
  28. document.getElementById("marked").innerHTML = marked.parse(md);
  29. </script>
Live preview · @pardnchiu/nanomd@2.0.9

Speed in Your Browser


Press the button to time both parsers on the sample repeated to about 10 KB, 100 KB and 1 MB; each case runs for one second and the result table is rendered by NanoMD.
  1. <button id="run">Run benchmark</button>
  2. <div id="result" class="nanomd"></div>
  3. <script type="text/markdown" id="md">
  4. ## Section
  5. Paragraph with **bold**, *italic*, `code`, a [link](https://nanomd.pardn.io) and ~~deleted~~ text.
  6. - Item one
  7. - Item two
  8. 1. Nested ordered
  9. 2. Another
  10. | Name | Value |
  11. |---|---:|
  12. | alpha | 1 |
  13. | beta | 2 |
  14. > Quoted line with *emphasis*.
  15. ~~~js
  16. const sum = (a, b) => a + b;
  17. ~~~
  18. </script>
  19. <script type="module">
  20. import { marked } from "https://cdn.jsdelivr.net/npm/marked@18.1.1/lib/marked.esm.js";
  21. const base = document.getElementById("md").textContent;
  22. const button = document.getElementById("run");
  23. const result = document.getElementById("result");
  24. const nano = (s) => MDParser.Parse(s).html();
  25. const mark = (s) => marked.parse(s);
  26. function bench(fn, input) {
  27. for (let i = 0; i < 10; i++) fn(input);
  28. let runs = 0;
  29. const start = performance.now();
  30. while (performance.now() - start < 1000) {
  31. fn(input);
  32. runs++;
  33. }
  34. return (performance.now() - start) / runs;
  35. }
  36. button.addEventListener("click", async () => {
  37. button.disabled = true;
  38. const rows = ["| Size | NanoMD | marked | marked / NanoMD |", "|---|---|---|---|"];
  39. for (const kb of [10, 100, 1000]) {
  40. result.innerHTML = MDParser.Parse(`Running ${kb} KB...`).html();
  41. await new Promise((r) => setTimeout(r, 50));
  42. const input = Array(Math.ceil((kb * 1024) / base.length)).fill(base).join("\n");
  43. const a = bench(nano, input);
  44. const b = bench(mark, input);
  45. rows.push(`| ${(input.length / 1024).toFixed(1)} KB | ${a.toFixed(3)} ms | ${b.toFixed(3)} ms | ${(b / a).toFixed(2)}× |`);
  46. }
  47. result.innerHTML = MDParser.Parse(rows.join("\n")).html();
  48. console.log(navigator.userAgent);
  49. button.disabled = false;
  50. });
  51. </script>
Live preview · @pardnchiu/nanomd@2.0.9
中文