vitepress-dgmo
v0.7.6
Published
VitePress integration to render DGMO diagrams from fenced code blocks at build time. A markdown-it fence override backed by an async pre-render cache — `withDgmo()` wires both into your .vitepress/config.ts in one call.
Maintainers
Readme
vitepress-dgmo
Render Diagrammo (DGMO) diagrams from fenced dgmo code blocks in your VitePress docs — at build time, as inline SVG. No client-side rendering, no runtime diagram library shipped to readers.
🔭 Live showcase: every chart type rendered through vitepress-dgmo — every block is in showcase mode, so hovering a diagram reveals its copy / open-in-editor footer.
```dgmo
flowchart
[Start] -> [End]
```Why this package is different
The other Diagrammo docs integrations (astro-dgmo, docusaurus-plugin-dgmo, fumadocs-dgmo) are thin wrappers around the shared remark-dgmo plugin. VitePress does not use remark — it renders markdown with markdown-it — so that plugin can't be reused. vitepress-dgmo is a markdown-it integration built from scratch, sharing only dgmo's render() and the showcase CSS.
Install
pnpm add -D vitepress-dgmo @diagrammo/dgmo
# or: npm i -D vitepress-dgmo @diagrammo/dgmo@diagrammo/dgmo and vitepress are peer dependencies.
Setup
1. Wrap your config
// .vitepress/config.ts
import { defineConfig } from 'vitepress';
import { withDgmo } from 'vitepress-dgmo';
export default defineConfig(
withDgmo(
{
title: 'My Docs',
// ...the rest of your VitePress config
},
{ palette: 'slate' } // dgmo options (optional)
)
);withDgmo registers both halves of the integration (see How it works) while preserving any markdown.config and vite.plugins you already have.
2. (Optional) client enhancement
Diagrams are fully rendered at build time, so this step is optional. It adds viewBox tightening and showcase copy / open-in-editor buttons.
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme';
import { setupDgmo } from 'vitepress-dgmo/client';
export default {
extends: DefaultTheme,
enhanceApp({ router }) {
setupDgmo(router); // re-tightens SVGs + wires buttons on every route change
},
};The stylesheet is not part of this step. Since 0.7.0 withDgmo adds vitepress-dgmo/client.css to every page itself, so light/dark visibility works whether or not you do the above. It keys on VitePress's <html class="dark"> convention, and on data-theme="dark" for good measure.
Through 0.6.x that import was a line you had to add here by hand — and a site that skipped it rendered every diagram twice, light and dark stacked, with a green build and nothing said anywhere (issue 507). Opt out with withDgmo(config, { injectClientCss: false }) if you ship your own copy of the color-mode rules; importing it yourself as well is harmless, since Vite resolves both to one module.
3. (Optional) re-draw live links that have moved
A fence can name a published Diagrammo Cloud diagram instead of carrying its own source (live-link dgm_01HQ3RSTUV). If that diagram changes after your last build, the page notices by default: readers get a small link to the current version. Re-drawing it in the browser instead means shipping the renderer, so it takes two steps that are really one decision.
// .vitepress/config.ts
withDgmo({ title: 'My Docs' }, { liveLink: { refresh: 'render' } });// .vitepress/theme/index.ts — beside setupDgmo(router)
import { setupDgmoRender } from 'vitepress-dgmo/client-render';
enhanceApp({ router }) {
setupDgmo(router)
setupDgmoRender()
}Set the option without the theme call and your build says so, once, naming both. Vite emits the renderer as its own chunk, fetched only when a diagram has actually changed.
⚠️ If your site sets a Content-Security-Policy it must allow connect-src https://api.diagrammo.app, or the baked diagram renders and simply never updates.
Usage in markdown
Write a fenced block with the dgmo language:
```dgmo
sequence
Alice -> Bob
```Per-block options (fence meta)
Everything after dgmo on the fence line configures that block:
```dgmo showcase palette=catppuccin theme=light title="Login flow"
flowchart
[User] -> [Login]
```| Meta token | Effect |
| --------------------------------------------------------------------------- | --------------------------------------------------------- |
| showcase / diagram | Output mode for this block (source + chrome vs SVG only). |
| palette=<name> | Palette for this block. |
| theme=light\|dark\|transparent | Theme (only when colorMode isn't auto). |
| colorMode=auto\|light\|dark | Color-mode strategy for this block. |
| title="…" | Caption. |
| noSource / source, noCopy / copy, noOpenInEditor / openInEditor | Toggle showcase chrome. |
Options
Passed as the second argument to withDgmo. A subset of remark-dgmo's DgmoOptions.
| Option | Type | Default | Description |
| ---------------------------------------------- | ------------------------------------ | ------------------------------ | ------------------------------------------------------------------------ |
| palette | string | 'slate' | Default palette name. |
| theme | 'light' \| 'dark' \| 'transparent' | 'dark' | Default theme (only consulted when colorMode is not auto). |
| colorMode | 'auto' \| 'light' \| 'dark' | 'auto' | auto dual-renders light + dark SVGs and toggles them via client.css. |
| mode | 'diagram' \| 'showcase' | 'diagram' | showcase adds syntax-highlighted source + copy + open-in-editor. |
| editorBaseUrl | string | https://online.diagrammo.app | Base URL for the "Open in editor" link. |
| showSource / showCopy / showOpenInEditor | boolean | mode-dependent | Fine-grained showcase chrome toggles. |
| className | string | 'dgmo' | Outer wrapper class. |
How it works
markdown-it's renderer.rules.fence is strictly synchronous, but dgmo's render() is async. vitepress-dgmo bridges that gap with a two-phase design, both halves sharing one cache:
Async warm-cache pre-pass — a Vite plugin (
enforce: 'pre') whosetransformhook runs on every.mdmodule before VitePress compiles it. It scans the raw markdown fordgmofences,awaitsrender()for each (dual light + dark undercolorMode: auto), sanitizes the SVG, and stores the resulting HTML in a cache keyed by a stable hash of(source, meta, options). Render failures are cached as an inline error card, never thrown.Sync markdown-it fence override — registered via VitePress's
markdown.config(md). For adgmofence it recomputes the same hash from the token and returns the pre-rendered HTML from the cache. Any other language delegates to the original fence renderer untouched.
Because both phases run for the same module in sequence (pre transform, then VitePress's markdown transform), the cache is always warm by the time the fence rule reads it. A cache miss (which shouldn't happen when withDgmo wires both halves) degrades to a visible placeholder plus a console warning rather than a crash.
The v-pre guard
VitePress compiles the HTML that markdown-it emits as a Vue template. Raw SVG routinely contains {{, <, and > in text labels and path data, which Vue would mis-parse as interpolation or markup. So every rendered block is wrapped in <div class="dgmo-vp" v-pre>…</div> — the v-pre directive tells Vue to emit the element and all its children verbatim, skipping template compilation.
Advanced: wire the two phases yourself
If you'd rather not let withDgmo merge your config, build a matched pair from one shared cache and place them yourself:
import { createDgmoParts } from 'vitepress-dgmo';
const { dgmoMarkdown, dgmoVitePlugin } = createDgmoParts({ palette: 'slate' });
export default defineConfig({
markdown: { config: (md) => dgmoMarkdown(md) },
vite: { plugins: [dgmoVitePlugin] },
});Both must come from the same createDgmoParts() call — that shared cache is the whole mechanism.
License
MIT © Demian Neidetcher
