Eleventy(SSG)をやってみた
11ty(Eleventy)をやってみた
名前をたまたま見かけただけ。
インストール
11ty/EleventyはNode.jsで動作するのでインストールはnpmを使う
npm init -ynpm 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.htmltitle: HomeeleventyNavigation: key: Home order: 1pagination: data: collections.posts size: 9 alias: postspermalink: "{% 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.htmltitle: プロフィール---{% 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に慣れてしまったせいかもしれない。