Eleventy(SSG)をやってみた
11ty(Eleventy)をやってみた
名前をたまたま見かけただけ。

インストール
11ty/EleventyはNode.jsで動作するのでインストールはnpmを使う
npm init -y
npm pkg set type="module"
npm install @11ty/eleventy
インストールしたパッケージ等
{
"name": "eleventy-blog",
"version": "0.0.1",
"type": "module",
"scripts": {
"start": "eleventy --serve",
"build": "eleventy"
},
"dependencies": {
"@11ty/eleventy": "^3.0.0",
"@11ty/eleventy-img": "^6.0.2",
"@shikijs/markdown-it": "^3.2.2",
"fast-glob": "^3.3.3",
"image-size": "^2.0.2",
"luxon": "^3.6.1",
"markdown-it": "^14.1.0",
"markdown-it-anchor": "^9.2.0",
"markdown-it-attrs": "^4.3.1",
"markdown-it-eleventy-img": "^0.10.2",
"photoswipe": "^5.4.4",
"sharp": "^0.34.1"
}
}
eleventy.config.js で設定
Eleventyの設定やスクリプトは eleventy.config.js に記述する。
下の方のreturn dirでディレクトリの設定をする。ここでは/src/content以下にテンプレートを置く。
includes:やlayouts:のディレクトリはinput:のディレクトリから見た相対パス。
以下は eleventy.config.js全体。eleventy-imgによる画像最適化もここで行う。
とても長いのでほとんどの部分は省略した。
import fs from 'fs';
import path from 'path';
import Image from '@11ty/eleventy-img';
import Sharp from 'sharp';
import Shiki from '@shikijs/markdown-it';
import markdownIt from 'markdown-it';
import markdownItAnchor from 'markdown-it-anchor';
import markdownItAttrs from 'markdown-it-attrs';
import markdownItEleventyImg from 'markdown-it-eleventy-img';
import { DateTime } from 'luxon';
const md = markdownIt({
html: true,
breaks: true,
linkify: true,
});
// Shiki の設定は省略
default function (eleventyConfig) {
let markdownLibrary = md;
markdownLibrary
.use(markdownItEleventyImg)
.use(markdownItAttrs)
.use(markdownItAnchor);
eleventyConfig.setLibrary('md', markdownLibrary);
// ファイルのコピー
eleventyConfig.addPassthroughCopy({ 'src/assets': 'assets' });
eleventyConfig.addPassthroughCopy({ 'src/public': '/.' });
/* 省略 */
// ショートコード
eleventyConfig.addShortcode('year', () => `${new Date().getFullYear()}`);
/* 省略 */
// フィルタ 日時を変換するなど
eleventyConfig.addFilter('readableDate', dateObj => {
return DateTime.fromJSDate(dateObj, { zone: 'utc' }).toFormat('yyyy年M月d日');
});
eleventyConfig.addFilter('htmlDateString', dateObj => {
return DateTime.fromJSDate(dateObj, { zone: 'utc' }).toFormat('yyyy-LL-dd');
});
// タグのコレクション
eleventyConfig.addCollection('tagList', function (collection) {
let tagSet = new Set();
collection.getAll().forEach(item => {
if ('tags' in item.data) {
let tags = item.data.tags;
if (Array.isArray(tags)) {
for (const tag of tags) {
tagSet.add(tag);
}
}
}
});
return [...tagSet].sort();
});
// postsのコレクション
eleventyConfig.addCollection('posts', function (collection) {
return collection.getFilteredByGlob('./src/content/posts/**/*.{md,mdx}').sort((a, b) => {
return b.date - a.date;
});
});
return {
dir: {
input: './src/content',
output: '_site',
includes: '../_includes',
layouts: '../_layouts',
data: '../_data',
},
templateFormats: ['md', 'mdx', 'njk', 'html', 'liquid'],
markdownTemplateEngine: 'njk',
htmlTemplateEngine: 'njk',
dataTemplateEngine: 'njk',
};
}
ディレクトリの図
大まかなツリーは以下の通り。
/
├── eleventy.config.js
├── package.json
└── src
├── _data
├── _includes
├── _layouts
│ ├── base.html
│ └── post.html
├── assets
├── content
│ ├── index.html(トップページのテンプレート)
│ ├── posts (markdownファイルがあるディレクトリ)
└── public
レイアウト
AstroでいうLayoutは/src/_layouts ディレクトリに置く。(上記の設定ファイルで場所は自由)
画像やcssなどは/src/assets以下に配置。
{{ content | safe }}はAstroで言う<slot>にあたる。
{{ metada.SITE_LANG }} のような表記は /src/_data ディレクトリのjsonファイルの中のデータを読み込んでいる。
metadataはmetadata.jsonのファイル名。
{
"SITE_TITLE": "へび右曲がり",
}
基本のレイアウト/src/_layouts/base.html
<!DOCTYPE html>
<html lang="{{ metadata.SITE_LANG }}">
<head>
<title>{% if title %}{{ title }} | {% endif %}{{ metadata.SITE_TITLE }}</title>
<style>{% include "css/base.css" %}</style>
<!-- ------ 省略 ------- -->
</head>
<body>
{% include "site_header.html" %}
<main class="site-main">
<div class="container">
{{ content | safe }}
</div>
</main>
{% include "site_footer.html" %}
</body>
</html>
ブログ記事のレイアウト。/src/_layouts/post.html
frontmatter部分でbase.htmlをレイアウトとして読み込んでいる。
---
layout: base.html
---
<style>
{% include "css/blog.css" %}
</style>
<article class="post">
<header class="post-header">
{% include "post_head.html" %}
</header>
<main id="blogbody" class="post-content">
{{ content | safe }}
</main>
<footer class="post-footer">
{% include "post_foot.html" %}
</footer>
</article>
テンプレート
ここでは/src/contet以下にテンプレートを置いていく。
markdownファイルもテンプレートなのでcontent内にディレクトリを作りそこに配置する。
以下はサイトのトップページのテンプレート。
---
layout: base.html
title: Home
eleventyNavigation:
key: Home
order: 1
pagination:
data: collections.posts
size: 9
alias: posts
permalink: "{% if pagination.pageNumber > 0 %}/{{ pagination.pageNumber + 1 }}/{% endif %}index.html"
---
{% include "blogcard.html" %}
{% include "page_nav.html" %}
<div style="display:none;">
{% include 'avatar.html' %}
</div>
pagination:でブログのコレクション(data: collections.posts)を元にしたページネーションの設定をしている。size: 9で9項目ごとにページ分割される。permalink:でページごとのURLを設定している。alias: postsでコレクションにアクセスできる。({% for post in posts %}のような使い方)。
インクルード
別のファイルを特定の位置に読み込みたい場合は_includes以下にファイルを置く。
インクルードされる側のテンプレート/src/_includes/avatar.html
<div class="profile-card">
<img src="/img/nyan4.jpg" loading="lazy" alt="ubanis nyan" />
<p>{{ metadata.SITE_OWNER }}</p>
</div>
{% include "avatar.html" %}のようにインクルードする。
---
layout: base.html
title: プロフィール
---
{% include "avatar.html" %}
引数のあるコンポーネントを使いたい場合はショートコード以外の方法としてmacroを使う。
{{ caller() | safe }}がAstroでいうコンポーネントの中の<slot>。
{% macro button(url, rounded=false, blank=false, color="base", border=false, extraAttributes="") %}
{% set classList = ["button-base", "button-color-" + color] %}
{% if rounded %}
{% set classList = classList.concat(["button-rounded"]) %}
{% endif %}
{% if border %}
{% set classList = classList.concat(["button-bordered"]) %}
{% endif %}
{% set classString = classList.join(" ") %}
<a href="{{ url }}" class="{{ classString }}"
{% if blank %} target="_blank" rel="noopener noreferrer"{% endif %}
{{ extraAttributes | safe }}
>
{{ caller() | safe }}
</a>
{% endmacro %}
呼び出し元では使いたいmacroをimportして call endcall ブロックを使ってmacroを呼び出す
{% import "components/button.html" as components %}
{% call components.button(url="https://example.com", rounded=true) %}
<span>ボタンの中身</span>
{% endcall %}
レイアウトの指定とURLの変更
markdownがあるディレクトリ以下にレイアウト情報などを設定する。
ここでレイアウトを設定するとこのディレクトリ以下のレイアウトはすべて指定されたものになる。
posts.11tydata.jsを作成する。(コレクション名がnoteならばnote.11tydata.js)
元のAstroブログではblogはサイトのルートに配置されるようになっていたので以下のようにURLを変更する設定も記述。
export default {
permalink: data => {
const filePathStem = data.page.filePathStem;
const slug = filePathStem.replace(`posts`, '');
return `/${slug}/`;
},
layout: "post.html",
};
実際に表示させてみる。
npm run start
Astroからかなり移植したので見た目はほぼ同じ。

11tyの結論
手間はかかりそうなもののかなりスクリプトでなんとかなる雰囲気。
Hugoと比べて融通は効くものの大変そうではある。画像変換はプラグインもあり便利。macroがイマイチ使いづらい。
Astroに慣れてしまったせいかもしれない。
