11ty(eleventy)で静的サイト構築

Blog

Githubの★の数などで静的サイトジェネレーターを比較できる StaticGenを見ていたら、Nunjucksが使える 11ty(eleventy)というのを見つけました。

そういうわけでオオコシです。

静的サイトジェネレーターというと JekyllHexo などが有名かと思うんですが、11tyは Google Open Source Award にも選ばれており、テンプレートは Nunjucks だけでなく Liquid / PUG / Mustache / Handlebars / EJS / HAML / JavaScript Template Literals、もちろんHTMLやMarkdown が使えるので、例えば Nunjucks やめて PUG 使いたい!っていうケースにも対応できるし、A simpler static site generator. って書いてあるし、なかなか良いんじゃないかと思って試してみました。

インストール〜設定

11tyを使う場合の Node.js のバージョンは8以上となります。

Requires version 8 of Node.js or higher.

今回は Node.js の 10.1.0 を使おうと思うので nodenv で指定します。
11tyのREADMEにはグローバルにインストールするように記載があるのですが、プロジェクトごとに管理したいのでローカルにインストールします。

例)11ty_sampleというディレクトリ配下で作成する場合

$ cd 11ty_sample
$ nodenv local 10.1.0
$ npm i -D @11ty/eleventy

グローバルに入れた場合は eleventy だけで実行できるのですが、ローカルなので npx で実行します。

$ npx eleventy

このままではデフォルトの設定(--input . --output _site)で出力されてしまうので .eleventy.js という名前で設定ファイルを作成します。
設定項目については Configration (optional) を参照してください。

今回は下記のようなディレクトリ構成にしようと思います。

11ty_sample/
  htdocs/ :出力
  src/
    html/ :入力
      _includes/ :テンプレート格納場所
      _data/ :データ(JSON)格納場所

この場合の.eleventy.jsは下記のようになります。
※ターミナルで npx eleventy --input ./src/html --output ./htdocs/ と入力したものと同等

module.exports = {
  dir: {
    input: "src/html",
    output: "htdocs"
  }
};

プラグインや独自フィルタの設定をする場合は関数にします。

module.exports = function(eleventyConfig) {
  return {
    dir: {
      input: "src/html",
      output: "htdocs"
    }
  };
};

そしてスターターキットとして用意されている eleventy-base-blog を参考に、いろいろやってみたものが下記になります。

.eleventy.js
const { DateTime } = require("luxon");
const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");

module.exports = (function(eleventyConfig) {
  // 日付表示変換フィルタ
  eleventyConfig.addFilter("readableDate", dateObj => {
    return DateTime.fromJSDate(dateObj).setLocale('ja').toFormat("yyyy'年'M'月'd'日'");
  });

  // シンタックスハイライト
  eleventyConfig.addPlugin(syntaxHighlight);

  // 11ty設定
  return {
    templateFormats: [
      "md",
      "njk",
      "html"
    ],
    pathPrefix: "/",
    markdownTemplateEngine: "liquid",
    htmlTemplateEngine: "njk",
    dataTemplateEngine: "njk",
    passthroughFileCopy: true,
    dir: {
      input: "src/html",
      includes: "_includes",
      data: "_data",
      output: "htdocs"
    }
  };
});

LuxonDate オブジェクトを扱いやすくするライブラリです。
eleventy-plugin-syntaxhighlight は、コードブロックがあった場合、ビルド時に Prism形式に変換するものです。Prism.js本体は不要で、Prism用のテーマCSSを読み込むだけでシンタックスハイライトが適用されます。
dir.data dir.includesdir.input から見たパスになります。

グローバル変数、テンプレートの作成〜出力

まずはサイト全体で使うグローバル変数を .eleventy.jsdir.data で指定したフォルダに site.json という名前で下記のようなファイルを作成します。

src/html/_data/site.json
{
    "name": "11ty test site",
    "url": "http://11ty.sample.local"
}

ファイル名に決まりはありませんが、filename.key という感じで {{ site.name }} でサイト名が、{{ site.url }} でサイトURLが全ページで取得できるようになります。
データファイルは他にもいろいろな使い方があるので、詳しくは Using Data を参照してください。

次に全体のベースとなる Nunjucksテンプレートを dir.includes で指定したフォルダに base.njk として作成します。

src/html/_includes/base.njk
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta name="viewport" content="width=device-width">
<title>{% if page.url !== '/' %}{{ title }} | {% endif %}{{ site.name }}</title>
<meta name="description" content="{{ description }}">

<link rel="stylesheet" href="{{ '/css/styles.css' | url }}">
{% block css -%}{%- endblock %}
</head>
<body>
<header>ヘッダー</header>

<main>
{% block main -%}
コンテンツ
{%- endblock %}

<p>Update:{{ page.date | readableDate }}</p>
</main>

<footer>フッター</footer>

<script src="{{ '/js/common.bundle.js' | url }}"></script>
{% block js -%}{%- endblock %}
</body>
</html>

site.jsonで設定したグローバル変数の他に、11tyでは page で現在のページの情報が取得できます。(下記コードブロック参照)
{{ page.url }} は出力先のルートからのパスになります。
というわけで、if文の中の page.url!=="/" はTOPページ以外(になるはず)です。

{
  url: "/current/page/file.html",
  date: new Date(),
  inputPath: "/current/page/file.md",
  fileSlug: "file"
}

その他、特殊な変数は Special Variables で確認できます。

{% block js -%}{%- endblock %}などは Nunjucks の記法になるので Nunjucksのドキュメント を参考に。

{{ '/css/styles.css' | url }}11ty 独自のフィルターで、URLの出力を最適化してくれます。
.eleventy.jsaddFilter で追加した独自のフィルターも使えます。

そして、上記のテンプレートを読み込むコンテンツ側はこんな感じになります。

src/html/index.njk
---
title: トップページ
description: ページ概要がはいります。
---
{% extends 'base.njk' %}

{% block css -%}
<link rel="stylesheet" href="{{'/css/top.css' | url }}">
{%- endblock %}

{% block main -%}
<h2>トップページのコンテンツ</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. <br>
Adipisci tempora asperiores earum, <br>
aspernatur pariatur officia cumque mollitia quos ab sapiente porro nulla cupiditate. <br>
Animi unde voluptatibus hic aspernatur deleniti minima.</p>
{%- endblock %}

{% block js -%}
<script src="{{ '/js/top.bundle.js' | url }}"></script>
{%- endblock %}

ページ単位で使う変数は、ページ上部にYAML形式で設定します。
titledescription は普通の変数なんですが、Front Matter on any Templateにあるように、permalink pagination layout tags date は特殊な変数となります。

permalinks は 出力先のURLを個別に指定することができます。
デフォルトではCool URIs don’t changeのルールを採用しており、
subdir/template.njk で作成しても subdir/template.html にはならず、subdir/template/index.html になるとのことで、
これに当てはまらない場合は permalinks に任意のパスを書くことになります。

layoutdir.includes 配下を参照してページに適用するテンプレートを選択します。
ただし、こちらでは Nunjucksの {% block %} が使えませんでした。
そのため、Markdown などで記事を書くときは layout、Nunjucks形式でがっつりコーディングするときは {% extend %} というような使い方になりそうです。

date はデフォルトでは new Date(); なので、記事の公開日(更新日)を指定したい場合に上書きする形になります。

pagenationtags はそれぞれ PaginationCollections を読んでみてください。記事一覧なんかに使えそうです。

そしてここまで長くなりましたが、$ npx eleventy を実行すると下記のように出力されます。

htdocs/index.html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta name="viewport" content="width=device-width">
<title>11ty test site</title>

<meta name="description" content="ページ概要がはいります。">

<link rel="stylesheet" href="/css/styles.css">
<link rel="stylesheet" href="/css/top.css">
</head>
<body>
<header>ヘッダー</header>

<main>
<h2>トップページのコンテンツ</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. <br>
Adipisci tempora asperiores earum, <br>
aspernatur pariatur officia cumque mollitia quos ab sapiente porro nulla cupiditate. <br>
Animi unde voluptatibus hic aspernatur deleniti minima.</p>

<p>Update:2018年5月18日</p>
</main>

<footer>フッター</footer>

<script src="/js/common.bundle.js"></script>
<script src="/js/top.bundle.js"></script>
</body>
</html>

layoutを使ったパターン

ちなみに layout を使った場合はこんな感じになります。
プラグインで指定したシンタックスハイライトを活かすために、prismのテーマCSSを追加で読み込んでいます。

テンプレート:src/_includes/blog.njk

{% extends 'base.njk' %}
{% block css -%}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.14.0/themes/prism.min.css">
{%- endblock %}

{% block main -%}
{{ content | safe }}
{%- endblock %}

原稿1:src/html/blog/test.md

---
title: マークダウンで書く記事のテスト
description: ページ概要がはいります。
layout: blog.njk
---
# {{title}}

{{ desctiption }}

 ```html
<p>コードが入ります</p>
 ```

## 表

| 表見出し | 表見出し | 表見出し |
|:-- | :--: | --:|
|左揃え| 中央 | 右揃え |

原稿2:src/html/blog/test2.md

---
title: マークダウンで書く記事のテスト2
description: ページ概要がはいります。
layout: <blog class="njk"></blog>
permalink: /blog/test2.html
---

(以下略)

結果1:htdocs/blog/test/index.html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta name="viewport" content="width=device-width">
<title>マークダウンで書く記事のテスト | 11ty test site</title>

<meta name="description" content="ページ概要がはいります。">

<link rel="stylesheet" href="/css/styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.14.0/themes/prism.min.css">
</head>
<body>
<header>ヘッダー</header>

<main>
<h1>マークダウンで書く記事のテスト</h1>
<pre class="language-html"><code class="language-html">
<div class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>コードが入ります<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span></div>
</code></pre>
<h2>表</h2>
<table>
<thead>
<tr>
<th style="text-align:left">表見出し</th>
<th style="text-align:center">表見出し</th>
<th style="text-align:right">表見出し</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">左揃え</td>
<td style="text-align:center">中央</td>
<td style="text-align:right">右揃え</td>
</tr>
</tbody>
</table>

<p>Update:2018年5月18日</p>
</main>

<footer>フッター</footer>

<script src="/js/common.bundle.js"></script>

</body>
</html>

結果2:htdocs/blog/test2.html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta name="viewport" content="width=device-width">
<title>マークダウンで書く記事のテスト2 | 11ty test site</title>

(以下略)

終わりに

実際制作するとなると11tyだけでなく、SassWebpack なども使う必要があるので、gulp-shell などで連携させる感じになると思いますが、それはまた別の機会に。

参考

簡単な使い方については、11ty公式のMediumにも記載があります。(level3は執筆中のようです)