Modify Markdown Image Output

Markdown itself supports only a few attributes for embedding images. Instead of plugins or shortcodes, let's extend the renderer!

The first option to set more attributes for an image than src and alt in Markdown is using a plugin (something like markdown-it-attrs). While this is doable, it should be possible to extend the markup without repeating every attribute for each image manually, right?

The second option is to bypass the regular Markdown syntax, e.g. by using a shortcode. This option offers the advantage of full flexibility, but if you use an editor or CMS to create the Markdown files, you usually have to extend the CMS itself and bypass feel-good features like the image preview.

However, there is a third way to dynamically change the output of Markdown images. When parsing Markdown via markdown-it you can hook into the renderer and determine the output manually.

Basic Example

A basic example of this would look like the following.

Note: The markdown-it renderer does not support asynchronous functions, so we have to limit ourselves to synchronous processing.

let md = require("markdown-it")();
md.renderer.rules["image"] = function (tokens, idx, options, env, self) {
  // Get token of a specific image
  const token = tokens[idx];

  // Use token to get src and alt attributes
  const src = token.attrGet("src");
  const alt = token.content;

  // Return every image output you want
  return `<img src="${src}" alt="${alt}" loading="lazy" />`;
}

Responsive Markdown Image using 11ty Image

With a library like 11ty Image you can now perform much more complex optimizations based on the data. In this example we create responsive images from Markdown data.

let md = require("markdown-it")();
md.renderer.rules["image"] = function (tokens, idx, options, env, self) {
  try {
    const token = tokens[idx];
    const alt = token.content;
    let src = token.attrGet("src");

    // Extend relative paths for local images 
    if (src.startsWith("/assets")) {
      src = "input/directory/" + src;
    }

    // Add additional options (see xyz for full list)
    const imageOptions = {
      widths: [320, 480, 600, 720, 1200, "auto"],
      formats: ["webp", "avif"],
      urlPath: "pass/this/path/to/output/url",
      outputDir: "output/image/here",

      // Set custom filename (see https://www.11ty.dev/docs/plugins/image/#custom-filenames)
      filenameFormat: function(id, src, width, format, options) {
        const extension = path.extname(src);
        const name = path.basename(src, extension);
        return `${name}-${width}w.${format}`;
      }
    };

    Image(src, imageOptions);
    let metadata = Image.statsSync(src, imageOptions);
    let sizes = "(min-width: 800px) 45rem, 90vw";

    // Automatically generate HTML and output for image
    return Image.generateHTML(metadata, {
      sizes,
      alt,
      loading: "lazy",
      decoding: "async",
    });
  } catch (e) {
    console.error(e);
  }
};