Skip to content

Astro Mermaid support with simple remark plugin

Posted on:July 9, 2023

What

I want to use Mermaid diagram on Markdown. By wrote this code (in code block) into .md|mdx:

classDiagram 
  direction LR

  class People {
    - address: Address
    + eat(food: Food)
  }

  People ..> Food
  People --> Address

Astro should render this diagram as SVG for me

Loading graph...

How

From Astro document, we can create Markdown plugin with Remark1. All we need to do is

  1. Create plugin file in ./src/plugins/mermaid.ts
  2. Update Astro’s config in ./astro.config.mjs
  3. Put Mermaid initial code in ./src/layouts/Layout.astro

Create Markdown plugin

Target: ./src/plugins/mermaid.ts2

import type { RemarkPlugin } from "@astrojs/markdown-remark";
import { visit } from "unist-util-visit";
import dedent from "ts-dedent";

const escapeMap: Record<string, string> = {
  "&": "&amp;",
  "<": "&lt;",
  ">": "&gt;",
  '"': "&quot;",
  "'": "&#39;",
};

const escapeHtml = (str: string) => str.replace(/[&<>"']/g, c => escapeMap[c]);

// @ts-ignore
export const mermaid: RemarkPlugin<[]> = () => tree => {
  visit(tree, "code", node => {
    if (node.lang !== "mermaid") return;

    // @ts-ignore
    node.type = "html";
    node.value = dedent`
      <div class="mermaid" data-content="${escapeHtml(node.value)}">
        <p>Loading graph...</p>
      </div>
    `;
  });
};

Update Astro’s config

Target: ./astro.config.mjs

import the new plugin and put in markdown.remarkPlugins

// ...
import { mermaid } from "./src/plugins";

export default defineConfig({
  markdown: {
    remarkPlugins: [
      mermaid,
      // ...
    ],
  }
})

Put Mermaid initialize code into main layout file

Target: ./src/layouts/Layout.astro

Put this code within <body />3

<script>
async function renderDiagrams(graphs) {
  const {default: mermaid} = await import("mermaid")
  mermaid.initialize({
    startOnLoad: false,
    fontFamily: "var(--sans-font)",
    // @ts-ignore This works, but TS expects a enum for some reason
    theme: window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "default",
  })

  for (const graph of graphs) {
    const content = graph.getAttribute("data-content")
    if (!content) continue
    let svg = document.createElement("svg")
    const id = (svg.id = "mermaid-" + Math.round(Math.random() * 100000))
    graph.appendChild(svg)
    mermaid.render(id, content).then(result => {
      graph.innerHTML = result.svg
    })
  }
}

const graphs = document.getElementsByClassName("mermaid")
if (document.getElementsByClassName("mermaid").length > 0) {
  renderDiagrams(graphs);
}
</script>

References

Footnotes

  1. Astro docs / Markdown content / Markdown plugins

  2. Github.com/withastro/issue/4433 - Markdown mermaid is not supported

  3. Mermaid API usage