Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Markdown mermaid is not supported #4433

Closed
1 task
Stratis-Dermanoutsos opened this issue Aug 23, 2022 · 16 comments · Fixed by #4519
Closed
1 task

Markdown mermaid is not supported #4433

Stratis-Dermanoutsos opened this issue Aug 23, 2022 · 16 comments · Fixed by #4519
Assignees
Labels
- P3: minor bug An edge case that only affects very specific usage (priority)

Comments

@Stratis-Dermanoutsos
Copy link

What version of astro are you using?

1.0.7

Are you using an SSR adapter? If so, which one?

None

What package manager are you using?

npm

What operating system are you using?

Windows

Describe the Bug

When I am added a mermaid code block, it only renders as plaintext.

To be more specific, when I visit the Markdown page that has any mermaid code block, the terminal logs the following:

The language "mermaid" doesn't exist, falling back to plaintext.

Example code block:

```mermaid
 graph TD;
   AppName-->app/view/main/pages;
   app/view/main/pages-->PageName;
   PageName-->PageNameModel.js;
\```

(The closing ` is escaped to get printed here, in the Github preview. I don't escape it in the actual code.)

Link to Minimal Reproducible Example

https://stackblitz.com/edit/github-xn8csz

Participation

  • I am willing to submit a pull request for this issue.
@bholmesdev bholmesdev added the - P2: nice to have Not breaking anything but nice to have (priority) label Aug 23, 2022
@bholmesdev
Copy link
Contributor

Thanks for reporting @Stratis-Dermanoutsos! Yes, it looks like mermaid is included in Shiki's default language list. I'll investigate why we're missing this 👍

@JuanM04
Copy link
Contributor

JuanM04 commented Aug 28, 2022

@Stratis-Dermanoutsos, #4519 should address your issue. If you are looking for a implementation of Mermaid in Astro like GitHub does, you can check out my webpage, where I've done that: mermaid.ts, Layout.astro

@jasekt
Copy link

jasekt commented Mar 29, 2023

@JuanM04 Thank you man! Your comment was extremely helpful in implementing Mermaid in my personal Astro site :)
Note for anyone trying to replicate - this implementation works for mermaid versions 9.* but breaks for versions 10.*

@xandermann
Copy link

I downgraded mermaid version and it is working too.
If anyone has a fix for mermaid 10 I'm interested !

@JuanM04
Copy link
Contributor

JuanM04 commented Jun 9, 2023

Just for the record, I've migrated to Mermaid 10 successfully! This is the remark plugin and this is the dynamic script.

@xandermann
Copy link

It is working for me ! Thank you very much @JuanM04 ! ❤️

@jzanecook
Copy link

@JuanM04 You're awesome, bro!

@LoneExile
Copy link

Thanks, @JuanM04, for the solution.

However, I want to optimize it by ensuring it only loads when required.
More specifically, I want it to run only when the '.mermaid' elements become visible in the viewport.

Here the approach:
<script>
  /**
   * @params {HTMLCollectionOf<HTMLElement>} graphs
   */
  async function renderDiagrams(graphs: HTMLCollectionOf<HTMLElement>) {
    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;
      });
    }
  }

  function onIntersection(entries: IntersectionObserverEntry[]) {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        const graphs = document.getElementsByClassName(
          "mermaid"
        ) as HTMLCollectionOf<HTMLElement>;
        if (graphs.length > 0) {
          renderDiagrams(graphs);
        }
        // Stop observing once the element is visible
        observer.unobserve(entry.target);
      }
    });
  }

  const observerOptions = {
    root: null, // Use the viewport as the root
    rootMargin: "0px",
    threshold: 0.1, // The callback will run when at least 10% of the target is visible
  };

  // Create a new observer with the callback and options
  const observer = new IntersectionObserver(onIntersection, observerOptions);

  // Start observing
  const target = document.querySelector(".mermaid");
  if (target) {
    observer.observe(target);
  }
</script>

The above code works, but it triggers all of my scripts for other elements that use Client Directives (client:visible).

My question is: Is there a way to run this script during the build process instead of on the client-side?

Currently, I'm running a script with Partytown

like this:

[slug].astro

<script type="text/partytown" src="/scripts/mermaid.js"></script>
<!-- src="https://unpkg.com/mermaid@10.2.4/dist/mermaid.min.js" -->

mermaid.ts

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

