vitepress-plugin-pele
v0.1.0
Published
Render Mermaid diagrams in VitePress at build time with Pele — zero client-side JavaScript.
Maintainers
Readme
vitepress-plugin-pele
Render Mermaid diagrams in VitePress at build time with Pele — no mermaid.js bundle, no client-side JavaScript, no headless browser.
Write the same fenced code blocks you already have:
```mermaid
flowchart LR
A[Markdown] --> B{VitePress}
B --> C[(Inline SVG)]
```…and get a static, themeable <svg> in the built HTML.
Why Pele?
| | mermaid.js based plugins | vitepress-plugin-pele | | --- | --- | --- | | Client JS | ~1.6 MB mermaid bundle | 0 bytes | | Rendering | in the browser | at build time | | SSR / no-JS / SEO | diagram missing | inline SVG | | Build dependency | none | none (no Chromium) | | Layout shift | on hydration | none | | Interactive zoom/pan | usually yes | not yet (planned) | | Syntax coverage | full Mermaid | Mermaid 12.1 types, stricter parsing |
Pele is a fast, dependency-free Mermaid → SVG renderer used by Obsidian. It renders synchronously, which is what makes build-time rendering in a markdown-it fence possible.
Install
pnpm add -D vitepress-plugin-pele
# npm install -D vitepress-plugin-pele
# yarn add -D vitepress-plugin-peleRequires Node.js 20+ and VitePress 1.6+.
Quick start
1. Register the markdown transform
// .vitepress/config.ts
import { defineConfig } from "vitepress"
import { peleMermaid } from "vitepress-plugin-pele"
export default defineConfig({
markdown: {
config: peleMermaid(),
},
})If you already have a markdown.config, call peleMermaid() after your own plugins so it wraps the final fence renderer:
markdown: {
config(md) {
myPlugin(md)
peleMermaid()(md)
},
},2. Load the theme styles
Create .vitepress/theme/index.ts (or add the import to your existing theme):
import DefaultTheme from "vitepress/theme"
import "vitepress-plugin-pele/style.css"
export default DefaultThemeIf you already extend the default theme, keep your theme object and just add the style.css import.
That's it. Existing ```mermaid blocks now render as inline SVG.
Fence options
Set the layout width for a single diagram:
```mermaid width=480
sequenceDiagram
Alice->>Bob: Hello
```width accepts a number, 480px, width=480, maxWidth=480, or {width=480}. The value is passed to Pele as maxWidth, which lets it wrap labels and switch direction on narrow layouts.
Options
peleMermaid({
maxWidth: 688,
responsive: true,
className: "pele-diagram",
vueSafe: true,
onError: "warn",
cache: true,
idPrefix: "pele",
renderOptions: {},
})| Option | Type | Default | Description |
| --- | --- | --- | --- |
| maxWidth | number | 688 (VitePress entry) | Layout width in px. The default matches the VitePress content column (43rem). |
| responsive | boolean | Pele default (true) | Let the SVG shrink to its container. |
| className | string | "pele-diagram" | Class of the wrapper element. Set to "" to omit the wrapper attribute. |
| vueSafe | boolean | true | Add v-pre to the wrapper so Vue does not interpret {{ ... }} in labels. |
| onError | "warn" \| "throw" \| "code" | "warn" | Invalid diagrams fall back to a code block, fail the build, or fall back silently. |
| cache | boolean | true | Cache identical diagrams across pages during a build. |
| idPrefix | string | derived from source | Prefix for accessibility ids inside the SVG. |
| renderOptions | RenderOptions | {} | Extra options forwarded to pele.render(). |
Error handling
With the default onError: "warn", a diagram Pele cannot parse is printed as a code block and the build continues with a warning that includes the file, line and the offending source:
[vitepress-plugin-pele] Failed to render Mermaid diagram in guide/architecture.md:12
at diagram line 3, column 7: ...
Falling back to a code block.Use onError: "throw" in CI to make broken diagrams fail the build. Use onError: "code" if you prefer a silent fallback.
Theming
Pele colors the SVG with CSS variables, so diagrams follow light and dark mode without re-rendering. The bundled stylesheet maps them to the VitePress palette:
| Pele variable | VitePress variable |
| --- | --- |
| --pele-bg | --vp-c-bg |
| --pele-fg | --vp-c-text-1 |
| --pele-muted | --vp-c-text-2 |
| --pele-line | --vp-c-text-2 |
| --pele-surface | --vp-c-bg-soft |
| --pele-surface-alt | --vp-c-bg-alt |
| --pele-border | --vp-c-divider |
| --pele-accent | --vp-c-brand-1 |
| --pele-font | --vp-font-family-base |
Override them in your own CSS:
:root {
--pele-accent: #ff6b35;
--pele-radius: 8px;
}See the Pele theming guide for the full list, including series colors and per-element classes.
Plain markdown-it
The renderer is also available as a regular markdown-it plugin, without the VitePress defaults:
import MarkdownIt from "markdown-it"
import markdownItPele from "vitepress-plugin-pele/markdown-it"
const md = new MarkdownIt().use(markdownItPele, { maxWidth: 720 })In this entry maxWidth is left undefined by default, so Pele uses the diagram's natural width. style.css assumes VitePress variables; outside VitePress, define --pele-* variables yourself.
How it works
peleMermaid()registers a markdown-it fence rule through VitePress'smarkdown.confighook.- Every
```mermaidfence is rendered withpele.render()during the build. - The resulting SVG is inlined into the page and wrapped in
<div class="pele-diagram" v-pre>. - Nothing diagram-related ships to the browser — only the CSS.
Because Pele is synchronous and does not need a DOM, this works in Node without Puppeteer or Playwright. Text widths are estimated with Pele's built-in width table during the build; if your site uses a very different font metric, adjust --pele-font or pass renderOptions.fontFamily.
Compatibility
| VitePress | Status |
| --- | --- |
| 2.0.0-alpha.x | tested in CI |
| 1.6.x | tested manually (1.6.4) |
Node.js 20+ is required. VitePress 2 itself requires Node 22. The package is ESM-only and ships its own TypeScript declarations.
Migrating from vitepress-plugin-mermaid
- Remove
vitepress-plugin-mermaidandmermaidfrom your dependencies. - Replace
withMermaid(defineConfig(...))with a plaindefineConfig. - Add
peleMermaid()tomarkdown.configand importvitepress-plugin-pele/style.css, as shown above. - Run your build. Any diagram Pele rejects is reported with its file and line number; fix the syntax or keep the fallback code block.
Your Markdown does not change. Diagram features that depend on mermaid.js (custom themeVariables, directives, interactive zoom) are not supported; colors are controlled with --pele-* CSS variables instead.
Limitations
- Build-time only. Diagrams are static SVG. Interactive zoom/pan is planned.
- Stricter parser. Pele supports the Mermaid 12.1 diagram types but rejects some malformed diagrams mermaid.js tolerates.
- No Mermaid config compatibility.
theme,themeVariables, and most directives are ignored; use CSS variables. - Approximate text metrics at build time. Pele measures labels with a built-in table when no canvas is available.
- No HTML labels. Label content is sanitized and rendered as SVG text.
Contributing
Contributions are welcome. Please read CONTRIBUTING.md and the Code of Conduct first.
- Bug reports: use the issue template and include a minimal reproduction.
- Security issues: see SECURITY.md, please do not open a public issue.
