npm package: @speajus/mermaid-to-svg
Description: Render Mermaid diagrams to SVG without a browser
Author: Justin Spears
Latest version: 0.1.6

# @speajus/mermaid-to-svg

[![npm version](https://img.shields.io/npm/v/@speajus/mermaid-to-svg)](https://www.npmjs.com/package/@speajus/mermaid-to-svg)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

Render Mermaid diagrams to SVG strings **without a browser**. No Puppeteer, no Playwright, no headless Chrome — just Node.js. Also works in the browser.

<p align="center">
  <img src="examples/output/flowchart-default.png" width="320" alt="Flowchart" />
  <img src="examples/output/sequence-dark.png" width="320" alt="Sequence diagram" />
</p>
<p align="center">
  <img src="examples/output/er-forest.png" width="320" alt="ER diagram" />
  <img src="examples/output/state-neutral.png" width="320" alt="State diagram" />
</p>

## Features

- **Zero browser dependencies** — pure Node.js rendering via React `renderToStaticMarkup()`
- **Browser support** — works in bundled web apps too
- **8 diagram types** — flowchart, sequence, class, state, ER, gantt, pie, mindmap
- **4 built-in themes** — default, dark, forest, neutral — plus custom theme support
- **Pipeline API** — use the full pipeline or individual steps (parse, layout, render)
- **SVG to PNG** — pair with `@resvg/resvg-js` for raster output, no browser needed

## Install

```bash
pnpm add @speajus/mermaid-to-svg
```

or

```bash
npm install @speajus/mermaid-to-svg
```

## Quick Start

```typescript
import { renderMermaid } from '@speajus/mermaid-to-svg';

const { svg, bounds } = await renderMermaid(`
  flowchart LR
    A[Start] --> B{Decision}
    B -->|Yes| C[OK]
    B -->|No| D[Cancel]
`);

console.log(svg); // <svg xmlns="...">...</svg>
```

## Themes

Four built-in themes matching Mermaid's defaults: `default`, `dark`, `forest`, `neutral`.

Try them interactively in the [Theme Builder](https://speajus.github.io/mermaid-to-svg).

[![Theme Builder Screenshot](./image.png)](https://speajus.github.io/mermaid-to-svg)

```typescript
// Use by name
const { svg } = await renderMermaid(diagram, { theme: 'dark' });

// Or import the theme object
import { darkTheme } from '@speajus/mermaid-to-svg';
const { svg } = await renderMermaid(diagram, { theme: darkTheme });

// Or create a custom theme
import { createTheme } from '@speajus/mermaid-to-svg';
const myTheme = createTheme({
  background: '#1a1a2e',
  primaryColor: '#e94560',
  primaryTextColor: '#ffffff',
});
const { svg } = await renderMermaid(diagram, { theme: myTheme });
```

### Theme Gallery

**Flowchart** across all four themes:

| Default                                                         | Dark                                                         | Forest                                                         | Neutral                                                         |
| --------------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------- | --------------------------------------------------------------- |
| <img src="examples/output/flowchart-default.png" width="180" /> | <img src="examples/output/flowchart-dark.png" width="180" /> | <img src="examples/output/flowchart-forest.png" width="180" /> | <img src="examples/output/flowchart-neutral.png" width="180" /> |

**Sequence** across all four themes:

| Default                                                        | Dark                                                        | Forest                                                        | Neutral                                                        |
| -------------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------- | -------------------------------------------------------------- |
| <img src="examples/output/sequence-default.png" width="180" /> | <img src="examples/output/sequence-dark.png" width="180" /> | <img src="examples/output/sequence-forest.png" width="180" /> | <img src="examples/output/sequence-neutral.png" width="180" /> |

**Pie chart** across all four themes:

| Default                                                   | Dark                                                   | Forest                                                   | Neutral                                                   |
| --------------------------------------------------------- | ------------------------------------------------------ | -------------------------------------------------------- | --------------------------------------------------------- |
| <img src="examples/output/pie-default.png" width="180" /> | <img src="examples/output/pie-dark.png" width="180" /> | <img src="examples/output/pie-forest.png" width="180" /> | <img src="examples/output/pie-neutral.png" width="180" /> |

## Diagram Types

### Flowchart

```
flowchart TD
  A[Christmas] -->|Get money| B(Go shopping)
  B --> C{Let me think}
  C -->|One| D[Laptop]
  C -->|Two| E[iPhone]
  C -->|Three| F[fa:fa-car Car]
```

<img src="examples/output/flowchart-default.png" width="400" alt="Flowchart diagram" />

### Sequence

```
sequenceDiagram
  participant Alice
  participant Bob
  Alice->>Bob: Hello Bob, how are you?
  Bob-->>Alice: Great!
  Alice->>Bob: See you later!
```

<img src="examples/output/sequence-default.png" width="400" alt="Sequence diagram" />

### Class

```
classDiagram
  class Animal {
    +int age
    +String gender
    +isMammal() bool
  }
  class Duck {
    +String beakColor
    +swim()
  }
  Animal <|-- Duck
```

<img src="examples/output/class-default.png" width="400" alt="Class diagram" />

### State

```
stateDiagram-v2
  [*] --> Idle
  Idle --> Processing : submit
  Processing --> Done : complete
  Processing --> Error : fail
  Error --> Idle : retry
  Done --> [*]
```

<img src="examples/output/state-default.png" width="250" alt="State diagram" />

### ER (Entity Relationship)

```
erDiagram
  CUSTOMER {
    string name
    int id PK
  }
  ORDER {
    int orderId PK
    date created
  }
  CUSTOMER ||--o{ ORDER : places
```

<img src="examples/output/er-default.png" width="400" alt="ER diagram" />

### Gantt

```
gantt
  title Project Plan
  dateFormat YYYY-MM-DD
  section Design
    Wireframes :a1, 2024-01-01, 10d
    Mockups    :after a1, 5d
  section Development
    Backend    :2024-01-10, 20d
```

<img src="examples/output/gantt-default.png" width="500" alt="Gantt chart" />

### Pie

```
pie title Pets adopted
  "Dogs" : 386
  "Cats" : 85
  "Rats" : 15
```

<img src="examples/output/pie-default.png" width="400" alt="Pie chart" />

### Mindmap

```
mindmap
  root((Central))
    Origins
      Long history
      Popularisation
    Research
      On effectiveness
      On adoption
```

<img src="examples/output/mindmap-default.png" width="400" alt="Mindmap" />

## API

### `renderMermaid(input, options?)`

Full pipeline: parse, layout, render. Returns `{ svg, bounds, diagramType }`.

```typescript
const result = await renderMermaid(mermaidText, {
  theme: 'dark', // 'default' | 'dark' | 'forest' | 'neutral' | Theme object
  padding: 20, // pixels around the diagram
  idPrefix: 'mermaid', // unique prefix for SVG element IDs
  fontMetrics: provider, // custom FontMetricsProvider for text measurement
  layoutOptions: {}, // ELK layout options override
});
```

### Individual Pipeline Steps

For advanced usage, each step is available separately:

```typescript
import { parse, layout, renderSvg } from '@speajus/mermaid-to-svg';

const ir = await parse(mermaidText); // Mermaid text → IR
const positioned = await layout(ir); // IR → positioned graph
const svg = renderSvg(positioned); // positioned graph → SVG string
```

### Theme Utilities

```typescript
import { createTheme, mergeThemes, defaultTheme } from '@speajus/mermaid-to-svg';

// Create from defaults with overrides
const theme = createTheme({ background: '#111' });

// Merge two themes
const merged = mergeThemes(baseTheme, overrides);
```

## SVG to PNG

Use `@resvg/resvg-js` to convert SVG output to PNG (no browser needed):

```typescript
import { renderMermaid } from '@speajus/mermaid-to-svg';
import { Resvg } from '@resvg/resvg-js';
import { writeFileSync } from 'node:fs';

const { svg, bounds } = await renderMermaid(diagram);
const resvg = new Resvg(svg, {
  fitTo: { mode: 'width', value: bounds.width * 2 },
});
writeFileSync('diagram.png', resvg.render().asPng());
```

## How It Works

```
Mermaid Text → Parser (mermaid) → IR → Layout (ELK.js) → SVG (React renderToStaticMarkup)
```

1. **Parse** — Uses mermaid's internal parser to extract nodes, edges, subgraphs, and direction
2. **Layout** — ELK.js computes positions and edge routing (layered algorithm, orthogonal edges)
3. **Render** — React components produce clean SVG via `renderToStaticMarkup()` — no browser DOM needed

## Development

```bash
pnpm test           # Run tests (node:test)
pnpm build          # Build with tsup (ESM + CJS + types)
pnpm typecheck      # Type check with tsc

# Generate example PNGs
node --import tsx examples/generate-theme-pngs.ts
```

## License

MIT