Files
Balint Orosz 3185b9b691 Initial release - beautiful-mermaid v0.1.0
MIT licensed mermaid diagram renderer with SVG + ASCII output.
15 built-in themes, full Shiki compatibility, zero DOM dependencies.

Co-Authored-By: Craft Agent <agents-noreply@craft.do>
2026-01-28 17:31:57 +01:00

152 lines
5.4 KiB
TypeScript

/**
* Performance benchmark for beautiful-mermaid.
*
* Runs all sample definitions through both renderers (SVG + ASCII) in Bun
* and prints a table with per-sample timing and aggregate stats.
*
* Usage: bun run packages/mermaid/bench.ts
*/
import { samples } from './samples-data.ts'
import { renderMermaid } from './src/index.ts'
import { renderMermaidAscii } from './src/ascii/index.ts'
// ============================================================================
// Types
// ============================================================================
interface Result {
index: number
title: string
category: string
svgMs: number
asciiMs: number
svgError: string | null
asciiError: string | null
}
// ============================================================================
// Helpers
// ============================================================================
/** Pad/truncate a string to exactly `width` characters, right-aligned if numeric. */
function col(value: string, width: number, align: 'left' | 'right' = 'left'): string {
const truncated = value.length > width ? value.slice(0, width - 1) + '\u2026' : value
return align === 'right' ? truncated.padStart(width) : truncated.padEnd(width)
}
function fmtMs(ms: number): string {
return ms.toFixed(1)
}
// ============================================================================
// Main
// ============================================================================
const results: Result[] = []
const totalStart = performance.now()
console.log(`\nbeautiful-mermaid — Benchmark (${samples.length} samples)`)
console.log('═'.repeat(90))
console.log(
`${col('#', 4, 'right')} ${col('Title', 38)} ${col('Category', 15)} ${col('SVG (ms)', 10, 'right')} ${col('ASCII (ms)', 10, 'right')} ${col('Total', 10, 'right')}`,
)
console.log('─'.repeat(90))
for (let i = 0; i < samples.length; i++) {
const sample = samples[i]!
const category = sample.category ?? 'Other'
let svgMs = 0
let asciiMs = 0
let svgError: string | null = null
let asciiError: string | null = null
// Render SVG (async — uses dagre layout for flowcharts/state/class/ER)
try {
const t0 = performance.now()
await renderMermaid(sample.source, sample.options)
svgMs = performance.now() - t0
} catch (err) {
svgError = String(err)
svgMs = -1
}
// Render ASCII (sync — custom text layout, no dagre)
try {
const t0 = performance.now()
renderMermaidAscii(sample.source)
asciiMs = performance.now() - t0
} catch (err) {
asciiError = String(err)
asciiMs = -1
}
const totalMs = (svgMs >= 0 ? svgMs : 0) + (asciiMs >= 0 ? asciiMs : 0)
results.push({ index: i, title: sample.title, category, svgMs, asciiMs, svgError, asciiError })
// Print row
const svgStr = svgMs >= 0 ? fmtMs(svgMs) : 'ERR'
const asciiStr = asciiMs >= 0 ? fmtMs(asciiMs) : 'N/A'
console.log(
`${col(String(i + 1), 4, 'right')} ${col(sample.title, 38)} ${col(category, 15)} ${col(svgStr, 10, 'right')} ${col(asciiStr, 10, 'right')} ${col(fmtMs(totalMs), 10, 'right')}`,
)
}
const totalElapsed = performance.now() - totalStart
// ============================================================================
// Aggregates
// ============================================================================
console.log('═'.repeat(90))
const svgTimes = results.filter(r => r.svgMs >= 0).map(r => r.svgMs)
const asciiTimes = results.filter(r => r.asciiMs >= 0).map(r => r.asciiMs)
const svgTotal = svgTimes.reduce((a, b) => a + b, 0)
const asciiTotal = asciiTimes.reduce((a, b) => a + b, 0)
console.log(`Total: ${fmtMs(totalElapsed)}ms (SVG: ${fmtMs(svgTotal)}ms, ASCII: ${fmtMs(asciiTotal)}ms)`)
console.log(`Average: ${fmtMs((svgTotal + asciiTotal) / results.length)}ms per sample`)
// Find slowest SVG and ASCII
if (svgTimes.length > 0) {
const slowestSvg = results.filter(r => r.svgMs >= 0).sort((a, b) => b.svgMs - a.svgMs)[0]!
console.log(`Slowest SVG: #${slowestSvg.index + 1} ${slowestSvg.title} (${fmtMs(slowestSvg.svgMs)}ms)`)
}
if (asciiTimes.length > 0) {
const slowestAscii = results.filter(r => r.asciiMs >= 0).sort((a, b) => b.asciiMs - a.asciiMs)[0]!
console.log(`Slowest ASCII: #${slowestAscii.index + 1} ${slowestAscii.title} (${fmtMs(slowestAscii.asciiMs)}ms)`)
}
// Report errors
const svgErrors = results.filter(r => r.svgError)
const asciiErrors = results.filter(r => r.asciiError)
if (svgErrors.length > 0) {
console.log(`\nSVG errors (${svgErrors.length}):`)
for (const r of svgErrors) {
console.log(` #${r.index + 1} ${r.title}: ${r.svgError}`)
}
}
if (asciiErrors.length > 0) {
console.log(`\nASCII unsupported (${asciiErrors.length}):`)
for (const r of asciiErrors) {
console.log(` #${r.index + 1} ${r.title}`)
}
}
// Category breakdown
console.log('\n── By Category ──')
const catMap = new Map<string, Result[]>()
for (const r of results) {
if (!catMap.has(r.category)) catMap.set(r.category, [])
catMap.get(r.category)!.push(r)
}
for (const [cat, catResults] of catMap) {
const catSvg = catResults.filter(r => r.svgMs >= 0).reduce((a, r) => a + r.svgMs, 0)
const catAscii = catResults.filter(r => r.asciiMs >= 0).reduce((a, r) => a + r.asciiMs, 0)
console.log(` ${col(cat, 16)} ${col(String(catResults.length), 3, 'right')} samples SVG: ${col(fmtMs(catSvg), 8, 'right')}ms ASCII: ${col(fmtMs(catAscii), 8, 'right')}ms Total: ${col(fmtMs(catSvg + catAscii), 8, 'right')}ms`)
}
console.log()