静的サイトジェネレーター 11ty/eleventy をもっと使ってみる

## はじめに
前回の記事で [11ty/eleventy](https://www.11ty.dev/)（以下、11ty） の公式スタートガイドに則ってサンプルを作成してみました。

[HTML テンプレートエンジン詰め合わせ 11ty/eleventy を使ってみる](https://qiita.com/yuzuru_xa/items/5a55b28707501181ed6f)

上記の記事で紹介した内容は、スタートガイドによるファイル生成と front-matter を使用した機能のごく一部です。11ty の機能をさらに詳しく確認していくことが本記事の目的になります。

## 記事の概要

公式の [スタータープロジェクト](https://www.11ty.dev/docs/starter/) として、[eleventy-base-blog](https://github.com/11ty/eleventy-base-blog) というリポジトリが公開されています。
[サンプルページはこちら。](https://eleventy-base-blog.netlify.com/)

11ty を使用してブログコンテンツを含む静的サイトを構築する、というテンプレートページになっていますので、このページのソースを眺めつつ 11ty でできることを確認していきます。

前回記事で記述していますが、 **11ty には CSS や JS ファイル周りのコンパイル/トランスパイルは含まれておらず、今回取り扱う eleventy-base-blog に関しても CSS, JS のコンパイル/トランスパイル環境は含まれていません。**
~~CSS, JS の環境構築については後ほど別記事で記載予定です。~~

**2020.04.18
[11ty/eleventy で CSS / SCSS を扱うために](https://qiita.com/yuzuru_xa/items/5f19aa9815cdfa1e2c3c)
を追加しました。**


## ディレクトリ構成

HTML ファイルを生成する `.njk` ファイルと `.md` ファイル、`.json` ファイルを中心にファイルを確認します。

>
eleventy-base-blog/
├ _site/
├ package.json
├ .eleventy.js
├ _data/
｜　　└ metadata.json
├ _includes/
｜　　└ layouts/
｜　　　　└ base.njk
｜　　　　└ home.njk
｜　　　　└ post.njk
｜　　└ postslist.njk
├ index.njk
├ sample.njk （記事の都合上追加）
├ page-list.njk
├ archive.njk
├ tags.njk
├ tags-list.njk
├ 404.md
├ about/
｜　　└ index.md
├ posts/
｜　　└ posts.json
｜　　└ firstpost.md
｜　　└ secondpost.md
｜　　└ thirdpost.md
｜　　└ fourthpost.md
├ feed/
｜　　└ feed.njk
｜　　└ htaccess.njk


これらのファイルがコンパイルされることで、`_site/` 以下に HTML ファイルが生成されます。`_site/` の中身に関してはリストでは省略しています。
`_includes/` 以下に保管したファイルは、コンパイル時に `_site/` に HTML ファイルが出力されません。

特に設定がなければ、`index.njk` は `index.html` として、
特定の名前のついたファイル（ex.`archive.njk`）は**ディレクトリ名をその名前とした `index.html` ファイルとして**（ex.`archive/index.html`）出力されます。
出力される HTML のファイル名は、のちに述べる **permalink** の設定によって自由に変更することが出来ます。

プロジェクトルート直下の `.eleventy.json` は 11ty の動作に関する設定ファイルで、11ty の動作や入出力ディレクトリなどをカスタマイズしたい場合は主にこのファイルを触っていくことになります。

また、eleventy-base-blog のリポジトリには元々存在しませんが、説明の便宜上 `sample.njk` を生成しています。こちらは本記事の説明の都合上追加したファイルですので、都度内容の変化するサンプルコードとして読んでいただければ幸いです。

## 基本事項: YAML front-matterの参照
まず 11ty の基本的な使い方ですが、各ファイルに **front-matter** という記法で、変数やテンプレートファイルの読み込みを設定します。
front-matter は `---` で区切った **YAML 形式**で記述し、front-matter に記述した値は `.njk` ファイルであれば、マスタッシュ記法 `{{}}` にて参照することが可能です。

```html:sample.njk
---
pageTitle: Hello 11ty
txt: Hi
---
<!doctype html>
<html>
  <head>
    <title>{{ pageTitle }}</title>
  </head>
  <body>
    <p>{{ txt }}</p>
  </body>
</html>
```

このファイルは以下のように出力されます。

```html:_site/sample/index.html
<!doctype html>
<html>
  <head>
    <title>Hello 11ty</title>
  </head>
  <body>
    <p>Hi</p>
  </body>
</html>
```

## eleventy-base-blog のトップページを眺める

まずはトップページである `_site/index.html` を生成するファイルを確認します。
eleventy-base-blog のトップページは、以下キャプチャのようにブラウザで出力されます。

![スクリーンショット 2020-03-22 16.22.54（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390806/5951a47b-7923-56cd-1153-dcc487fe459d.png)

このトップページは以下のファイルから構成されています。

- base.njk
- home.njk
- index.njk
- postslist.njk

ページの中核を構成するのは `index.njk` で、このファイルからテンプレートである `base.njk`, `home.njk`、インクルードファイルとして `postslist.njk` を読み込む、という形になっています。
ブラウザ上の構成は以下キャプチャのようになります。

![スクリーンショット-2020-03-22-16.22.54（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390806/8dd44fc0-479e-8df8-c612-a095e5977cba.png)

## 11ty によるテンプレート継承

`layout` をキーとして`_includes/` 内のファイルを指定することで、テンプレート継承を行うことができます。
eleventy-base-blog の index.html においては `_include/layouts/home.njk` をまずテンプレートとして指定しています。

```html:index.html
---
layout: layouts/home.njk
eleventyNavigation:
  key: Home
  order: 1
---
<h1>Latest 3 Posts</h1>

{% set postslist = collections.posts | head(-3) %}
{% set postslistCounter = collections.posts | length %}
{% include "postslist.njk" %}

<p>More posts can be found in <a href="{{ '/posts/' | url }}">the archive</a>.</p>
```

`index/html` のテンプレートとして指定されている `home.njk` の中身は

```html:home.njk
---
layout: layouts/base.njk
templateClass: tmpl-home
---
{{ content | safe }}
```

となっており、更に `base.njk` をレイアウトとして持ちます。
front-matter に記載されている `templateClass: tmpl-home` は `base.njk` に渡り、`<main>`タグのクラス名条件分岐に使用されているようです。

```html:base.njk
<!-- mainタグ部分 -->
<main{% if templateClass %} class="{{ templateClass }}"{% endif %}>
  <div class="warning">
    <ol>
      <li>Edit the <code>_data/metadata.json</code> with your blog’s information.</li>
      <li>(Optional) Edit <code>.eleventy.js</code> with your <a href="https://www.11ty.dev/docs/config/">configuration preferences</a>.</li>
      <li>Delete this message from <code>_includes/layouts/base.njk</code>.</li>
    </ol>
    <p>
      <em>This is an <a href="https://www.11ty.io/">Eleventy project</a> created from the <a href="https://github.com/11ty/eleventy-base-blog">
          <code>eleventy-base-blog</code> repo</a>.</em>
    </p>
  </div>

  {{ content | safe }}
</main>
```

11ty では、このように入れ子構造でのテンプレート継承が可能です。
また `base.njk` に渡されている `templateClass: tmpl-home` のように、変数によってテンプレートの制御を行うこともできます。

しかし 11ty の front-matter によるテンプレート継承では、 **block 構文はサポートされていません**。
11ty の front-matter ではなく、Nunjucks のテンプレート継承記法である `{% extends %}` でテンプレート継承を行うことで block 構文の使用が可能になりますが、こちらは **front-matter との併用はできないようです**。

[Eleventy Documentation: NUNJUCKS](https://www.11ty.dev/docs/languages/nunjucks/)

>
SUPPORTED FEATURES
 {% extends 'base.njk' %} looks in _includes/base.njk. Does not process front matter in the include file.

## JSON ファイルから変数を参照する

変数の管理は front-matter だけでなく、外部ファイルへの切り分けが可能です。
デフォルト設定ではプロジェクトルート直下の `_data/` 内に保存された `.json` ファイルはグローバルデータとして各ファイルから参照することができます。

eleventy-base-blog では `_data/metadata.json` がグローバルデータファイルとして保存されているので、`base.njk` のヘッダー部分を参考に、参照方法を確認してみます。

```html:base.njk
<!--header部分-->
<header>
  <h1 class="home">
    <a href="{{ '/' | url }}">{{ metadata.title }}</a>
  </h1>

  {#- Read more about `eleventy-navigation` at https://www.11ty.dev/docs/plugins/navigation/ #}
  <ul class="nav">
    {%- for entry in collections.all | eleventyNavigation %}
      <li class="nav-item{% if entry.url == page.url %} nav-item-active{% endif %}">
        <a href="{{ entry.url | url }}">{{ entry.title }}</a>
      </li>
    {%- endfor %}
  </ul>
</header>
```

`h1` タグ内の `{{ metadata.title }}` という記述にあるように、**`_data` 内のデータファイル名を含めて JSON オブジェクトを指定する**ことで、グローバルデータを参照することができます。

また、`_data/` ディレクトリ外でも `.11tydatafile.json` という拡張子を用いて

- **テンプレートファイル内のみで使用できるデータ（テンプレートデータファイル）**
- **ディレクトリ内で共通使用できるデータ（ディレクトリデータファイル）**

を設定することができます。

11ty では一つのデータについて複数箇所で記述されている場合、影響範囲の狭いものが優先されます。
従って、グローバルデータファイル、テンプレートデータファイル、ディレクトリデータファイルに同じ変数についての記述があった場合、
**テンプレートデータファイル（優先度 高） > ディレクトリデータファイル（優先度 中） > グローバルデータファイル（優先度 低） の順で反映されます**。

### テンプレートデータファイルとして使用する場合
`テンプレートファイル名と同様のファイル名.11tydatafile.json` として、テンプレートファイルと同じ場所に保存します。
eleventy-base-blog においての例としては `archive.11tydatafile.json` というファイルをプロジェクトルート直下に保存することで `archive.njk` 内のみで使用したいデータを JSON ファイルとして切り分けることができます。


### ディレクトリデータファイルとして使用する場合
`ディレクトリ名と同様のファイル名.11tydatafile.json` として、使用したいディレクトリ直下に保存します。
eleventy-base-blog においての例としては `posts.11tydatafile.json` というファイルを `posts/` に保存することで、`posts/` 内のみで使用したいデータを JSON ファイルとして切り分けることができます。

なお 11ty において `.md` ファイルはデフォルトでは `liquid` テンプレートとして前処理されるようで、マスタッシュ記法 `{{}}` で変数を参照することができます。

[Eleventy Documentation: MARKDOWN](https://www.11ty.dev/docs/languages/markdown/)

> Markdown files are by default pre-processed as Liquid templates. You can change this default in your configuration file (or disable it altogether). To change this for a single template and not globally, read Changing a Template’s Rendering Engine.

## パーマリンクを設定する

11ty のデフォルト設定では `index.njk` は `index.html` として、
特定の名前のついたファイル（ex.`archive.njk`）は**出力ディレクトリ名をその名前とした `index.html` ファイルとして**（ex.`archive/index.html`）出力されます。
出力される URL やファイル名を制御したい場合、front-matter に **permalink** を指定します。

```html:archive.njk
permalink: /posts/
```

上記の記述によって `archive.njk` は `_/site/post/index.html` として出力されます。
また、`index.html` ではなく `archive.html` としたい場合は、拡張子を含め以下のように記述することができます。

```
permalink: /posts/archive.html
```

## 11ty の提供するデータを扱う

11ty がデフォルトで提供するデータとして、各ページに関するデータがあります。具体的には以下のデータが `page` オブジェクトとして保持されており、特別な設定無しで `page` をキーに参照することができます。

- **url** : ページが出力されるURLが取得されます。
- **fileSlug** : 拡張子の除かれたソースファイル名が取得されます。[^1]
- **filePathStem** : 拡張子の除かれたソースファイルのパスが取得されます。
- **date** : デフォルトではファイルの作成日が取得されます。[^2]
- **inputPath** : ソースファイルのパスが取得されます。
- **outputPath** : 出力ファイルのディレクトリパスが取得されます。

### 日付を扱う
11ty では `{{ page.date }}` でファイルの生成日や最終更新日を参照することができます。
例として `posts/firstpost.md` を取りあげます

```:firstpost.md
---
title: This is my first post.
description: This is a post on My Blog about agile frameworks.
date: 2018-05-01
tags:
  - another-tag
layout: layouts/post.njk
---
```

上記の場合 `{{ page.date }}` で参照したデータは `firstpost.md` の front-matter で上書きされ `Wed Jul 04 2018 09:00:00 GMT+0900 (日本標準時)` が返されます。

front-matter に date オブジェクトが無かった場合、11ty はデフォルトでファイルの作成日を返すようになっています。
また、日付ではなく date オブジェクトのプロパティとして `Created` や `Last Modified` を指定することによって、ファイルの生成日や最終更新日を取得することができます。

## コレクションを使用する
11ty では作成した全てのページに関する情報を **[コレクション](https://www.11ty.dev/docs/collections/)** として保管します。
コレクションとして取得できるデータは以下のリストになります。

- **inputPath** : ソース入力ファイルへのフルパスが取得されます。
- **fileSlug** : 拡張子の除かれたソースファイル名が取得されます。[^1]
- **outputPath** : 出力ファイルへのフルパスが取得されます。
- **url** : 各ページへのリンクに使用されるURLが取得されます。
- **date** : デフォルトではファイルの作成日が取得されます。[^2]
- **data** : レイアウトから継承されたデータを含む、コンテンツのすべてのデータが取得されます。
- **templateContent** : レイアウトラッパーを含まない、テンプレートのレンダリングされたコンテンツが取得されます。

`{{ collections.all }}` で上記データオブジェクトを全ページ分取得することができます。
また、各ファイルの front-matter に `tags: xxx` を指定しておくことで、`{{ collections.xxx }}` によって `tags: xxx` を持つファイルに絞り込んだコレクションを取得できるようです。

`sample.njk` に、全ページのスラッグとURLを対応させたリストを出力する、簡単なサンプルコードを書いてみます。

```html:sample.njk
---
layout: layouts/home.njk
---
<ul>
  {% for post in collections.all %}
    <li>
      {{ post.fileSlug }}: <a href="{{ post.url }}">{{ post.url }}</a>
    </li>
  {% endfor %}
</ul>
```

以下キャプチャのように、各ページへのリンク集が作成されたかと思います。

<img width="840" alt="スクリーンショット 2020-03-29 18.50.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390806/75e1eb1b-8470-f221-389f-1f9af06b7e4b.png">

### ページネーションを使用する

eleventy-base-blog では `page-list.njk` にて、各ページへのリンク集が作成されています。こちらは **pagenation** を使用しているようです。
ここでは特に front-matter に指定されている **pagination** オブジェクトに着目していきます。

```html:page-list.njk
---
pagination:
  data: collections.all
  size: 20
  alias: entries
layout: layouts/home.njk
permalink: /page-list/{% if pagination.pageNumber > 0 %}{{ pagination.pageNumber }}/{% endif %}
---
<table>
  <thead>
    <th>URL</th>
    <th>Page Title</th>
  </thead>
  <tbody>
{%- for entry in entries %}
  <tr>
    <td><a href="{{ entry.url }}"><code>{{ entry.url }}</code></a></td>
    <td>{{ entry.data.title }}</td>
  </tr>
{%- endfor %}
  </tbody>
</table>
```

上記の `page-list.njk` では `data: collections.all` の記述によって、データソースをコレクションから20ページ分取得します。

20ページ以上ある場合は 
`permalink: /page-list/{% if pagination.pageNumber > 0 %}{{ pagination.pageNumber }}/{% endif %}` 
の指定によって、次ページが自動生成されます。

また、`alias: entries` の指定によって、`colletions.all` を `entries` として取得することができます。**pagination** の詳細な機能については公式ドキュメント [PAGINATION](https://www.11ty.dev/docs/pagination/) のページをご参照ください。

## 入出力ディレクトリ等の変更について
入出力のディレクトリ、グローバルデータディレクトリ等のパスは、11ty の設定ファイル `.eleventy.js` に設定を記述することで変更が可能です。
ディレクトリの設定はデフォルトでは以下のようになっています。

- **入力ディレクトリ**（input）: プロジェクトルート
- **出力ディレクトリ**（output）: _site
- **インクルードディレクトリ**（includes）: _includes
- **データディレクトリ**（data）: _data

以下のように dir オブジェクトを設定することで、これらの設定を上書きすることができます。

```js:.eleventy.js
module.exports = {
  dir: {
    input: "views",
    output: "dist",
    includes: "my_includes",
    data: "lore"
  },
};
```

他にも使用するテンプレートエンジンの設定やウォッチターゲットの追加などの設定はこの `.eleventy.js` を変更することで行います。詳しくは公式ドキュメントの [CONFIGURATION](https://www.11ty.dev/docs/config/) をご参照ください。

## 感想

はじめに 11ty の使い方を確認していた時は「**データが front-matter 一箇所にまとまって便利そうだなー**」くらいに思っていたのですが、具体的な使用方法や設定方法、機能などを確認していくと、中〜大規模サイト構築に向けた機能があるだけでなく、**環境構築の自由度が高く、柔軟性の高い静的サイトジェネレータである**という印象を持ちました。
自分の開発環境に合わせて、ディレクトリ構造のカスタマイズや SCSS のコンパイルなどと合わせた環境構築ができれば、静的サイト製作においてはかなり強力な静的サイトジェネレーターとして力を発揮してくれるのではないでしょうか。
次は現在の自身の開発環境に合わせて 11ty の環境構築を進めて行こうかと思っています。


[^1]: ファイル名が `index.**` となっている場合は親ディレクトリ名が返されます。詳細は [ELEVENTY SUPPLIED DATA : fileSlug](https://www.11ty.dev/docs/data-eleventy-supplied/#fileslug) をご参照ください。
[^2]: 詳細は [CONTENT DATES](https://www.11ty.dev/docs/dates/) をご参照ください。