Search code examples
htmlimagemarkdownwebpeleventy

Adding a fallback images in markdown


I have been replacing my existing site images with .webp. And in HTML it's relatively easy to add fallback support for instance in which .webp isn't supported. However, I'm struggling to find an equivalent for markdown?

HTML

<img src="img.webp" />

HTML with fallback support

<picture>
  <source srcset="img.webp" type="image/webp">
  <source srcset="img.jpg" type="image/jpeg"> 
  <source srcset="img.png" type="image/png"> 
</picture> 

For Example

Markdown

![](img.webp)

Markdown with fallback support

![](img.webp,img.png)

Solution

  • I don't think there's a spec for fallback images like that in markdown, but if you're using markdown-it (which is the default markdown parser in Eleventy) you can add custom plugins to extend the syntax. You can also just put HTML in your markdown, or you can even use shortcodes in your markdown if you're using Eleventy.

    Using shortcodes

    You can define custom shortcodes in Eleventy and then use them in your markdown. This could be easier and more flexible than setting up markdown-it plugins, but has the drawback of putting non-markdown things in your markdown.

    Using a markdown-it plugin

    If you already have pre-generated images that you want to use, you may want to use something like markdown-it-picture and modify it to set the type attribute, either by parsing the file extension or changing the media query/title spot to accept the type instead. You'll want to copy the code into a new file in your project, make your modifications, and register it like so:

    // .eleventy.js
    module.exports = function(eleventyConfig) {
      const mdPicture = require('/path/to/plugin.js')
      const md = require('markdown-it')()
      md.use(mdPicture)
      eleventyConfig.setLibrary('md', md)
      // ...
    };
    

    With eleventy-image

    Alternatively, if you don't have images already generated and would like to generate them as part of the 11ty build step, you can use eleventy-image (Docs). This Twitter thread has a discussion on creating a custom markdown-it renderer, which I've modified to use with eleventy-image below.

    // you might want to put this in another file
    // such as ./utils/markdown.js
    
    // responsive images with 11ty image
    // this overrides the default image renderer
    // titles are also used for size setting (optional)
    //
    // ![alt text](/path/to/img.png '(max-width: 768px) 100vw, 768px')
    
    const md = require('markdown-it')();
    const Image = require('@11ty/eleventy-img')
    
    md.renderer.rules.image = function (tokens, idx, options, env, self) {
      const token = tokens[idx]
      let imgSrc = token.attrGet('src')
      const imgAlt = token.content
    
      // you can modify the default sizes, or omit
      const imgSize = token.attrGet('title') || '(max-width: 768px) 100vw, 768px'
    
      const widths = [250, 426, 580, 768] // choose your own widths, or [null] to disable resize
      const imgOpts = {
        widths: widths
          .concat(widths.map((w) => w * 2)) // generate 2x sizes for retina displays
          .filter((v, i, s) => s.indexOf(v) === i), // dedupe widths
        formats: ['webp', 'jpeg'], // choose your own formats (see docs)
        urlPath: '/assets/img/', // src path in HTML output
        outputDir: './_site/assets/img/' // where the generated images will go
      }
    
      // generate the images
      // see https://www.11ty.dev/docs/plugins/image/#synchronous-usage
      Image(imgSrc, imgOpts)
    
      const metadata = Image.statsSync(imgSrc, imgOpts)
    
      return Image.generateHTML(metadata, {
        alt: imgAlt,
        sizes: imgSize,
        loading: 'lazy',
        decoding: 'async'
      })
    }
    
    // in your .eleventy.js
    module.exports = function(eleventyConfig) {
      // you may need to `require` the file with
      // your markdown config with the custom renderer
      // const md = require('./utils/markdown.js')
      eleventyConfig.setLibrary('md', md)
      // ...
    };
    

    This renderer will automatically generate different sizes of your image, as well as different formats, and then output the <picture> tags in your HTML. Since markdown-it doesn't play well with async functions, we use the syncronous pattern of eleventy-image. More information about configuration can be found in the docs.

    One thing you might have to be careful about is that the image path in your markdown should be where the image is located in your website source, NOT the eventual published URL. For example, my image may be located at src/images/img.png, but published at https://example.com/images/img.png. The markdown should be ![alt text](src/images/img.png) and NOT ![alt text](/images/img.png). You can also do additional parsing in the renderer function.