export const mermaid: RemarkPlugin<[]> = () => (tree) => {
  visit(tree, 'code', (node) => {
    if (node.lang !== 'mermaid') return

    // @ts-ignore
    node.type = 'html'
    node.value = dedent`
      <pre
        class="mermaid"
        style="
        overflow: auto;
        max-width: 100%;
        background: none;
        background-color: #BDD1CE;
        width: 100%;
      ">
        ${node.value}
      </pre>
    `
  })
}

However, this solution I don't know how to initialize mermaidAPI theme.

@Nomango
Copy link

Nomango commented Dec 29, 2023

Not working on Astro v4 ToT. Always shows [ERROR] __vite_ssr_import_1__.default is not a function

@Nomango
Copy link

Nomango commented Jan 2, 2024

Not working on Astro v4 ToT. Always shows [ERROR] __vite_ssr_import_1__.default is not a function

Resolved by removing ts-dedent. Follow at tamino-martinius/node-ts-dedent#40.

@tamaracha
Copy link

As another approach, I use rehype-mermaid and rehype-shikiji plugins and disable astro's built-in syntax highlighting. This renders the diagrams to svg via playwright, no client-side js needed. The remaining code blocks get highlighted afterwards. You could also use e.g. rehype-pretty-code for highlighting.

import { defineConfig } from 'astro/config'
import rehypeMermaid from 'rehype-mermaid'
import rehypeShikiji from 'rehype-shikiji'

// https://astro.build/config
export default defineConfig({
  markdown: {
    rehypePlugins: [rehypeMermaid, [rehypeShikiji, { theme: 'github-dark' }]],
    syntaxHighlight: false
  },
})

In my md files, I can have mermaid syntax in code fences and they get rendered to inlined svg diagram.

@nvkhuy
Copy link

nvkhuy commented Feb 28, 2024

The SVG render with size 24x24 only, cannot change it. Any one has solution

@87xie
Copy link

87xie commented Feb 28, 2024

The SVG render with size 24x24 only, cannot change it. Any one has solution

Overriding the default style using the themeCSS option in the Mermaid initialize API or the global CSS.

mermaid.initialize({
  themeCSS: 'width: 100%; height: auto;', 
  // skip
});

or

.mermaid svg {
  width: 100%;
  height: auto;
}

@tamaracha
Copy link

Since Astro v4.5, syntax highlighting uses shiki again instead of shikiji, and internal syntax highlighting has been migrated to use rehype plugins instead of remark plugins. These plugins still run before custom plugins, so sequence matters. What has changed is that rehype-shikiji is not needed anymore and we can use the internal shiki plugin.

# Upgrade astro
npx @astrojs/upgrade
# Remove rehype-shikiji plugin
npm rm rehype-shikiji

The markdown section in astro.config.js looks like this (shortened):

import { defineConfig } from 'astro/config'
import { rehypeShiki } from '@astrojs/markdown-remark'
import rehypeMermaid from 'rehype-mermaid'

// https://astro.build/config
export default defineConfig({
  markdown: {
    rehypePlugins: [
      rehypeMermaid,
      rehypeShiki,
    ],
    syntaxHighlight: false,
  },
})

Is this still a recommended way of adding Mermaid, or am I missing some implications from the newer changes?

@stephengrice
Copy link

stephengrice commented Apr 20, 2024

Just for the record, I've migrated to Mermaid 10 successfully! This is the remark plugin and this is the dynamic script.

Thanks so much for this. It worked for me after I made a few tweaks. Sharing in case it helps anyone else.

For me, using data-content was removing line breaks, so I had to change it to dump the mermaid source into a <pre>:

mermaid.ts

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

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

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

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

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

<script> in Layout.astro

      <script>
        // Source: https://github.com/JuanM04/portfolio/blob/983b0ed0eabdac37bf8b7912d3e8128a443192b9/src/pages/docs/%5B...documentSlug%5D.astro#L74-L103
        // From this comment: https://github.com/withastro/astro/issues/4433#issuecomment-1584019991
        /**
         * @params {HTMLCollectionOf<HTMLElement>} graphs
         */
        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.querySelector(".mermaid-src").innerText
            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>

astro.config.mjs

import { mermaid } from "./src/plugins/mermaid";
// ...
  markdown: {
    remarkPlugins: [
      // ...
      mermaid,
    ],
// ...

Used in this repo in case you're curious.

Thanks again for the solution!

@pengjeck
Copy link

@stephengrice Thank you for the detailed description in your answer, it saved me a lot of time.

After successfully rendering the mermaid diagram following the steps mentioned above, I noticed that when I use the browser's back button to return to the blog, the mermaid rendering does not work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
- P3: minor bug An edge case that only affects very specific usage (priority)
Projects
None yet
Development

Successfully merging a pull request may close this issue.