Docsify Mermaid 10
Using Docsify and Mermaid 10 can be tricky since Mermaid 10 renders asynchronously (mermaid.render
returns a Promise).
This post shows how to work around the mismatch between Docsify’s markdown.renderer.code
and Mermaid’s render
function on Mermaid v9 and v10.
Table of Contents
Working example available at: github.com/HugoDF/docsify-mermaid-10.
A summary of the problem
To extend Docsify markdown rendering, a $docsify.markdown.renderer.code
function can be provided. However $docsify.markdown.renderer.code
expects a function that returns a string, not a Promise.
This causes problems due to Mermaid v9+ rendering. On Mermaid v9, the flowchart-elk
diagram is rendered asynchronously (mermaid.render
returns a promise). This behaviour of mermaid.render
returning a Promise is true for all diagrams in Mermaid v10.
A naive implementation when mermaid.render
returns a Promise would be to extract the svg
property as follows:
window.$docsify = {
markdown: {
renderer: {
code(code, lang) {
if (lang === 'mermaid') {
return mermaid.render(`mermaid-svg`, code).then(({ svg }) => svg);
}
return this.origin.code.apply(this, arguments);
},
},
},
};
When we load this in the browser, the block is rendered as follows, this proves the point that $docsify.markdown.renderer.code
does not support a function that returns a Promise:
[object Promise]
We’ve now covered the issue caused by the mismatch of interface between mermaid.render
(which returns a Promise) and $docsify.markdown.renderer.code
(which expects a string). Next we’ll see how to work around this.
A solution
Since we control the return value of $docsify.markdown.renderer.code
and the code in that function runs with access to the page’s DOM, we can solve this issue as follows:
- Docsify starts running
markdown.renderer.code
- Start Mermaid rendering (out of band, we won’t wait for this to complete before doing 3.)
- Return a string with an element which can be referenced by id (
MERMAID_CONTAINER
) or other attribute later to Docsify - Docsify will render the returned element
- When Mermaid rendering completes (started in 2.)
- Inject the generated SVG into the
MERMAID_CONTAINER
element
- Inject the generated SVG into the
As a timeline, this looks as follows:
We can code this up as follows:
let svgCounter = 0;
window.$docsify = {
markdown: {
renderer: {
code(code, lang) {
if (lang === 'mermaid') {
const svgName = `mermaid-svg-${svgCounter++}`;
const MERMAID_CONTAINER_ID = `${svgName}-container`;
// 2. start mermaid rendering
mermaid.render(svgName, code).then(({ svg }) => {
// 5. On Mermaid render completion, inject the SVG
// into the element created from 3.
const containerElement = document.querySelector(
`#${MERMAID_CONTAINER_ID}`
);
if (containerElement) {
containerElement.innerHTML = svg;
} else {
console.error(`Error: #${MERMAID_CONTAINER_ID} not found`);
}
});
// 3. Return an element that can be referenced by mermaid rendering
return `<div class="mermaid" id="${MERMAID_CONTAINER_ID}"></div>`;
}
return this.origin.code.apply(this, arguments);
},
},
},
};
Demos
We can see the code in action at example.codewithhugo.com/docsify-mermaid-10/#/?id=demos.
The source code is available at: github.com/HugoDF/docsify-mermaid-10.
Interested in Alpine.js?
Power up your debugging with the Alpine.js Devtools Extension for Chrome and Firefox. Trusted by over 15,000 developers (rated 4.5 ⭐️).
orJoin 1000s of developers learning about Enterprise-grade Node.js & JavaScript