How to use markdown-exit (or another Markdown renderer) in Eleventy

Problem

Eleventy uses markdown-it as its default Markdown renderer. However, markdown-it forces plugins to be synchronous, which makes it impossible to, for example, fetch Open Graph data or optimize images during Markdown rendering. Shiki, my favorite syntax highlighter, is also asynchronous by default. Shiki can be run synchronously, but we would have to load all languages in advance or manually specify the languages used in our Markdown files. Neither solution is ideal.

Several projects attempt to address this issue. markdown-it-async, a thin wrapper for markdown-it authored by one of the maintainers of Shiki, adds support for asynchronous syntax highlighting. markdown-exit is a drop-in replacement for markdown-it that addresses a broader set of markdown-it's issues, including support for asynchronous rendering.

I read the "Markdown" page of the official documentation and learned how to modify markdown-it options or add plugins. Since markdown-exit is compatible with markdown-it, we can replace the markdown-it instance with markdown-exit via setLibrary as described in the documentation, but this does not enable asynchronous rendering. Eleventy calls the render method of the library instance and expects it to return a string.

It took me a while to find a solution because I had been stuck on using the default setLibrary approach. In reality, the best solution doesn't even rely on compatibility between markdown-it and markdown-exit.

Solution

Actually, Eleventy does not crash and still generates HTML correctly even if the render method returns Promise<string>, so the following code accidentally works fine. Of course, this behavior is undocumented and could change at any time.

// It works, but...
eleventyConfig.setLibrary("md", { render: md.renderAsync.bind(md) });

Fortunately, Eleventy is flexible enough to allow us to use a completely different Markdown renderer. This behavior is explicitly documented.

Custom — Eleventy

import shikiPlugin from "@shikijs/markdown-exit";
import { createMarkdownExit } from "markdown-exit";

// Eleventy enables the `html` option by default
const md = createMarkdownExit({ html: true });
md.use(shikiPlugin({ theme: "nord" }));

eleventyConfig.addExtension("md", {
  async compile(input, path) {
    return async (data) => await md.renderAsync(input);
  },
});

It is worth noting that we could even choose a library that is not compatible with markdown-it, such as Remark or Marked. However, I still use markdown-exit because it is the fastest Markdown renderer that Shiki officially supports; it is noticeably faster than Remark & Rehype.

Source code

This blog is built using Eleventy v4 alpha and markdown-exit. Note that the blog's technology stack may change in the future.

eleventy.config.ts at 9d18664