How I Use Eleventy
This website is based on the static site generator Eleventy, the template language Liquid, and the markup language Markdown.
Contents
Working on the Website
Before making changes to the website’s layout and content, start Eleventy and the development server:
npx @11ty/eleventy --ignore-initial --incremental --serve
Option
--ignore-initial
prevents the initial build of the whole website.Option
--incremental
builds only pages that need to be rebuilt because of changes to the page or the layout. Incremental builds require declaration of collections used by certain layouts or pages.
Publishing the Website
When the changes (e. g., adding new pages, editing pages, modifying the layout) are done, call a shell script publish.sh
to upload the changed pages to the web server using FTP:
#!/usr/bin/env bash
USER='<user name>'
PASSWORD='<password>'
HOST='example.org'
LOCAL_DIR="$(pwd)/_site/" # End slash to prevent creation of dir on server.
REMOTE_DIR='/'
NUM_PARALLEL_CONNS=8
# lftp's `mirror` command supports an option `--delete` to delete files that
# are not present in the source. However, as this project does not have its
# designated directory on the server, this option is not used for safety
# reasons.
echo "Starting upload at $(date)."
lftp -u "$USER","$PASSWORD" $HOST <<EOF
#set ftp:ssl-allow true
#set ftp:ssl-force true
#set ftp:ssl-protect-data true
#set ssl:verify-certificate no
mirror --parallel=$NUM_PARALLEL_CONNS --verbose -R "$LOCAL_DIR" "$REMOTE_DIR";
exit
EOF
echo "Upload finished at $(date)."
The FTP client LFTP will only upload files that do not yet exist on the web server or are newer than the files on the web server. It can be installed on Linux Mint:
sudo apt install lftp
The correct file permissions are important for this approach to work, as lftp
will retain the permissions of the transferred files. Read permissions have to be set for other so the uploaded files can be accessed via HTTP.
Theme Development
Automatic Figures for Fenced Code Blocks
Modify markdown-it’s fenced code renderer to enclose the rendered pre
element in a figure
element:
import markdownIt from 'markdown-it';
export default function (eleventyConfig) {
…
const mdSetup = markdownIt('commonmark', { html: true });
const proxy = (tokens, idx, options, env, self) =>
self.renderToken(tokens, idx, options)
;
const defaultFenceRenderer = mdSetup.renderer.rules.fence || proxy;
mdSetup.renderer.rules.fence = (tokens, idx, options, env, self) => {
return `<figure class="listing">
${defaultFenceRenderer(tokens, idx, options, env, self)}
</figure>\n`;
};
…
}
Preserve Text Formatting in TOC Entries
The markdown-it plugin markdown-it-table-of-contents strips Markdown formatting from table of contents entries by default. Text formatting can be preserved by providing a custom implementation for getTokensText
that renders the entry’s tokens:
import markdownIt from 'markdown-it';
import markdownItAnchor from 'markdown-it-anchor';
import markdownItTOC from 'markdown-it-table-of-contents';
export default function (eleventyConfig) {
…
eleventyConfig.amendLibrary('md', (mdLib) =>
mdLib.use(
markdownItTOC,
{
includeLevel: [2, 3],
transformContainerOpen: () => {
return '<h2 id="toc"><a href="#toc">Table of Contents</a></h2>';
},
transformContainerClose: () => { return ''; },
// Allow markdown formatting in TOC entries. The default implementation of
// `getTokensText` removes any formatting.
getTokensText: (tokens) => { return mdLib.renderer.render(tokens); },
}
)
);
…
}
Custom Containers in Markdown with Caption
The markdown-it plugin @mdit/plugin-container can be used to generate custom containers, such as figures, listings, and blockquotes. The example below creates a source code listing with a caption:
This is a paragraph.
:::listing This is a _very_ important code sample.
```
int main()
{
return 42;
}
```
:::
This is a paragraph.
This markup is rendered to the following HTML:
<p>This is a paragraph.</p>
<figure class="listing">
<pre><code>int main()
{
return 42;
}</code></pre>
<figcaption>This is a <em>very</em> important code sample.</figcaption>
</figure>
<p>This is a paragraph.</p>
The implementation renders figure
elements with a figcaption
element at the bottom of the figure. Unfortunately, the close renderer does not provide access to the caption available to the open renderer. Additionally, custom containers can be nested. Therefore we need to push the caption on a stack in the open renderer and pop it in the close renderer:
import markdownIt from 'markdown-it';
import { container } from '@mdit/plugin-container';
export default function (eleventyConfig) {
…
let figureCaptions = [];
const figureOpenRender = (tokens, index, _options) => {
const info = tokens[index].info.trim();
const separatorPos = info.indexOf(' ');
let figureType = '', figureCaption = '';
if (separatorPos != -1) {
figureType = info.substring(0, separatorPos).trim();
figureCaption = info.substring(separatorPos + 1).trim();
} else {
figureType = info.trim();
}
figureCaptions.push(figureCaption);
return `<figure class="${figureType}">`;
};
const figureCloseRender = (tokens, index, _options) => {
const figureCaption = figureCaptions.pop();
const figcaption =
figureCaption ?
`<figcaption>${mdSetup.renderInline(figureCaption)}</figcaption>` : ''
;
return `${figcaption}</figure>`;
};
eleventyConfig.amendLibrary('md', (mdLib) => mdLib.use(container, {
name: 'listing',
openRender: figureOpenRender,
closeRender: figureCloseRender,
}));
eleventyConfig.amendLibrary('md', (mdLib) => mdLib.use(container, {
name: 'figure',
openRender: figureOpenRender,
closeRender: figureCloseRender,
}));
…
}
Create a List of Direct Child Pages
This website uses index pages that provide a list of direct child pages. To do so, it makes use of eleventyNavigation
together with key
–parent
relations set up in the front matter. The Liquid code below compiles the list of child pages. Title and description are taken from each page’s front matter, the filter md
renders the Markdown markup to HTML:
{%- assign navPages = collections.all | eleventyNavigation: eleventyNavigation.key -%}
{%- if navPages.size > 0 %}
<dl class="index">
{%- for child in navPages %}
<dt><a href="{{ child.url | url }}">{{ child.data.title | md }}</a></dt>
<dd><p>{{ child.data.description | md }}</p></dd>
{%- endfor %}
</dl>
{% endif -%}
The listing below shows a solution based on a WebC component that renders a list of the current page’s subpages. The file subpages-index.webc
has to be put into the _components
directory which is located in the project’s root directory:
<script webc:setup>
const currentKey = getCollectionItem(collections.all).data.eleventyNavigation.key;
const subpages = eleventyNavigation(collections.all, currentKey);
</script>
<dl webc:if="subpages.length > 0">
<x webc:for="page of subpages" webc:nokeep>
<dt><a :href="url(page.url)" @html="md(page.data.title)"></a></dt>
<dd><p @html="md(page.data.description)"></p></dt>
</x>
</dl>
The eleventyNavigation
front matter data entry is shadowed by the eleventyNavigation
filter, therefore the current page is retrieved from the collection to gain access to the front matter data. In order to render the dt
element together with the dd
element for each subpage, they have to be enclosed by an arbitrary placeholder element that holds the webc:for
attribute.
The subpages-index
element can be added to a layout or template:
<subpages-index></subpages-index>
How To Write Markdown Code
Markdown does not support commenting out lines or adding comments. However, lines surrounded by a certain character sequence consisting of invalid hyperlink markup will not be rendered by markdown-it and likely other Markdown renderers:
[//]: # (This is a comment.)
Thin spaces can be entered as HTML character entity
 
and non-breaking spaces as
to make them distinguishable from normal spaces in text editors. The same applies to−
to denote a minus sign (»−«) that can be easily confused with the en dash (»–«).