@generative-dom/plugin-external-renderer
v0.1.0
Published
Generative DOM plugin — delegate rendering of specific code-fence languages (mermaid, katex, etc.) to user-supplied render functions
Maintainers
Readme
@generative-dom/plugin-external-renderer
Delegate rendering of specific code-fence languages to your own code. LLMs constantly emit Mermaid diagrams, KaTeX math, PlantUML, GraphViz, D3 specs — this plugin watches for matching fenced blocks and hands each one off to a render function you supply. Generative DOM ships zero bundled libraries; you bring your own.
Install
pnpm add @generative-dom/plugin-external-rendererMermaid example
import { GenerativeDom } from '@generative-dom/core';
import { markdownCode } from '@generative-dom/plugin-markdown-code';
import { externalRenderer } from '@generative-dom/plugin-external-renderer';
import mermaid from 'mermaid';
const ext = externalRenderer({
adapters: [{
language: 'mermaid',
render: async (code, el, signal) => {
const { svg } = await mermaid.render(`m-${Date.now()}`, code);
if (signal.aborted) return;
el.innerHTML = svg; // user owns this div — innerHTML is their call
},
}],
});
const container = document.getElementById('chat')!;
const md = new GenerativeDom({ container, plugins: [markdownCode(), ext] });
ext.attach(container);KaTeX example
import katex from 'katex';
externalRenderer({
adapters: [{
language: 'math',
render: (code, el) => {
katex.render(code, el, { throwOnError: false, displayMode: true });
},
}],
});Options
externalRenderer({
adapters: [
{ language: 'mermaid', render: renderMermaid },
{ language: 'math', render: renderKatex, wrapperClass: 'my-math' },
],
showSource: false, // set true to append a <details>source</details> block
});Rendered shape
For a fenced block with ```mermaid:
<div class="md-external-mermaid" data-generative-dom-external="mermaid">
<div class="md-external-content">
<!-- whatever your render function appended -->
</div>
<!-- optional: <details class="md-external-source">...</details> -->
</div>If the render function throws or its promise rejects, the wrapper gains
data-render-error="..." and falls back to the raw <pre><code> so the
content is never lost.
Cancellation
Each render receives an AbortSignal that fires when:
- The plugin is destroyed (
ctx.onDestroy) detach()is called- The container is swapped via a second
attach(newContainer)
Async adapters should check signal.aborted before touching the DOM after an
await, or hook signal.addEventListener('abort', ...) to cancel upstream
work (e.g. in-flight fetch requests).
API
externalRenderer(options)— returns anGenerativeDomPluginaugmented with:attach(container)— start observing; rewrite matched blocksdetach()— stop observing and abort pending renders
