Skip to content

Commit a7a3829

Browse files
committed
feat(react): render ```dot code blocks as Graphviz diagrams
When the optional `@hpcc-js/wasm-graphviz` dependency is installed, ```dot code blocks become inline SVG diagrams. Colors written as `${light|dark}` render a diagram per color scheme. Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent f0abf2c commit a7a3829

4 files changed

Lines changed: 95 additions & 0 deletions

File tree

‎packages/react/package.json‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@
5959
"vite": "~8.2.2"
6060
},
6161
"devDependencies": {
62+
"@hpcc-js/wasm-graphviz": "^1.22.0",
6263
"dedent": "^1.7.2"
64+
},
65+
"peerDependencies": {
66+
"@hpcc-js/wasm-graphviz": "^1.22.0"
67+
},
68+
"peerDependenciesMeta": {
69+
"@hpcc-js/wasm-graphviz": {
70+
"optional": true
71+
}
6372
}
6473
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
'use strict';
2+
3+
import { visit } from 'unist-util-visit';
4+
5+
/**
6+
* Graphviz, when the optional `@hpcc-js/wasm-graphviz` dependency is
7+
* installed. Loaded up front, as the Markdown pipeline runs synchronously.
8+
*
9+
* @type {import('@hpcc-js/wasm-graphviz').Graphviz | undefined}
10+
*/
11+
const graphviz = await import('@hpcc-js/wasm-graphviz')
12+
.then(({ Graphviz }) => Graphviz.load())
13+
.catch(() => undefined);
14+
15+
/** A color for each color scheme: `${#3c3c43|#dfdfd6}` */
16+
const THEMED_COLOR = /\$\{([^|}]*)\|([^}]*)\}/g;
17+
18+
/**
19+
* Renders DOT source as an inline SVG.
20+
*
21+
* @param {string} dot
22+
*/
23+
const render = dot =>
24+
graphviz
25+
.dot(dot, 'svg_inline')
26+
// A blank line would end the HTML block the SVG is embedded in
27+
.replace(/\n\s*\n/g, '\n')
28+
.trim();
29+
30+
/**
31+
* Renders a diagram. One using colors for each color scheme renders twice,
32+
* one SVG per scheme, the page's styles showing the one of the current
33+
* scheme.
34+
*
35+
* @param {string} dot
36+
*/
37+
export const renderDiagram = dot => {
38+
if (!dot.includes('${')) {
39+
return `<div class="diagram">${render(dot)}</div>`;
40+
}
41+
42+
return ['light', 'dark']
43+
.map(scheme => {
44+
const colors = dot.replace(THEMED_COLOR, (_, light, dark) =>
45+
scheme === 'light' ? light : dark
46+
);
47+
48+
return `<div class="diagram diagram-${scheme}">${render(colors)}</div>`;
49+
})
50+
.join('\n');
51+
};
52+
53+
/**
54+
* Renders ```dot code blocks as Graphviz diagrams, when Graphviz is
55+
* installed; otherwise, they stay code blocks.
56+
*
57+
* @param {import('mdast').Root} tree
58+
*/
59+
const transformer = tree => {
60+
if (!graphviz) {
61+
return;
62+
}
63+
64+
visit(tree, 'code', (node, index, parent) => {
65+
if (node.lang !== 'dot') {
66+
return;
67+
}
68+
69+
parent.children[index] = { type: 'html', value: renderDiagram(node.value) };
70+
});
71+
};
72+
73+
/**
74+
* The remark plugin rendering Graphviz diagrams.
75+
*/
76+
export default () => transformer;

‎packages/react/src/jsx-ast/utils/remark.mjs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { visit } from 'unist-util-visit';
1515

1616
import { AST_NODE_TYPES } from '../constants.mjs';
1717
import transformAlerts from './plugins/alerts.mjs';
18+
import transformDiagrams from './plugins/diagrams.mjs';
1819
import transformElements from './plugins/transformer.mjs';
1920

2021
const passThrough = ['element', ...Object.values(AST_NODE_TYPES.MDX)];
@@ -58,6 +59,7 @@ export const getRemarkRecma = lazy(() =>
5859
unified()
5960
.use(remarkParse)
6061
.use(transformAlerts)
62+
.use(transformDiagrams)
6163
// We make Rehype ignore existing HTML nodes, and JSX nodes
6264
// as these are nodes we manually created during the generation process
6365
// We also allow dangerous HTML to be passed through, since we have HTML within our Markdown

‎pnpm-lock.yaml‎

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)