Walk with an Eleventy site, before you can run.
Tags: webdev, javascript, github
Published: 2022-09-04 10:50:50

Every time I open the documentation website of [Eleventy](http://11ty.dev/), I get this urge to cry, or change career! So I decided to create a small website, that has absolutely nothing, but the bare minimum, and keep it as reference, so next time I consider Eleventy, I don't have to go through their appreciated -yet painful- get started!

The final project is on [StackBlitz](https://stackblitz.com/edit/sekrab-11ty)

The plan is to cover the following:

-   Eleventy Setup
-   Eleventy Folders
    -   `_data` folder
    -   `_includes` folder
-   The front matter
-   Multiple files from a collection
-   Multiple files from an array
-   Liquid specifics
-   Liquid include files
    -   Passing variables
    -   Layout regions
-   Bonus: delete folder first
-   Hosting on GitHub Pages

The bare minimum
----------------

The target is to cover the basics, after which, finding specifics becomes easier in documentation.

-   [Liquid](https://liquidjs.com/), using HTML extensions
-   Install globally only
-   Two configurations, one for dev, and one for publishing
-   Deploy `dist` folder only, no pipelines, no actions
-   Use default `_data` and `_includes`
-   No plugins

I also will deploy on **Github Pages**, to remind myself of how easy it is; their documentation makes you believe it's a military mission!

Eleventy Setup
--------------

1\. Install Eleventy globally

`npm install -g @11ty/eleventy`

2\. Create a folder for the project, and `npm init` for future use. This is helpful in case we want to make it a local installation, or add other packages, or simply save scripts shortcut

3\. Create `.eleventy.js` configuration file, to change the source directory (to keep things tidy), and the only truly valuable setting is `addPassthroughCopy`:

```js
// .eleventy.js
module.exports = function (eleventyConfig) {
  // this is a must, pass through your assets
  eleventyConfig.addPassthroughCopy("src/assets");
	return {
		dir: {
			input: "src"
			// the default output is _site
		}
	}
};
```

4\. Create a `src` folder and add your first HTML

```html
<!DOCTYPE html>
<html>
  <body>
    <h1>Hello World</h1>
  </body>
</html>
```

5\. Run one of the following commands:

-  `eleventy` generates the site under `_site` folder
-  `eleventy --serve` generates the site, watches changes and starts a local server at `localhost:8080`
-  `eleventy --watch` generates the site, watches changes, but does not start a server, it's up to you to start a server that servers `_site` folder
-  `eleventy --config=somethingelse` uses a different configuration file to generate the site.

6\. Create a new file: `.distconfig.js` to build on a different folder

```js
// first get the dev config in
const devConfig = require("./.eleventy.js");

module.exports = function (eleventyConfig) {
  // pass everything from config
  const config = devConfig(eleventyConfig);

  // set different output, you can deep clone first but it's too much work
  return {
    dir: {
      input: config.dir.input,
      output: "dist"
    }
  };
};
```

Run `eleventy --config:.distconfig.js`, the output is in `dist` folder. Deploy *that*!

That's it. This is the bare minimum. But not quite useful is it? Let's add our shortcut scripts in `packages.json` then move on.

```json
// packages.json
{
  "scripts": {
    "start": "eleventy --serve",
    "watch": "eleventy --watch",
    "build": "eleventy --config=.distconfig.js"
  }
}
```

Eleventy folders
----------------

Two folders matter most, inside `src`: `_data` and `_includes`. Contents do not get processed, so they can contain all data, layouts, shortcodes, and plugins.

### `_data` folder

The file name is the **JavaScript object name**, start with `something.json`

```json
// src/_data/something.json
{
  "name": "orange"
}
```

This is used in HTML like this:

```html
<div>
<!-- in html, this outputs: orange -->
{{ something.name }}
</div>
```

If it is an array, `things.json`:

```json
// src/_data/things.json
[
  {
    "name": "orange"
  },
  {
    "name": "purple"
  }
]
```

In HTML:

```html
<div>
<!-- in html, any html, this outputs: orange, purple -->
{{ things[0].name }}, {{ things[1].name }}
</div>
<!-- for loop in liquid -->
<ul>
  {% for thing in things %}
  <li>{{ thing.name }}</li>
  {% endfor %}
</ul>
```

If it is a key-value, `fewthings.json`:

```json
// src/_data/fewthings.json
{
  "orange": "sunny orange",
  "purple": "dark purple"
}
```

In HTML:

```html
<p>
  <!-- this outputs: sunny orange, dark purple -->
  {{ fewthings.orange }}, {{ fewthings.purple }}
</p>
<!-- for loop in liquid, with key-value -->
<ul>
  {% for thing in fewthings %}
  <!-- first element is key, second is value -->
  <li>{{ thing[0] }}: {{ thing[1] }}</li>
  {% endfor %}
</ul>
```

### `_Includes` folder

`_includes` is where templates are created. Begin with a base html template: `base.html`. Notice we did not have to create any special extensions this far. The only template variable that works out of the box is `{{content}}`. In HTML extension, the `safe` filter is not needed, nor will it work.

```html
<!-- src/_includes/base.html -->
<!DOCTYPE html>
<html>
  <head>
    <!-- title is a variable that needs to be set in child page -->
    <title>{{ title }} | Base 11ty Template</title>
  </head>
  <body>
    <h1>Hello 11ty</h1>
    <!-- here is the template literal that passes the content  -->
    {{ content }}
  </body>
</html>
```

The front matter
----------------

Let's create our first file, with the front matter, the bare minimum:

```html
<!-- src/index.html -->
---
# must be directly under _includes folder
layout: "base.html"
# title is optional
title: "a new title"
---

<h2>The base page using base html</h2>
```

To include the content of another file:

`{% include './filelocation/file.html' %}`

To create a sub layout, it's just as easy as creating the first layout, all data is fed upwards:

```html
<!-- src/_includes/color.html -->
---
layout: base.html
---
<div>
<!-- the color variable is fed from pages using this template -->
	Here is the details of {{ color }}
</div>
{{ content }}
```

Outside, let's create a folder `colors` and a couple of html files:

```html
<!-- src/colors/orange.html-->
---
# use child layout
layout: color.html
# pass color to first layout
color: orange
# pass title to parent layout
title: Orange
---

<h1>{{ color }} page</h1>
```

Multiple files from a `tags` using `collections`
------------------------------------------------

A `collection` in Eleventy is a group of similar pages, the similarity is based on the `tags` property. This allows us to list the similar pages, each `item` has its own `data` property.

```html
<!-- src/colors/orange.html, similar purple.html -->
---
layout: color.html
color: orange
title: Orange
tags: colors
---

<h1>{{ color }} page</h1>
```

Listing them anywhere, here they are listed in `color.html` layout

```html
<!-- src/_includes/color.html -->
<h5>List of all colors</h5>
<ul>
	<!-- 'collections' is provided by 11ty,
		'colors' is the tags property,
		'data' is provided by 11ty, -->
  {% for item in collections.colors %}
	  <li>{{ item.data.color }}</li>
  {% endfor %}
</ul>
```

### Eleventy provided data

We can use `page.url` to compare to `collections[item].url` to single out the current item in a list

```html
<!-- src/_includes/color.html -->
<ul>
  {% for item in collections.colors %}
	<!-- inline if condition for page.url and item.url, both provided by 11ty -->
  <li {% if page.url == item.url %}class="selected"{% endif %}>
    <a href="{{item.url}}">{{ item.data.color }}</a>
  </li>
  {% endfor %}
</ul>
```

This is how you list pages with the same `tags`, but it is too fragmented. A better way is to let Eleventy loop through a data `array`, and create pages accordingly.

Multiple files from an array using `pagination`
-----------------------------------------------

To generate multiple HTML files from an array in `_data` folder, for example, `things.json`:

```html
<!-- src/loop.html, use front matter to create multiple files -->
---
layout: "base.html"
pagination:
  data: things
  # size 1 to create a page per item
  size: 1
  # alias it to use its props
  alias: thing
---

{{ thing.name }}
```

This generates

`/_site/loop/index.html` (0 index is removed from URL)

`/_site/loop/1/index.html`

To change the file names to proper names, we use `permalink`, notice the `/` characters, it generates a subfolder for `index.html`, and thus a friendly URL.

```html
<!-- src/loop.html -->
---
layout: "base.html"
pagination:
  data: things
  size: 1
  alias: thing
# use permalink to fix the file names, use slugify filter to make name slug-ish
permalink: "loop/{{ thing.name | slugify }}/"
---

{{ thing.name }}
```

This will generate

`/_site/loop/orange/index.html`

`/_site/loop/purple/index.html`

If the name was `Liberty Statue`, the `slugify` filter would create: `liberty-statue`

Great. Now to **pass back the props to the base template**, we use `eleventyComputed` in front matter, notice the format of the property value:

```html
<!-- src/loop.html -->
---
layout: "base.html"
pagination:
  data: things
  size: 1
  alias: thing
permalink: "loop/{{ thing.name | slug }}/"
# pass back the title to base template: use quotations
eleventyComputed:
  title: "{{thing.name}}"
---

{{ thing.name }}
```

Listing them is as simple as looping through the array, unfortunately mapping the current page to one of the items is a little bit of work. Eleventy provides a `pagination` object that is available in the pages that use it, one property is `hrefs`, which we can use to compare to `page.url`. `forloop.index0` is Liquid syntax for item index in a zero-based array.

```html
<ul>
  {% for thing in things %}
    <!-- pagination.hrefs is provided by 11ty
         forloop.index0 is liquid syntax for zero-based index -->
    <li {% if page.url == pagination.hrefs[forloop.index0] %}class="selected"{% endif %}>
      <a href="/loop/{{thing.name | slugify }}">{{ thing.name }}</a>
    </li>
  {% endfor %}
</ul>
```

Liquid specifics
----------------

The following are very handy in Liquid JS:

-   to assign a variable name and use it

`{% assign something: 'pretty' %}` → `{{ something }}`

-   to open a liquid region with multiple lines

```
{% liquid
	assign var = value | filter
	# other liquid statements
%}
```

Liquid include files
--------------------

More often than not, I want to combine two features: centralizing the array under `_data`, and having the freedom of writing HTML. For that, we use Liquid include files.

To include another file as it is, another HTML file, let me begin with an example, having HTML data files in `_data` folder:

```html
<!-- /_data/things/orange.html, and a similar one purple.html -->
<h4>Orange</h4>
<p>
  This is a rich HTML content file, that has access to liquid template variables
</p>
```

In a `loop` file, make an include:

```html
<!-- src/loop.html -->
<!-- include _data/thingname.html as it is -->
{% include './_data/things/{{thing.name | slugify }}.html' %}
```

Note: we must create the files manually and make sure the name of the file is equal to the output of `slugify` filter. But the better way is to create a new property in our `things.json` with `slug` and use it everywhere instead.

### Passing variables

We can pass variables and use them in HTML in Liquid:

```html
<!-- src/loop.html -->
---
layout: "base.html"
pagination:
  data: things
  size: 1
  alias: thing
permalink: "loop/{{ thing.name | slugify }}/"
eleventyComputed:
  title: "{{thing.name}}"
---
<!-- pass a property -->
{% include './_data/things/{{thing.name | slugify }}.html', theme: 'dark' %}
```

Then use it:

```html
<!-- src/_data/things/orange.html -->
<h4>Orange</h4>
<p>
  {{ theme }}
</p>
```

### Liquid layout regions

There is no support for Liquid layout blocks, there is however support for template variables. We can create a conditional in our HTML pages:

```html
<!-- src/_data/things/orange.html -->
{% case region %}
  {% when "header" %}
     Header of orange
  {% when "content" %}
     The content region of orange, and the theme is {{ theme }}
{% endcase %}
```

And use it in our loop

```html
<!-- src/loop.html -->
---
layout: "base.html"
pagination:
  data: things
  size: 1
  alias: thing
permalink: "loop/{{ thing.name | slugify }}/"
eleventyComputed:
  title: "{{thing.name}}"
---
<!-- include regions -->
<h4>{{ thing.name }}</h4>
<!-- liquid island to assign a _filepath variable -->
{% liquid
  assign _slug = thing.name | slugify
	assign _filepath = './_data/things/' | append: _slug  | append: '.html'
%}
<article>
  <header>
    {% include _filepath', region: 'header' %}
  </header>
  <div>
		<!-- passing extra params are okay -->
    {% include _filepath, region: 'content', theme: 'dark' %}
  </div>
</article>
```

I just need to remind myself that this can be taken a bit further, by making extra regions to be used elsewhere. For example, if I had an `excerpt` region, I can display it on the home page.

```html
<!-- src/_data/things/orange.html -->
{% case region %}
	{% when "excerpt" %}
		Short excerpt of Orange
  {% when "header" %}
     Header of orange
  {% when "content" %}
     The content region of orange, and the theme is {{ theme }}
{% endcase %}
```

In the loop:

```html
{% for thing in things %}

  <li>
    ...
		add excerpt region
    <div>
    {% include './_data/things/{{thing.name | slugify }}.html', region: 'excerpt' %}
    </div>
  </li>
{% endfor %}
```

So this was a *Getting Started*. Deeper stuff is easier to find once we learn how to walk. Some of the things to look for:

-   Eleventy **configuration**, specifically to change to **Nunjucks**
-   **Nunjucks templates** and extended templates, and template regions
-   Eleventy **Data**, fed from layouts
-   Eleventy shortcodes, filters and plugins

Bonus: delete folders first
---------------------------

A bonus, is to delete the `_site` and `dist` folder before starting and building, you might find other solutions on the web, but here is the simple bare minimum, in the config files:

```js
// .eleventy.js
// get fs from Node
const fs = require('fs');

module.exports = function (eleventyConfig) {

	// rm sync _site folder, recursively
	fs.rmSync('./_site', {recursive: true, force: true});

	// ...
};

// .distconfig.js
const fs = require('fs');

module.exports = function (eleventyConfig) {

	// rm sync dist folder
	fs.rmSync('./dist', {recursive: true, force: true});

	// remember: this will also remove _site
	const config = devConfig(eleventyConfig);

  // ...
};
```

Let's put it on GitHub Pages

Hosting on GitHub Pages
-----------------------

1.  Build the project: `npm run build`
2.  Push the project to a GitHub repository (there are a number of ways to do that, none of them is straight forward, if you have a visual interface like VSCode, it makes things a tad bit easier). Ignore `_site` folder, but allow `dist` folder. There is no `node_modules` folder in the bare minimum setup.
3.  In GitHub repository, go to **Settings > Pages**
4.  Under **Build and Deploy**, choose **Github Actions**. This will take you to newly created page `.github/workflows/pages.yml`
5.  Scroll down to line 39 (approximate): `jobs>deploy>steps>with>path`, and change it to `./dist`
6.  Commit

You probably want to pull this locally for future commits.

Give it 10 minutes, or a little more, when you go back to **Settings > Pages**, the new URL will be displayed on top. It most probably going to be

`https://[username].github.io/[repositoryname]`

If you setup a custom domain, it would be

`https://[customdomain]/[repositoryname]`

If your repository name is `[username].github.io`, then the URL would be

`https://[customedomain]`

Note: you only need to set up a custom domain for the `[username].github.io` just once, all new repos can be served off of your custom domain automatically.

For custom domain or subdomain, in your DNS manager, setup:

```
CNAME record:
- name: www [or subdomain],
- value: [username].github.io
```

To add apex domain (`www`), also add:

```
A Record:
- name: @ (or domain.com)
- value: 185.199.108.153
```

So here is our project on [GitHub](https://github.com/ayyash/sekrab-11ty/), and hosted on [Pages](https://www.ayyash.tech/sekrab-11ty/).

Eleventy, demystified.

### RESOURCES

-   [Eleventy Documentation](https://www.11ty.dev/docs/)
-   [LiquidJs](https://liquidjs.com/tags/overview.html)
-   [GitHub Pages](https://pages.github.com/)

{% embed https://garage.sekrab.com/posts/walk-with-an-eleventy-site-before-you-can-run %}