these notes concern ways of importing markdown from external files into a page built with eleventy, primarily to coalesce multiple markdown notes into a single web page. can be used to build things like a single page with multiple book reviews. if you use eleventy like a normal person (1 markdown file = 1 blog post) then you probably just want to write a layout for your markdown files.
collections
if each markdown file is treated by eleventy as a page (it's not in a directory that doesn't get built like _includes) then markdown files can be accessed using collections.
benefits:
- can be used in for loops
- already preconfigured – don't need to mess about with plugins and filters to use it
- if want to return a subset, can use filters
cons:
- so far i've only figured out how to use it with for loops
- if you want to render an individual file, can hack it using a for loop with only an if statement inside with some unique condition. i image if u have a lot of files in the collection though it might get inefficient
- might be able to write a filter that gets a specific file but i haven't figured that out yet
how i use it on my crafts page:
{% for fo in collections.crafts %}
<section>
<input class="modal-toggle" id="modal-{{ fo.page.fileSlug | slugify }}" type="checkbox" />
<label class="modal-open" for="modal-{{ fo.page.fileSlug | slugify }}">
<div class="img-box">
<img src="{{ fo.data.image }}" alt="{{ fo.data.title }}" />
<div class="transparent-box">
<h2 class="caption">{{ fo.data.title }}</h2>
</div>
</div>
</label>
<div class="modal">
<label class="modal-close" for="modal-{{ fo.page.fileSlug | slugify }}"></label>
<div class="popup">
{{ fo.content }}
</div>
</div>
</section>
{% endfor %}
{{ fo.content }}
is the body of the file, converted to html. {{ fo.data.image }}
and {{ fo.data.title }}
correspond to a property in the YAML front matter. {{ fo.page.fileSlug | slugify }}
is the filename without extension with the slugify filter. this makes it so i can generate a unique id for each project that is valid (ex. periods removed, spaces converted to dashes). for example, i can name a markdown file cardigan no. 9.md
, and it'll generate the id modal-cardigan-no-9
.
if you don't want the file to get their own page, can skip writing to the output folder by adding permalink: false
to the front matter of the markdown file. you can also set it for a whole folder with a directory data file. if, for example, you keep all markdown files used in the generation of /crafts/index.html
inside the folder /crafts/projects/
, you can create /crafts/projects/projects.json
that looks like {"permalink": false}
.
adding a markdown filter to the eleventy config
you can configure the markdown library eleventy uses to render markdown into html if you want to add some extra features, which will apply anytime the library is used. then you can add your own filter to render a given templating variable. for example, i add markdown-it plugins to set attributes and use definition lists. then i write a filter that would render a variable from markdown into html. these parts of my config file look like this:
const markdownIt = require("markdown-it");
const markdownItAttrs = require("markdown-it-attrs");
const markdownItDeflist = require("markdown-it-deflist");
module.exports = function (eleventyConfig) {
// basic markdown setup
const md = markdownIt({
html: true,
breaks: true,
linkify: true,
typographer: true
}).use(markdownItAttrs)
.use(markdownItDeflist);
eleventyConfig.setLibrary("md", md);
// filters for rendering markdown
eleventyConfig.addFilter("markdownify", (content) => {
return md.render(content);
});
eleventyConfig.addFilter("markdownify-inline", (content) => {
return md.renderInline(content);
});
};
i use the filter markdownify-inline
with my changelog. i store entries in a json file, and i write them in markdown syntax as i find writing links in markdown to be easier than html. here's the relevant part of my changelog template file:
{% for log in logs %}
<section class="box">
<div class="content">
<div class="date">{{ log[0] }}</div>
<div class="message">{{ log[1] | markdownify-inline }}</div>
</div>
</section>
{% endfor %}
if i didn't add markdownify-inline
to {{ log[1] }}
, then links would appear like [title](url). that's because eleventy just assumes it's plaintext; it needs to be explicitly told to render that part of the text from markdown.
the render plugin
another option is to use eleventy's built-in render plugin. a good option if you want to embed a chunk of markdown right into a file like
{% renderTemplate "md" %}
# I am a title
* I am a list
* I am a list
{% endrenderTemplate %}
and have it render as html. there's also options to use it with variables and files. when i tried to use the renderFile filter however, it wouldn't render just the body of the file but also the front matter. if you don't use front matter in your markdown files this is probably a more straightforward method than the next solution.
writing a filter to render a specific markdown file without front matter
on my language page, i prefer keeping the text of each tab as a markdown file on my computer. unlike my crafts page, their locations in the html file aren't generated programmatically; rather i want to tell eleventy which file to add where. while i could use the hack solution i mentioned using collections, i ideally wanted to be able to keep the files in an _includes folder, and i also wanted to have yaml front matter in my files. after longer than i care to admit i figured out how to write a function that takes a file and returns the body without the front matter, converted to html. add this to the top of .eleventy.js
next to the other imported packages:
// necessary npm packages
const fs = require("fs");
const matter = require("gray-matter");
then add the following function inside of module.exports = function (eleventyConfig) {}
// shortcode to include rendered content of a specific input file
eleventyConfig.addShortcode("renderExternalMarkdown", function (filePath) {
// read the file and parse with gray-matter
const fileContent = fs.readFileSync(filePath, "utf8");
const parsedFile = matter(fileContent);
// return the content without the front matter, converted to html
return md.render(parsedFile.content);
});
now on my language page, i can use the following code to render my entry on studying thai:
{% renderExternalMarkdown "language/_includes/thai.md" %}