/ #javascript #mermaid 

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:

  1. Docsify starts running markdown.renderer.code
  2. Start Mermaid rendering (out of band, we won’t wait for this to complete before doing 3.)
  3. Return a string with an element which can be referenced by id (MERMAID_CONTAINER) or other attribute later to Docsify
  4. Docsify will render the returned element
  5. When Mermaid rendering completes (started in 2.)
    1. Inject the generated SVG into the MERMAID_CONTAINER element

As a timeline, this looks as follows:

gantt
    title Timeline of Docsify vs Mermaid rendering
    axisFormat  
    todayMarker off
    tickInterval 100millisecond
    section Docsify
      Run markdown.renderer.code()          : rendercode, 2000, 100ms
      Render renderer.code() output         : after rendercode, 140ms
    Section Mermaid
      Mermaid render()                      : mmd, 2000, 200ms
      Inject render() output SVG            : after mmd, 90ms

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.

Author

Hugo Di Francesco

Co-author of "Professional JavaScript", "Front-End Development Projects with Vue.js" with Packt, "The Jest Handbook" (self-published). Hugo runs the Code with Hugo website helping over 100,000 developers every month and holds an MEng in Mathematical Computation from University College London (UCL). He has used JavaScript extensively to create scalable and performant platforms at companies such as Canon, Elsevier and (currently) Eurostar.

Get The Jest Handbook (100 pages)

Take your JavaScript testing to the next level by learning the ins and outs of Jest, the top JavaScript testing library.