Twenty rendering engines under one chrome sounds like a maintenance disaster. It mostly isn't. Here's how.
The Renderer dispatcher
Every engine renders into the same DOM node: a single #diagram-host div. The Renderer is a long if/else dispatcher — one branch per engine — that lazy-loads its dependency on first use and writes output into that host. Adding a new engine is one new branch.
} else if (engine === "wardley") {
const { wardleyToSvg } = await import("@/lib/wardley");
host.innerHTML = wardleyToSvg(code, { theme });
}
The benefits compound:
- The export pipeline (PNG / SVG / PDF / TikZ) reads from the host. It doesn't care which engine produced the SVG. One export pipeline, twenty engines.
- Click-to-edit, the AI suggestions, the theme observer, the share-link encoder — all engine-agnostic.
- The bundle stays small because every engine is a dynamic import.
Three deployment strategies
We use three strategies depending on the engine, weighing bundle size against client requirements:
1. Pure TypeScript engines. Wardley, Newick, D2-to-DOT translator, our SVG path renderer. Small, no deps, run in a webworker if we want them to.
2. WASM engines loaded on demand. Graphviz (@hpcc-js/wasm/graphviz), Mermaid 11, KaTeX. Heavy on first use, cached forever after.
3. Server proxies / Kroki. Pikchr renders by POST to kroki.io. PlantUML renders through our own /api/plantuml route. We don't ship a 10 MB Java runtime to every visitor.
The right choice per engine depends on a single question: does the user pay for it in bundle size? If the answer would be every page load, we proxy. If the answer is only when this engine is selected, we lazy-import. If the engine itself is small (< 5 kB), we inline.
The hard part isn't the engines
It's everything else. Click-to-edit binds to elements in the rendered SVG — needs an engine-agnostic post-processor. The AI co-pilot needs a system prompt that knows every engine's syntax (and the engineHint field nudges it). Exports need transparentised backgrounds. Dark mode needs each engine's text fill to flip. The studio's right-side AI panel needs to refine the diagram without losing the user's manual edits.
Each of those is an engine-agnostic concern with one implementation. Twenty engines × one implementation each = twenty things that all work uniformly.
What this means for users
You don't see any of this. You see twenty engines that all share the same toolbar, the same AI panel, the same export menu, the same share-link format, the same shortcut keys. You don't have to learn a different tool for every diagram type — you have to learn one tool that happens to do twenty diagram types.
The point of having twenty engines isn't to be biggest. It's that you stop tab-switching when the diagram type changes.