Using mermaid.js with eleventy.io.

Recently I have been working on a project to create documentation for a large project automatically, and following current trends, we’ve focused on generating a static site using eleventy.io. This awesome system allows us to use markdown readme files for contextual content, and include markdown format in our jsdocs and swagger comments as well.

The issue I’m facing today is this: now that we can include much more contextual content in our documentation, we find we need to include diagrams a lot – after all, a diagram can save 1000 words. or something.

We already have the ability to create diagrams in Visio or Lucid Charts, and export those into image, which we import into the site and reference from the docs (shew) – but that makes it very hard to keep the diagrams updated – you have to track down the original source file, and re-export-import the image. Much better to use some sort of descriptor which can be saved inline – want to update the diagram in your doc? Just update the descriptor in that same doc – quick and easy.

Introducing mermaid.js, which gives us exactly this functionality – we can add some code in markdown, and it renders as a diagram in our final output. All we need to do is wire it up – simple.

Markdown content like this

```mermaid
graph TD;
    A(Start)-->B;
    A-->C;
    B-->D[End];
    C-.->D;
```

becomes

My first thought was to create an eleventy plugin that would capture these mermaid fenced blocks as they come past, and transform them into the correct HTML. There were several problems with this approach, though:

  • Mermaid is very tightly dependent on the browser context to function. Even the mermaidApi, which I had hoped would not suffer from this very hard dependency, does. A quick delve into the code shows that it uses the browser context to identify all sorts of style and formatting settings you would otherwise have to manually provide, so it is not entirely a bad thing. But it does make it very difficult to use from Node, and therefore almost impossible for my use case.
    • It is possible to run mermaid in a headless browser to generate the HTML code on the server, but that seems like a lot of processing for a small part of a much larger process.
  • Eleventy only allows one markdown highlighter at a time. This seems to be an inherited limitation from the underlying markdown-it library. This is an issue because I also want code highlighting for all other code fence languages – this is, after all, a software documentation site; it’ll be filled with code snippets and examples.

The solution I’ve come up with goes like this: mermaid.js is hard to use on the backend, but very easy to add to my final HTML files. All I need to do is intercept the markdown highlighting and decorate my mermaid code with:

<pre class="mermaid">...</pre>

mermaid on the client will pick that up, and convert the code into an SVG on the fly. So my code goes like this:

.eleventy.js (the eleventy config file):

const eleventyPluginSyntaxHighlighter = require('@11ty/eleventy-plugin-syntaxhighlight');

module.exports = function (eleventyConfig) {
  eleventyConfig.addPlugin(eleventyPluginSyntaxHighlighter);
  const highlighter = eleventyConfig.markdownHighlighter;
  eleventyConfig.addMarkdownHighlighter((str, language) => {
    if (language === "mermaid") {
      return `<pre class="mermaid">${str}</pre>`;
    }
    return highlighter(str, language);
  });

  return {
    ...
  }
};

We add the highlighter plugin first, which sets itself as the markdown highlighter. We grab a reference to that, and replace it with a quick one of our own. That handles any ‘mermaid’ code snippets, and offloads all others to the highlighter.

template.hbs (layout template for all markdown files)

<!DOCTYPE html>
<html lang="en">
<head>
  <title>{{ title }}</title>
  <script src="https://unpkg.com/mermaid/dist/mermaid.min.js"></script>
  <script>mermaid.initialize({ startOnLoad: true });</script>
  <link rel="stylesheet" href="_styles/prism-atom-dark.css" />
</head>
<body>
  {{{ content }}}
</body>
</html>

This fetches the mermaid javascript, and initialises it. It also includes the prism atom-dark theme css, for use by the highlighter. That’s it. With this setup, this markdown page:

---
layout: template
---

# Testing

## Some Code

```js
function(x){
  doSomething();
  return false;
}
```

## Flow Diagram

```mermaid
graph TD;
    A(Start)-->B;
    A-->C;
    B-->D[End];
    C-.->D;
```

renders as: