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

はじめに

前回の記事で 11ty/eleventy(以下、11ty) の公式スタートガイドに則ってサンプルを作成してみました。

HTML テンプレートエンジン詰め合わせ 11ty/eleventy を使ってみる

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

記事の概要

公式の スタータープロジェクト として、eleventy-base-blog というリポジトリが公開されています。 サンプルページはこちら。

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

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

2020.04.18 11ty/eleventy で CSS / SCSS を扱うために を追加しました。

ディレクトリ構成

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.njkindex.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 ファイルであれば、マスタッシュ記法 {{}} にて参照することが可能です。

  • sample.njk
---
pageTitle: Hello 11ty
txt: Hi
---
<!doctype html>
<html>
  <head>
    <title>{{ pageTitle }}</title>
  </head>
  <body>
    <p>{{ txt }}</p>
  </body>
</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

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

  • base.njk
  • home.njk
  • index.njk
  • postslist.njk

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

スクリーンショット-2020-03-22-16.22.54(2).png

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

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

  • 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 の中身は

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

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

  • 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

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 のヘッダー部分を参考に、参照方法を確認してみます。

  • 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

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.njkindex.html として、 特定の名前のついたファイル(ex.archive.njk)は出力ディレクトリ名をその名前とした index.html ファイルとして(ex.archive/index.html)出力されます。 出力される URL やファイル名を制御したい場合、front-matter に permalink を指定します。

  • 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 オブジェクトのプロパティとして CreatedLast Modified を指定することによって、ファイルの生成日や最終更新日を取得できます。

コレクションを使用する

11ty では作成したすべてのページに関する情報を コレクション として保管します。 コレクションとして取得できるデータは以下のリストになります。

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

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

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

  • 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>

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

スクリーンショット 2020-03-29 18.50.50.png

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

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

  • 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.allentries として取得できます。pagination の詳細な機能については公式ドキュメント PAGINATION のページをご参照ください。

入出力ディレクトリ等の変更について

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

  • 入力ディレクトリ(input): プロジェクトルート
  • 出力ディレクトリ(output): _site
  • インクルードディレクトリ(includes): _includes
  • データディレクトリ(data): _data

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

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

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

感想

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

Footnotes

  1. ファイル名が index.** となっている場合は親ディレクトリ名が返されます。詳細は ELEVENTY SUPPLIED DATA : fileSlug をご参照ください。 2

  2. 詳細は CONTENT DATES をご参照ください。 2