Chris Draycott-Wheatley

Superpower your Markdown in Eleventy

It recently occurred to me that it's possible to execute template code inline from Markdown files with Eleventy.

This opened up a whole bunch of possibilities I thought I'd explore and share.

So how does this work? When handling Markdown files Eleventy is able to run a pre-processing pass using your template engine of choice.

This pass can be used to trigger Nunjuck template includes meaning we can include specific templates directly in our Markdown files.

To set this up add markdownTemplateEngine: "njk" to your configuration object inside your Eleventy config file.

First up, rendering a React component to add some interactivity to a static article.

Inline code execution

If you've used React before you're probably familiar with the counter example above.

To render and execute this I'm including a Nunjuck template directly in the Markdown file used to render this blog post. The template doesn't contain any Nunjuck markup but instead includes an inline script tag which loads React and renders a component.

Here's what the code looks like in our markdown file:

{% include '_includes/components/react-counter.njk' %}

And here's what the include file looks like:

react-counter.njk

<div id="react"></div>

<script type="module">
import { React, ReactDOM, useState } from "https://unpkg.com/es-react";
import htm from "https://unpkg.com/htm?module";

const html = htm.bind(React.createElement);

function Example() {
const [count, setCount] = useState(0);

return html`<div>
<p>You clicked
${count} times</p>
<button onClick=
${() => setCount(count + 1)}>Click me</button>
</div>
`
;
}

ReactDOM.render(html`<${Example} />`, document.getElementById("react"));
</script>

Neat, huh? I'm using htm and es-react to avoid any kind of a build step for simplicity's sake.

This type of inline execution could be useful for interactive data visualisations or third-party widgets.

So what else could we use this functionality for? How about embedding a YouTube video. Using Eleventy's shortcodes we can create a YouTube shortcode which takes a video ID and returns the YouTube embed code and some formatting styles:

Inline code execution
{% youtube 'ix5mPa6D7ZA' %}

youtube-shortcode.js

const outdent = require("outdent")({ newline: " " });

module.exports = function (id) {
return outdent`
<style>
.video-wrapper {
position: relative;
padding-bottom: 56.25%;
}

.video-wrapper iframe {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
</style>
<div class="video-wrapper">
<iframe src="https://www.youtube.com/embed/
${id}" frameborder="0" allowfullscreen allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"></iframe>
</div>
`
;
};

This shortcode needs registering in our Eleventy config file, like so:

module.exports = function (eleventyConfig) {
eleventyConfig.addShortcode(
"youtube",
require("./_includes/shortcodes/youtube")
);
};

Shortcodes are a great tool to create small versatile components which take arguments. Other uses could be things like an Instagram, Twitter or Github Gist embed.

So far we've created a JavaScript style widget and an embed style widget. How about creating something that is more integrated into your site's data cascade.

If you've checked out my post on displaying your Eleventy leaderboard rank on your site you'll know that I put together an Eleventy leaderboard API. Let's call that API, add the data to the Eleventy cascade and show the top 11 sites on the leaderboard.

Inline code execution

Eleventy Leaderboard top 11

  1. toddl.dev
  2. a11yproject.com
  3. lamplightdev.com
  4. mikeaparicio.com
  5. industrialempathy.com
  6. piraces.dev
  7. hire.wil.to
  8. ryangittings.co.uk
  9. austinjavascript.com
  10. eleventy-gallery.netlify.app
  11. joelamyman.co.uk

Here's the code we're using to include that above:

{% include '_includes/components/top-eleven.njk' %}

To power this I'm pulling in the leaderboard data like so:

leaderboard.js

const CacheAsset = require("@11ty/eleventy-cache-assets");

module.exports = async () => {
try {
const json = await CacheAsset(
`https://eleventy-leaderboard-api.netlify.app/v1/leaderboard.json`,
{
duration: "1d",
type: "json",
}
);

return json;
} catch (error) {
console.warn(`Unable to generate leaderboard data due to ${error}`);
}
};

And here's what our top-eleven.njk template looks like:

top-eleven.njk

<p>Eleventy Leaderboard top 11</p>
<ol>
{% for i in range(0, 11) %}
<li>
<a href="{{leaderboard[i].url}}">{{leaderboard[i].url.slice(0, -1)}}</a>
</li>
{% endfor %}
</ol>

This pattern gives you the ability to create reusable components and share them across your site dynamically.

As I've come to expect from Eleventy by now, anything is possible.

Whether you want to embed a video, run some JavaScript or render a custom widget inside a Markdown file it's all possible thanks to the flexibility and customisability of Eleventy.