При создании сайтов частой задачей является упорядочивание контента, например, статей в блоге. Генератор статических сайтов Eleventy для каждой страницы предоставляет полезный набор данных page
, в котором хранится url страницы, путь до файла-исходника и некоторые другие свойства. Среди них важным является page.date
. На его основе Eleventy сортирует коллекции страниц, только если мы сами не задали свои правила сортировки.
Поле date
Cсылка на данный раздел: Поле dateПо умолчанию в page.date
помещается дата создания файла. Но это значение можно настроить. Для этого есть специальное поле date
, которое указывается в frontmatter-секции шаблонов или любом другом источнике данных Eleventy.
Туда можно записать любое стандартное (ISO 8601) значение даты.
---
date: 2016-01-01
---
Кроме этого, поле date
может принимать особые значения-константы:
Last Modified
- дата последнего изменения файла.Created
- дата создания файла.git Last Modified
- дата последнего изменения файла, которая вычисляется с помощью системы контроля версий Git.git Created
- дата создания файла, которая вычисляется с помощью системы контроля версий Git.
Пример даты на основе Git:
---
date: git Created
---
Как видите, довольно удобно - дату можно брать из файловой системы, из Git или задавать вручную.
Git-даты и сервисы CI/CD
Cсылка на данный раздел: Git-даты и сервисы CI/CDПреимущество Git-способа является в том, что процесс установки даты автоматизируется. Но есть и минусы – такие вычисления требуют довольно много ресурсов, особенно для даты создания, так как нужна вся история изменений. Сложности могут возникнуть при использовании различных CI/CD-инструментов, таких как Github Actions. Например, Checkout делает по умолчанию поверхностное (shallow) клонирование репозитория. В этом случае даты могут вычисляться некорректно.
Одним из способов решения проблемы может быть указание в настройках Checkout параметра fetch-depth: '0'
. Тогда даты будут верными, но будет выкачиваться вся история, все ветки и все теги.
- name: Checkout
uses: actions/checkout
with:
fetch-depth: '0'
Такие настройки необходимы для получения даты создания, но если нужна только дата последнего изменения, то есть другой способ - вручную склонировать репозиторий c последним коммитом, а вспомогательную информацию забрать с помощью git fetch
:
- name: Checkout
run: |
git clone --depth 1 <YOUR_REPO> .
git fetch --unshallow
Реализация своих полей
Cсылка на данный раздел: Реализация своих полейEleventy даёт только одно поле date
, но что если нам нужно несколько полей для работы с датами, например, для даты публикации поста и для даты последнего обновления поста. Реализуем их сами и назовём как publishedAt
и updatedAt
.
Поле updatedAt
будем вычислять через Git, а publishedAt
будем задавать вручную в frontmatter-секции:
---
publishedAt: 2024-1-30
---
Теперь нам нужно для дальнейшего удобства работы преобразовывать эти данные из строки в объекты Date
и сортировать коллекции на этой основе.
Преобравазование даты можно выполнять в общем для всех статей data-файле. Например, если все статьи хранятся в папке articles
, то нужно создать в ней файл articles.11tydata.js
:
articles
|_ article1
|_ index.md
|_ article2
|_ index.md
articles.11tydata.js
Воспользуемся мощной функциональностью Eleventy - вычисляемые поля eleventyComputed
.
// articles.11tydata.js
module.exports = {
eleventyComputed: {
publishedAt(data) {
if (!data.publishedAt) {
return;
}
return new Date(data.publishedAt);
}
}
}
С помощью такой техники мы создаём новое одноимённое поле, которое получает доступ к старому строковому значению и возвращает объект типа Date
.
Отсортируем коллекцию постов так, чтобы сначала шли новые статьи. Для этого воспользуемся методами для работы с коллекциями внутри конфигурационного файла eleventy.config.js
:
module.exports = function(eleventyConfig) {
eleventyConfig.addCollection('articles', (collectionAPI) => {
return collectionAPI
.getFilteredByGlob('src/articles/*/index.md')
.toSorted((a, b) => b.data.publishedAt - a.data.publishedAt);
});
}
Здесь мы ищем все нужные нам файлы статьей index.md
, предполагая, что они хранятся внутри папки src/articles
.
Для красивого форматирования дат внутри шаблонов можно создать свой фильтр или завести ещё одно вычисляемое поле в articles.11tydata.js
.
Пример с вычисляемым полем:
// articles.11tydata.js
module.exprots = {
eleventyComputed: {
formattedPublishDate(data) {
if (!data.publishedAt) {
return;
}
return data.publishedAt.toLocaleString('ru', {
day: 'numeric',
month: 'long',
year: 'numeric'
});
}
}
}
Пример с фильтром:
// eleventy.config.js
module.exports = function(eleventyConfig) {
eleventyConfig.addFilter('formatDate', function (date) {
date = typeof date === 'string' ? new Date(date) : date;
return date.toLocaleString('ru', {
day: 'numeric',
month: 'long',
year: 'numeric'
});
});
}
Наконец, можно использовать дату публикации в шаблонах. Hа примере шаблонизатора Nunjucks отрендерим список статей:
<!-- index.njk -->
{% for article in collections.articles %}
<article>
<h3>{{ article.data.title }}</h3>
<!-- Пример с вычисляемым полем: -->
<time>{{ article.data.formattedPublishDate }}</time>
<!-- Пример с фильтром: -->
<time>{{ article.data.publishedAt | formatDate }}</time>
</article>
{% endfor %}
Теперь реализуем поле updatedAt
. В терминах Git это дата последнего коммита. Её можно найти с помощью такой команды:
git --no-pager log -n 1 --format="%ci <path>
В ней нужно будет заменить <path>
на путь к нужному файлу или папке. Работать будем именно с папкой, содержащей статью, так как там могут быть и другие связанные с ней материалы, например, картинки. Их правки тоже стоит учитывать.
Будем запускать Git как внешний процесс через Node.js-модуль child_process
. А завернём всё это снова в вычисляемое поле, которое можеть быть асинхронной функцией:
// articles.11tydata.js
const path = require('node:path');
const util = require('node:util');
const { execFile } = require('node:child_process');
async function runCommand(command) {
const [bin, ...args] = command.split(' ');
const { stdout } = await util.promisify(execFile)(bin, args, {
encoding: 'utf-8'
});
return stdout;
}
async function parseLastCommitDate(contentPath) {
const command = `git --no-pager log -n 1 --format="%ci" ${contentPath}`;
const date = (await runCommand(command)).trim();
return date ? new Date(date) : null;
}
module.exports = {
eleventyComputed: {
updatedAt(data) {
const contentPath = path.dirname(data.page.inputPath);
return parseLastCommitDate(contentPath);
}
}
}
Можем дополнить рендер шаблона новым типом даты:
<!-- index.njk -->
{% for article in collections.articles %}
<article>
<h3>{{ article.data.title }}</h3>
Дата публикации: <time>{{ article.data.publishedAt | formatDate }}</time>
Дата обновления: <time>{{ article.data.updatedAt | formatDate }}</time>
</article>
{% endfor %}
Eleventy-плагин для работы с датами
Cсылка на данный раздел: Eleventy-плагин для работы с датамиВсе вышеперечисленные идеи я завернул в плагин, добавив возможность использовать ключевые слова, подобные тем, что есть в Eleventy для date
:
Date. FS. Created
Date. FS. Last Modified
Date. Git. Created
Date. Git. Last Modified
Названия говорят сами за себя. Указывать их можно как в frontmatter-разделе:
---
createdAt: 'Date. Git. Created'
updatedAt: 'Date. Git. Last Modified'
---
<time datetime="{{ createdAt.toISOString()}}">
{{ createdAt.toLocaleDateString() }}
</time>
<time datetime="{{ updatedAt.toISOString()}}">
{{ updatedAt.toLocaleDateString() }}
</time>
Так и в 11tydata
-файлах:
const { TIMESTAMPS } = require('@web-alchemy/eleventy-plugin-content-dates');
module.exports = {
createdAtWithFS: TIMESTAMPS.FS_CREATED,
updatedAtWithFS: TIMESTAMPS.FS_LAST_MODIFIED,
createdAtWithGit: TIMESTAMPS.GIT_CREATED,
updatedAtWithGit: TIMESTAMPS.GIT_LAST_MODIFIED,
}
Функции из плагина можно использовать как библиотеку без необходимости регистрировать плагин. Подробнее в README репозитория.