comark-pele

npm version npm downloads CI license

Render Mermaid diagrams in Comark with Pele, a small, fast renderer that outputs themeable SVG.

It works like Comark’s built-in mermaid plugin: the plugin turns ```mermaid code blocks into pele nodes, and a Pele component draws them. There is a component for every Comark renderer:

RendererImportWhat it renders
HTMLcomark-pele/htmlInline SVG
Vuecomark-pele/vueSVG on the server, responsive diagram with zoom in the browser
Reactcomark-pele/reactSame as Vue
Sveltecomark-pele/svelteSame as Vue
Angularcomark-pele/angularSame as Vue
ANSIcomark-pele/ansiThe diagram source, since terminals can’t show SVG
Markdowncomark-peleThe original fenced code block

Install

pnpm add comark-pele

Usage

Pass the plugin and the Pele component to your renderer.

Vue

<script setup lang="ts">
import { Markdown } from '@comark/vue'
import pele, { Pele } from 'comark-pele/vue'
</script>

<template>
  <Suspense>
    <Markdown :components="{ Pele }" :plugins="[pele()]">{{ content }}</Markdown>
  </Suspense>
</template>

React

import { Markdown } from '@comark/react'
import pele, { Pele } from 'comark-pele/react'

<Markdown components={{ Pele }} plugins={[pele()]}>
  {content}
</Markdown>

Svelte

<script lang="ts">
  import { Markdown } from '@comark/svelte'
  import pele, { Pele } from 'comark-pele/svelte'
</script>

<Markdown value={content} components={{ Pele }} plugins={[pele()]} />

Angular

import { Component } from '@angular/core'
import { Markdown } from '@comark/angular'
import pele, { Pele } from 'comark-pele/angular'

@Component({
  selector: 'app-root',
  imports: [Markdown],
  template: \`<comark-markdown [value]="content" [plugins]="plugins" [components]="components" />\`,
})
export class AppComponent {
  content = '\`\`\`mermaid\nflowchart LR\n  A --> B\n\`\`\`'
  plugins = [pele()]
  components = { Pele }
}

HTML

import { createHtmlRenderer } from '@comark/html'
import pele, { Pele } from 'comark-pele/html'

const renderHtml = createHtmlRenderer({
  plugins: [pele()],
  components: { Pele },
})

const html = await renderHtml(content)
// <div class="pele-diagram" data-diagram="flowchart"><svg class="pele pele-flowchart" ...>...</svg></div>

To pass Pele render options, see Pele options.

ANSI

import { createAnsiRenderer } from '@comark/ansi'
import pele, { Pele } from 'comark-pele/ansi'

const renderAnsi = createAnsiRenderer({ plugins: [pele()], components: { Pele } })

Markdown

To write pele nodes back as fenced code blocks:

import { renderMarkdown } from 'comark/render'
import { Pele } from 'comark-pele'

const markdown = await renderMarkdown(tree, { components: { Pele } })

The pele node

\`\`\`mermaid {.wide #flow}
flowchart LR
  A --> B
\`\`\`

parses to:

['pele', { class: 'wide', id: 'flow', language: 'mermaid', content: 'flowchart LR\n  A --> B\n' }]

Props on the opening fence are passed to the component, as with the mermaid plugin.

Plugin options

pele({
  languages: ['mermaid', 'pele'], // code block languages turned into \`pele\` nodes
})

Using Comark’s mermaid plugin as well? Give Pele its own language, for example pele({ languages: ['pele'] }).

Components

The Vue, React, Svelte and Angular components accept:

PropDescription
contentDiagram source. Set from the node.
classExtra classes for the wrapper. Set from fence props.
optionsPele mount options, such as zoom, icons, onRender, or fontSize.

On the server and on the first render they output the SVG from Pele’s render(), so diagrams show up without JavaScript. In the browser they switch to Pele’s mount(), which:

  • fits the layout to the container width and redraws on resize,
  • enables zoom when a diagram has to shrink,
  • shows errors over the last diagram that rendered.

The wrapper is div.pele-diagram, with data-diagram set to the diagram type and data-error set to the message when the source can’t be drawn. Before the browser takes over, an invalid diagram shows its source in pre.pele-source.

Pele options

Every renderer that draws SVG exports createPeleComponent(options), which returns a Pele component with default Pele options:

import pele, { createPeleComponent } from 'comark-pele/vue'
// or 'comark-pele/html', '/react', '/svelte', '/angular'

const Pele = createPeleComponent({
  zoom: false,                  // no zoom controls
  fontSize: 14,
  autoDirection: false,         // keep \`flowchart LR\` horizontal on narrow screens
  icons: name => myIcons[name], // SVG for icons such as \`fa:fa-car\`
  onRender: result => console.log(result.type, result.links),
})

Then pass it like the default component: components: { Pele }. Pele itself is createPeleComponent().

  • Call it once, outside templates and render functions, so the component type stays the same between renders.
  • A component’s own options prop is merged on top. onRender and onError from both are called.
  • The HTML renderer only uses render options. zoom, onRender and onError apply in the browser, where the components use mount().
  • Mermaid settings such as themes go in the diagram itself, with frontmatter or a %%{init}%% directive.

Theming

Pele styles diagrams with CSS variables. Changing them, for example for dark mode, needs no re-render:

.pele {
  --pele-bg: #fffcf0;
  --pele-fg: #100f0f;
  --pele-line: #100f0f;
  --pele-surface: #f2f0e5;
  --pele-border: #cecdc3;
  --pele-accent: #205ea6;
  --pele-font: system-ui, sans-serif;
}

.dark .pele {
  --pele-bg: #100f0f;
  --pele-fg: #cecdc3;
  --pele-line: #cecdc3;
  --pele-surface: #1c1b1a;
  --pele-border: #403e3c;
}

See Pele theming for all variables.

Playground

pnpm play

Opens a two-column playground: edit markdown on the left, and pick a renderer on the right (HTML, Vue, React, Svelte, Angular, ANSI, or Markdown). Under the markdown, edit Pele options as JSON; they’re passed to createPeleComponent() and the renderer redraws. Each renderer uses the real Comark Markdown component of its framework with comark-pele, and is loaded only when selected. Use ?renderer=vue in the URL to open a specific one.

Development

pnpm install
pnpm play       # playground dev server (Vite)
pnpm play:build # build the playground to playground/dist
pnpm test       # Vitest: plugin, HTML, ANSI, Vue, React, Svelte, Angular
pnpm typecheck  # tsc + svelte-check
pnpm build      # tsdown, svelte-package, and ngc

Releasing

Releases use release-it with conventional commits:

pnpm release:dry  # preview the version, changelog, and commands
pnpm release      # release from a clean \`main\`

pnpm release runs pnpm verify (typecheck, tests, build), asks for the version, updates CHANGELOG.md from feat, fix, perf, and docs commits, commits chore: release vX.Y.Z, tags vX.Y.Z, publishes to npm with pnpm, pushes, and creates a GitHub release. Set GITHUB_TOKEN to create the release automatically; without it, release-it opens a pre-filled release form in the browser.