@docvia/plugin-mermaid
Mermaid diagrams for docvia. The compiler rewrites diagram fences into component nodes; your app draws them.
@docvia/plugin-mermaid renders Mermaid diagrams
written as fenced code blocks. During compilation its beforeRender hook
rewrites every ```mermaid block into a component node, and your app
supplies the component that draws it through the renderer's
ComponentRegistry.
Mermaid itself is never a compiler dependency. The compiler only moves a string from one node type to another, so nothing is added to the build, the server bundle, or the edge bundle.
Installation
pnpm add -D @docvia/plugin-mermaid
pnpm add mermaid
Requires Node.js >=20.0.0. ESM only.
Usage
Register it in the plugins array of your docvia.config.ts. Put it before
any highlighter so diagram fences are claimed first:
import { defineConfig } from "@docvia/cli";
import { mermaid } from "@docvia/plugin-mermaid";
import { shiki } from "@docvia/plugin-shiki";
import { createSvelteRenderer } from "@docvia/renderer-svelte/node";
export default defineConfig({
sourceDir: "src/docs",
outDir: ".docvia",
renderer: createSvelteRenderer(),
plugins: [mermaid(), shiki({ theme: "github-dark" })],
});
The plugin declares phase: "pre", so the ordering holds even if you list it
after the highlighter.
How it works
flowchart LR
F["```mermaid<br/>graph TD; A-->B;<br/>```"] --> CB["code-block node<br/>lang: mermaid"]
CB --> P["@docvia/plugin-mermaid<br/><i>phase: pre</i>"]
P --> CN["component node<br/>name: Mermaid<br/>attributes: { code, title }"]
CN --> R["Renderer + ComponentRegistry"]
R --> SVG["Your component draws the SVG"] - The
beforeRenderhook receives the document IR. - It walks the tree for
code-blocknodes whose language matcheslang(default"mermaid"). - Each match becomes a
componentnode named after thecomponentoption (default"Mermaid"), carrying the diagram source ascodeand any leading%% title:comment astitle. The original node'sidis reused, so ids stay stable across edits that do not touch the diagram. - Everything else passes through untouched, so a highlighter running later sees only real code blocks.
Options
| Option | Type | Default | Description |
|---|---|---|---|
lang | string | "mermaid" | Fence info string that marks a diagram. |
component | string | "Mermaid" | Component name emitted into the IR. |
props | Record<string, unknown> | {} | Extra props merged into every diagram component. |
cacheKey() is derived from all three, so changing any of them invalidates the
incremental cache.
Drawing the diagrams
Register a component under the emitted name and pass the registry to the renderer:
<script lang="ts">
import { Renderer } from "@docvia/renderer-svelte";
import Mermaid from "$lib/components/mermaid.svelte";
import type { PageProps } from "./$types";
let { data }: PageProps = $props();
const registry = {
resolve: (name: string) =>
name === "Mermaid" ? { component: Mermaid } : null,
};
</script>
<Renderer nodes={data.page.content} {registry} />
Inside that component, load mermaid with a dynamic import so it stays out
of the server bundle and off the initial page payload:
<script lang="ts">
import { browser } from "$app/environment";
let { code, title }: { code: string; title?: string } = $props();
let svg = $state("");
$effect(() => {
if (!browser) return;
let current = true;
(async () => {
const { default: mermaid } = await import("mermaid");
mermaid.initialize({ startOnLoad: false, securityLevel: "strict" });
const { svg: out } = await mermaid.render("d", code);
if (current) svg = out;
})();
return () => { current = false; };
});
</script>
{#if svg}{@html svg}{:else}<pre>{code}</pre>{/if}
Rendering the raw source when svg is empty gives you a readable fallback for
SSR, prerendered HTML, browsers with JavaScript disabled, and diagrams Mermaid
cannot parse.
The site you are reading uses exactly this setup; see
apps/web/src/lib/components/docs/mermaid.svelte
for the full component, including theme-aware colours and error handling.
Authoring
Write ordinary Mermaid inside a fenced block:
```mermaid
graph LR
Markdown --> Compiler --> IR --> Renderer
```
A leading %% title: line becomes the title prop and is stripped from the
diagram source. %% is Mermaid's own comment syntax, so the block still renders
correctly anywhere else it is pasted:
```mermaid
%% title: The compile pipeline
graph LR
Markdown --> Compiler --> IR --> Renderer
```
The fence meta string (```mermaid My caption) cannot be used for this:
@docvia/ir drops it when converting the HAST tree, so it
never reaches a plugin.
See also
- Writing plugins covers the plugin hook system and the component-node pattern.
@docvia/plugin-shikiis the highlighter that handles every remaining code block.- Architecture explains the IR that both plugins operate on.