Eleventy in eleven minutes
Tags: eleventy, thingsivelearned, blazingfast, staticsitegenerator
Published: 2021-02-05 09:34:27

*Image by [11ty.dev](https://www.11ty.dev/)*

I would like to share my opinionated path how I got started using.

# Table of Contents

- [What is Eleventy?](#what-is-eleventy)
- [Why Eleventy?](#why-eleventy)
- [Create a new project](#new-project)
- [Adding content](#adding-content)
- [Running and building](#running-building)
- [HTML templates](#html-templates)
- [Providing data for your site](#providing-data)
- [Collections](#collections)
- [Filters](#filters)
- [Plugins](#plugins)
- [Adding toolchains for CSS and JS](#tooling)

# What is Eleventy? <a name="what-is-eleventy"></a>

[Eleventy](https://11ty.dev) is a popular static site generator. It creates a static site from your input files. The input files eleventy looks for are content files, html template files and data files which will be covered in this article. 

It supports several data file formats, content file formats and html template engines which you can use together. In this article, I'm using [Markdown](https://guides.github.com/features/mastering-markdown/) files together with [Nunjucks](https://mozilla.github.io/nunjucks/) templates.

# Why Eleventy? <a name="why-eleventy"></a>

There are many static site generators out there and you may wonder what benefits it offers compared to others. The key points which make me really love Eleventy are:

- it is built on node.js
- it does one job and does that well: create markup from content plus templates
- it is completely unopinionated about client-side JS+CSS toolchains: bring your own
- no angular/react/vue knowledge necessary. 
- html first approach which makes it great for progressive enhancement.
- easily extendible and combinable with npm packages
- similar to jekyll, but with node.js as a base
- easy to mock data via the data folder.

# Create a new project <a name="new-project"></a>

```sh
mkdir my-awesome-site
cd my-awesome-site
git config --global init.defaultBranch main
npm init -y
npm i @11ty/eleventy -D
```

## Initialize your repository <a name="init-repo"></a>

Additionally, run `git init` and provide a `.gitignore` if you are working with git:

```sh
node_modules
.DS_Store
Thumbs.db

# Eleventy output folder
public
```

## Configuration <a name="config"></a>

Configuration is done via a single javascript file named `.eleventy.js`. Providing a configuration is optional.

My personal preference is to provide `src` as input folder and  `public` as output folder. Additionally, I use to specify folders that are copied over to the output folder on build. These are also automatically watched by eleventy when starting the development server.

```sh
module.exports = (config) => {
  // specify folders to be copied to the output folder
  config.addPassthroughCopy('./src/js/');
  config.addPassthroughCopy('./src/css/');

  return {
    markdownTemplateEngine: 'njk',
    htmlTemplateEngine: 'njk',
    dir: {
      input: 'src',    // default: '.'
      output: 'public' // default: '_site'
    }
  }
};
```

# Adding content <a name="adding-content"></a>

Create a markdown file and name it like this: `src/index.md`

```md
---
title: "Hello world"
---
# Hello World

Welcome to my awesome {{title}} site! 
```

## Markdown and front matter <a name="markdown-front-matter"></a>

Markdown files can optionally provide a metadata block, which is marked via three hyphens `---` in the beginning and the end of the block. In this block, you can specify meta data in [YAML](https://yaml.org/) notation.

## From content to paths <a name="content-paths"></a>

For each markdown content file, eleventy creates a folder with an `index.html` for nice urls:

```md
index.md            --> /
about.md            --> /about/
faq.md              --> /faq/
blog/hello-world.md --> /blog/hello-world/
```

# Running and building <a name="running-building"></a>

Finally, we can start adding the start and build tasks to our `package.json` file:

```js
{
  "scripts": {
    "start": "eleventy --serve",
    "build": "eleventy"
  }
}
```

- `npm start` -> start Development server
- `npm run build` -> build your website
- `npx serve public` -> test your build

The development server is using `browsersync` which automatically keeps track of changes and updates your DOM. Additionally, if you open the page in multiple browsers, events are kept in sync, which is useful for cross-browser-testing.


# HTML templates <a name="html-templates"></a>

To define your HTML structure and layouts, you can use HTML templates. There are several template engines available in Eleventy. In this example, I'm using the [Nunjucks](https://mozilla.github.io/nunjucks/) template engine.

Other template formats supported are `.html`, `.liquid`, `.hbs`, `.ejs`, `.haml`, `.pug`.

A general approach is to build a base html file and then build 
several other structures as you need based upon it.

Add a `src/_includes/base.njk` file

```html
<!DOCTYPE html>
<html lang="en">
  <head><title>{{ title }}</title></head>
  <body>
    <main>
      {% block main %}
        {{ content | safe }}
      {% endblock %}
    </main>
  </body>
</html>
```

Via the `{% block %}{% endblock %}` syntax, you can add several slots into your template which you can use when extending your template. 

The `content` variable is a reserved variable which contains the content body of the current content file. 

The `| safe` directive is a builtin filter which tells the template engine that the content you want to insert is safe. This way, HTML tags are not converted to plain text containing [HTML entities](https://dev.w3.org/html5/html-author/charref). This allows using html inside your content.

# Using your templates in your content files <a name="#using-templates"></a>

In the front matter of your markdown file, specify the layout you want to use:

```
layout: base
```

## Extending your templates <a name="#extending-templates"></a>

Next to your `base.njk` file, create an `article.njk` file:

```html
{% extends "base.njk" %}

{% block main %}
  <article class="article">
    {{ content | safe }}
  </article>
{% endblock %}
```

Nunjucks also has a section about inheritance in the documentation: https://mozilla.github.io/nunjucks/templating.html#template-inheritance

## Includes <a name="includes"></a>

You can include partial layouts anywhere in your njk or markdown files:

```html
{% include "header.njk" %}
```

# Providing data for your site <a name="providing-data"></a>

There are several ways to provide data that can be used from inside your templates or content files: 

- file specific: in the markdown's front matter 
- folder specific: add a json file to a content folder
- globally `_data` directory: globally available
- `_data` supports `.js`, `.yaml`, `.json` files

## `_data` example

Imagine you would like to build a navigation and provide all the url entries from a JSON file:

`src/_data/nav.json`
```js
[
  {"title": "Home", "url": "/"},
  {"title": "Blog", "url": "/blog/"}
]
```

Then, you can create a partial html snippet to include in your main template, eg. `src/_includes/nav.njk`

```html
<nav>
  <ul>
    {% for link in nav %}
      <li>
        <a href="{{ link.url }}">{{ link.title }}</a>
      </li>
    {% endfor %}
  </ul>
</nav>
```

## `_data` javascript example

`_data/site.js`

```js
module.exports = {
  name: 'My awesome site',
  url: 'https://awesome.site/'
};
```

Can be used like this in the content: 
`{{ site.name }}`

Having a `.js` file instead of a plain json or yaml file brings the flexibility to use node.js environment variables (like, checking if you're in the development or production environment). Also, you can do API fetches from there to pull in a headless CMS, for example.

# Collections <a name="collections"></a>

You can tag your content with a keyword and then iterate through these via collections.

This is useful for auto-generating table of contents or listing articles that are related to each other

## Collections example

In your src folder, add a `blog` folder with a bunch of markdown files. Tag them as `posts` in your front matter:

```md
tags: posts
```

Then, in your markdown or include files, you can iterate through these collection via a for loop:

`index.md`:
```md
# Blog

<ul>{% for post in collections.posts %}<li>
  <a href="{{ post.url }}">
    {{ post.date | date('YYYY-MM-DD') }}: {{ post.data.title }}
  </a>
</li>{% endfor %}</ul>
```

# Filters <a name="filters"></a>

Filters provide a way to further process your content. You can use these filters from inside your content and template files by using the pipe. 

## Adding a custom filter

In your `.eleventy.js` file, you can add several filters you can use inside your file. You can also use third party libraries here. This is an example for a scream filter and a date formatting filter:

```js
const moment = require('moment');

module.exports = (config) => {
  config.addFilter('date', (date, format) => moment(date).format(format || 'YYYY-MM-DD'));
  config.addFilter('scream', (str) => str.toUpperCase());
  // ...additional config 
  return { ... }
};
```

Then, you can use this filter in your content and template files like this: 

```
{{ content | scream | safe }}`
{{ page.date | date('YYYY-MM-DD') }}
```

## Processing include files with a filter

If you would like to process an include with a filter, you can use the nunjucks `set` directive to store includes into a variable. In my personal site, I've used this technique to minify WebGL shader code on the fly:

```html
{% set vertexShader %}
{% include 'shaders/vertex-shader.vs' %}
{% endset %}

{{ vertexShader | glslminify | safe }}
```

## Built-in filters

- you can use all [built-in nunjucks filters](https://mozilla.github.io/nunjucks/templating.html#builtin-filters)
- [`safe`](https://mozilla.github.io/nunjucks/templating.html#safe) – the content is safe to insert, so html specific characters are not converted to html entities (use this to inject html and scripts). 
- [`url`](https://www.11ty.dev/docs/filters/url/) – specify a prefix path (useful for deployment into a subdirectory, eg. on github pages).
- [`slug`](https://www.11ty.dev/docs/filters/slug) – convert a string to an url-friendly slug (eg `My site` to `my-site`)
- [`get*CollectionItem`](https://www.11ty.dev/docs/filters/collection-items/) – get next or previous item in collection

# Plugins and tools <a name="plugins"></a>

Eleventy provides a rich plugin ecosystem where you can add further magic✨ to your workflow 🙌. 

Check out the [Eleventy plugins documentation](https://www.11ty.dev/docs/plugins/)

# Adding toolchains for CSS and JS <a name="tooling"></a>

In the article, we used a pass-through-copy command and used CSS and JS without any bundling or further processing. My favorite approach is to use a CSS preprocessor plus ES module JavaScript files. These are not supported in legacy browsers such as IE11. When using progressive enhancement, JavaScript is not required to read
your content. 

In the following, I will demonstrate the approach I used (only using a CSS transpiles) and a complete JS+CSS toolchain using parcel as an alternative approach. 

## CSS transpiler only

In my personal project, I used the sass together with concurrently, to start two processes concurrently running in my `npm start` script.  

```sh
npm i sass concurrently -D
```

To build the CSS, I'm running `sass src/scss:src/css` which compiles every .scss to CSS:

```json
{
  "scripts": {
    "start": "concurrently 'npm:watch-css' 'npm:serve-11ty'",  
    "build-11ty": "eleventy",
    "serve-11ty": "eleventy --serve",
    "build-css": "sass src/scss/:src/css/",
    "watch-css": "sass src/scss/:src/css/ --watch",
    "build": "npm run build-css -s && npm run build-11ty -s"
  }
}
```

## Or complete Javascript+CSS toolchain.

If you would like to have a complete frontend toolchain taking care of compiling JavaScript and CSS, one way to do is is to use [Parcel](https://parceljs.org/getting_started.html). 

```sh
npm i parcel-bundler concurrently -D
echo src/dist >> .gitignore
echo .cache >> .gitignore
```

For the development mode, I'm also using `concurrently` to start eleventy and parcel in parallel:

```json
{
  "scripts": {
    "start": "concurrently 'npm:watch-bundle' 'npm:serve-11ty'",
    "build": "npm run build-bundle -s && npm run build-11ty -s",
    "watch-bundle": "parcel watch src/app/index.js -d src/dist",
    "build-bundle": "parcel build src/app/index.js -d src/dist",
    "serve-11ty": "eleventy --serve",
    "build-11ty": "eleventy"
  }
}
``` 

In `src/app`, put an `index.js` file. Additionally, put any CSS import of your choice into it:

```js
import './scss/styles.scss';

console.log('Hello world');
```

Finally, in your eleventy config, change the pass through copy to copy the parcel output into your eleventy output folder:

```js
config.addPassthroughCopy('./src/dist/');
```

Then, parcel creates an index.js and index.css in the dist folder, which you can use in your html templates like this:

```html
<!-- in your head tag --> 
<link rel="stylesheet" href="/dist/index.css">

<!-- right before your closing </body> tag -->
<script src="/dist/index.js"></script>
```


# Example project

- [https://github.com/terabaud/lea-codes/](https://github.com/terabaud/lea-codes/) 

# Resources
- [https://terabaud.github.io/eleventy-talk/](https://terabaud.github.io/eleventy-talk/) - My slides for a talk about 11ty (useful as a summary of this article)
- [https://11ty.dev/](https://11ty.dev/) - Official site
- [https://11ty.rocks/](https://11ty.rocks/) - Eleventy rocks
- [https://piccalil.li/course/learn-eleventy-from-scratch](https://piccalil.li/course/learn-eleventy-from-scratch) - in-depth course

# Thank you 👩‍💻