rehype-graphviz-diagram
Version:
A Unified/Rehype Plugin that allows you transform graphviz codes into SVG diagram.
81 lines (80 loc) • 2.94 kB
JavaScript
import { instance } from '@viz-js/viz';
import { visit } from 'unist-util-visit';
import { fromHtmlIsomorphic } from 'hast-util-from-html-isomorphic';
import { h } from 'hastscript';
const defaultOptions = {
containerTagName: 'figure',
containerTagProps: {},
postProcess: (svg) => svg,
};
const layoutEngines = [
'dot',
'neato',
'fdp',
'sfdp',
'circo',
'twopi',
'nop',
'nop2',
'osage',
'patchwork',
];
const graphvizInstance = await instance();
export const rehypeGraphvizDiagram = function (options = defaultOptions) {
const mergedOptions = {
containerTagName: options?.containerTagName ?? defaultOptions.containerTagName,
containerTagProps: options?.containerTagProps ?? defaultOptions.containerTagProps,
postProcess: options?.postProcess ?? defaultOptions.postProcess,
};
return (tree) => {
visit(tree, 'element', (node, index, pre) => {
// Ensure the current node is a 'pre' block containing a 'code' element
if (node.tagName !== 'code' ||
pre?.type !== 'element' ||
pre.tagName !== 'pre' ||
index === undefined) {
return;
}
const className = node.properties.className;
if (!Array.isArray(className) || className.length === 0)
return;
const lang = className[0].toString().replace(/^language-/, '');
let engine;
if (layoutEngines.includes(lang)) {
engine = lang;
}
else if (lang === 'graphviz') {
engine = 'dot';
}
else if (lang.match(/^graphviz-.+$/i)) {
engine = lang.split('-')[1];
}
else {
return;
}
// If there's no content in the code block, skip it
if (node.children.length === 0 || node.children[0].type !== 'text') {
return;
}
const graphvizCode = node.children[0].value;
// Generate SVG from Graphviz code
try {
const svg = mergedOptions.postProcess(graphvizInstance.renderString(graphvizCode, { engine, format: 'svg' }));
const svgHast = fromHtmlIsomorphic(svg, {
fragment: true,
});
// update the node to be a generated SVG
pre.tagName = mergedOptions.containerTagName;
pre.properties = mergedOptions.containerTagProps;
pre.children = svgHast.children;
}
catch (e) {
pre.tagName = 'div';
pre.children = [h('div', [h('b', 'Error:'), h('p', e.message)])];
}
// Skip the next index since we're replacing the original code block with the SVG
index += 1;
return index;
});
};
};