Est.
Graphs with Mermaid & Dot
Diagrams generated by graph software are a typical extension to markdown sites. On Github, for example, you can enter Mermaid graph specifications directly as fenced code like this:
```mermaid
sequenceDiagram
Client->>Server: GET /index.html
Server-->>Client: <!DOCTYPE html><html>...
```
Which renders something like this:
To achieve this, I could look for an additional markdown-it plugin and be done. A straight forward implementation would send the mermaid graph specification to the reader's browser and use mermaid.js on the client side to render images like the one in
The SSG Way
I use a similar approach when rendering the topic map with Cytoscape.js. But the use case Topic Map is very dynamic and would not work without client side layout. The situation is different for the mermaid & graphviz graphs. Those are usually static.
I use a similar approach when rendering the topic map with Cytoscape.js. But the topic map use case is highly dynamic and would not work without client side layout. The situation is different for the mermaid & graphviz graphs. Those are usually static. For my statically rendered site I want to render the images during the build phase.
Static Site Rendering also has the advantage that I am not limited to JavaScript graph drawing. Especially, I can use my favorite graph drawing tooling Graphviz / Dot.
The steps for rendering images from Graphviz or Mermaid graph specification are shown in . I didn't bother to write a markdown-it-plugin, but implemented the flow as a Vite plugin. To be precise, these are two plugins. One for Graphviz and one for Mermaid. Both use the same abstract implementation and only differ in things like filename extensions and command invocation. This is the Mermaid plugin:
import { svg } from "./svg";
import type { PluginOption } from "vite";
export default function (): PluginOption {
return {
enforce: "pre",
name: "mermaid",
transform: (content: string, id: string) => {
if (id.endsWith(".md")) {
// requires @mermaid-js/mermaid-cli in the devDependencies.
const command =
"./node_modules/@mermaid-js/mermaid-cli/src/cli.js -i ";
return svg(content, id, "mermaid", "mmd", command, "-o");
}
},
};
}
import { svg } from "./svg";
import type { PluginOption } from "vite";
export default function (): PluginOption {
return {
enforce: "pre",
name: "mermaid",
transform: (content: string, id: string) => {
if (id.endsWith(".md")) {
// requires @mermaid-js/mermaid-cli in the devDependencies.
const command =
"./node_modules/@mermaid-js/mermaid-cli/src/cli.js -i ";
return svg(content, id, "mermaid", "mmd", command, "-o");
}
},
};
}
The SVG files that Mermaid generates are not suitable for use with VitePress. VitePress turns the whole Markdown page into a single Vue component. And Vue complains about <style>
blocks if they are nested somewhere in the file. If you move the block out of the SVG and append it to the SVG, VitePress can hoist it out of the way. Alternatively, with custom CSS definitions for the graphs, the automatically generated style block can be deleted. The latter is what I do.
Custom CSS Styling
Styling Mermaid graphs is tedious. Try to change the color of an edge and have the arrowhead in the same color. At some point, it becomes clear that it is better to change colors with CSS than in the Mermaid input.
There is another important reason to do color styling in CSS and not to use Mermaid's built-in mechanisms for colors: This way, styling can dynamically respond to the light mode / dark mode settings.
It would be possible to color graphs for light and dark background at the same time: Keep the graph's background transparent and use colors
that are close #808080.
But it is so much simpler with CSS. You can reuse CSS variables and select the dark colors by checking for the the root element's .dark
class. The edges in Fig. 1 where styled for dark mode as follows:
.dark svg[id*="mermaid"] line {
stroke: var(--vp-c-brand-light);
}
.dark svg[id*="mermaid"] #arrowhead path {
stroke: var(--vp-c-brand-light);
fill: var(--vp-c-brand-light);
}
.dark svg[id*="mermaid"] line {
stroke: var(--vp-c-brand-light);
}
.dark svg[id*="mermaid"] #arrowhead path {
stroke: var(--vp-c-brand-light);
fill: var(--vp-c-brand-light);
}