mdx-mermaid
Version:
Display mermaid diagrams in mdx files.
147 lines (143 loc) • 5.98 kB
JavaScript
;
var unistUtilVisit = require('unist-util-visit');
/**
* Copyright (c) Samuel Wall.
*
* This source code is licensed under the MIT license found in the
* license file in the root directory of this source tree.
*/
/* istanbul ignore next */
const renderToSvg = async (id, src, config, url) => {
const puppeteer = await import('puppeteer');
let browser = await puppeteer.launch({ args: ["--no-sandbox"] });
try {
let page = await browser.newPage();
await page.goto(`data:text/html,<!DOCTYPE html><script src="${url}"></script>`);
return await page.evaluate((diagramId, mermaidDiagram, config) => {
window.mermaid.initialize({ startOnLoad: false, ...config });
try {
return window.mermaid.mermaidAPI.render(diagramId, mermaidDiagram);
}
catch (error) {
return JSON.stringify(error);
}
}, id, src, config);
}
finally {
await browser.close();
}
};
const createMermaidNode = (node, hName, config) => {
return [{
type: 'mermaidCodeBlock',
data: {
hName,
hProperties: {
config: JSON.stringify(config),
chart: node.value,
},
},
}];
};
const outputAST = (node, index, parent, config) => {
return createMermaidNode(node, 'mermaid', config);
};
/* istanbul ignore next */
const outputSVG = async (node, index, parent, config) => {
const value = await renderToSvg(`mermaid-svg-${index}`, node.value, config && config.mermaid ? config.mermaid : {}, config?.svgMermaidSrc ?? 'https://cdn.jsdelivr.net/npm/mermaid@9.3.0/dist/mermaid.min.js');
const { fromHtml } = await import('hast-util-from-html');
const { toEstree } = await import('hast-util-to-estree');
const { toJs, jsx } = await import('estree-util-to-js');
const { fromMarkdown } = await import('mdast-util-from-markdown');
const { mdxjs } = await import('micromark-extension-mdxjs');
const { mdxFromMarkdown } = await import('mdast-util-mdx');
const { visit } = await import('estree-util-visit');
const hast = fromHtml(value, {
fragment: true,
space: 'svg'
});
const estree = toEstree(hast);
visit(estree, (node, key, index, ancestors) => {
const jsxElement = node;
if (node.type === 'JSXElement' && jsxElement.openingElement.name.name === 'style') {
const styleExpression = jsxElement.children[0];
const css = styleExpression.expression.value;
const buffer = Buffer.from(css);
const encoded = buffer.toString('base64');
jsxElement.children = [];
jsxElement.openingElement.attributes.push({
type: 'JSXAttribute',
name: { type: 'JSXIdentifier', name: 'href' },
value: { type: 'Literal', value: `data:text/css;base64,${encoded}` }
}, {
type: 'JSXAttribute',
name: { type: 'JSXIdentifier', name: 'rel' },
value: { type: 'Literal', value: `stylesheet` }
}, {
type: 'JSXAttribute',
name: { type: 'JSXIdentifier', name: 'type' },
value: { type: 'Literal', value: `text/css` }
});
jsxElement.openingElement.name.name = 'link';
jsxElement.openingElement.selfClosing = true;
jsxElement.closingElement = null;
const parent = ancestors[ancestors.length - 1];
parent.children.splice(parent.children.indexOf(node), 1);
estree.body[0].expression.children.push(node);
}
});
const js = toJs(estree, { handlers: jsx });
const tree = fromMarkdown(js.value.substring(2, js.value.length - 5), {
extensions: [mdxjs()],
mdastExtensions: [mdxFromMarkdown()]
});
return tree.children[0].children;
};
const findInstances = (ast) => {
const instances = [];
unistUtilVisit.visit(ast, { type: 'code', lang: 'mermaid' }, (node, index, parent) => {
instances.push([node, index, parent]);
});
return instances;
};
/**
* mdx-mermaid plugin.
*
* @param config Config passed in from parser.
* @returns Function to transform mdxast.
*/
function plugin(config) {
/* istanbul ignore next */
if (config?.output === 'svg') {
return async function transformer(ast) {
// Find all the mermaid diagram code blocks. i.e. ```mermaid
let instances = findInstances(ast);
// Replace each Mermaid code block with the Mermaid component
// Here we iterate over the instances and replace them with the SVG
// and run findInstances again to get the next set instances. We do this
// because the replacement process can change the AST and cause
// the indexes to be incorrect.
while (instances.length > 0) {
const [node, index, parent] = instances[0];
const result = await outputSVG(node, index, parent, config);
Array.prototype.splice.apply(parent.children, [index, 1, ...result]);
instances = findInstances(ast);
}
return ast;
};
}
return function transformer(ast) {
// Find all the mermaid diagram code blocks. i.e. ```mermaid
const instances = findInstances(ast);
// Replace each Mermaid code block with the Mermaid component
for (let i = 0; i < instances.length; i++) {
const [node, index, parent] = instances[i];
/* istanbul ignore next */
const passConfig = i == 0 ? config : undefined;
const result = outputAST(node, index, parent, passConfig);
Array.prototype.splice.apply(parent.children, [index, 1, ...result]);
}
return ast;
};
}
module.exports = plugin;