Перестаньте откладывать. Делитесь знаниями в блоге, созданном с помощью Eleventy.

Вы когда-нибудь испытывали желание создать сайт, но не предпринимали никаких действий? В последнее время я постоянно думаю о блоге. Но начать — это большое препятствие для меня. Сегодня я бросаю себе вызов: создать блог как можно быстрее и сосредоточиться на трех моментах:

Производительность
Используя Lighthouse в качестве инструмента измерения, блог должен набрать 100 баллов по всем критериям.

Опыт разработчика
Автору должно быть легко создавать новые посты для блога.

Прогрессивное совершенствование
Блог следует философии прогрессивного совершенствования. Базовая функциональность доступна каждому пользователю, а лучшие возможности доступны только пользователям с современным браузером.

Начало

Из-за моей привычки откладывать дела на потом, я поставил перед собой задачу начать блог как можно скорее, не забывая при этом о вышеперечисленных пунктах.

Для блога, в котором почти нет динамических данных, статическая генерация сайта — отличный вариант для достижения лучшей производительности. Статический сайт генерирует страницы во время сборки, а не во время выполнения, как рендеринг на стороне сервера или на стороне клиента. При использовании статических генерируемых страниц нет необходимости получать данные из базы данных, API и т.д. во время выполнения.

Для генерации статического блога мы будем использовать Eleventy. Используем пример блога из Eleventy в качестве основы для нашего блога. Клонируйте репозиторий:

git clone https://github.com/11ty/eleventy-base-blog.git my-blog

Запустите npm install и отредактируйте метаданные в _data/metadata.json.

Интеграция Tailwind CSS

Поскольку я хочу создать блог быстро, я буду использовать Tailwind CSS. Tailwind — это фреймворк, ориентированный на утилиты, который делает создание и дизайн в 10 раз быстрее (по крайней мере, для меня). Мне больше не нужно беспокоиться об именах классов и я могу легко настроить дизайн блога. Конечно, свобода в настройке может быть и недостатком. В случае с блогом это влияние равно нулю. Вы можете пропустить эту часть, если не хотите использовать Tailwind.

Установите пакеты

Сначала установите следующие плагины:

Tailwindcss
Фреймворк, который мы можем использовать для стилизации нашего блога.

Autoprefixer
Записывайте CSS, не заботясь о префиксах производителей, таких как -webkit, -moz и т.д.

Eleventy плагин postcss
Он включает поддержку PostCSS в Eleventy. Если вы хотите сделать это самостоятельно, вы можете, и я, вероятно, тоже это сделаю. Но сейчас я хочу запустить сайт как можно быстрее.

Я также буду использовать TailwindUI. TailwindUI предлагает различные примеры компонентов, которые были созданы с использованием фреймворка. Это источник вдохновения и облегчает создание макета для блога.

Заставьте его работать

Теперь пришло время запустить инструментарий. Начнем с создания конфигурации PostCSS.

// postcss.config.js

module.exports  =  {
    plugins:  {
        tailwindcss:  {},
        autoprefixer:  {},
    }
}
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь нам нужно создать конфигурацию Tailwind.

// tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
    content: [
        '_includes/**/*.njk',
    ],
    theme:  {
        extend:  {},
    },
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Укажите файлы, использующие классы Tailwind, в массиве content. Компилятор добавит эти классы в таблицу стилей.

Вы можете добавить классы Tailwind в файлы nunjucks, и компилятор загрузит в таблицу стилей только эти классы. Сохраняя таблицу стилей небольшой и свободной от ненужных классов, браузеру не приходится загружать огромный файл CSS, и в результате он работает лучше.

Поскольку мы будем использовать Tailwind, мы можем удалить стандартные функции CSS из примера блога. Удалите ссылки на prism-base16-monokai.dark.css и prism-diff.css. Также удалите следующую строку из eleventy.config.js: eleventyConfig.addPassthroughCopy("css");.

Замените содержимое в index.css на:

/* css/index.css */

@tailwind base;
@tailwind components;
@tailwind utilities;
Войти в полноэкранный режим Выйти из полноэкранного режима

И добавьте плагин PostCSS в eleventy.config.js.

Запустите npm run serve и можете начинать возиться с Tailwind в Eleventy.

Интеграция dev.to API

Хорошо, теперь, когда Tailwind готов, давайте подумаем о создании постов в блоге. Я не хочу возиться с созданием редактора, добавлением опции для комментариев и хранением их в какой-то базе данных.

Процесс создания редактора и работы с комментариями — это то, что я предпочел бы пропустить. Моя конечная цель — сделать все как можно проще. Именно здесь на помощь приходит dev.to. С помощью API я смогу размещать статьи на форуме и в своем блоге.

Dev.to состоит из редактора и комментариев. Пользователи смогут взаимодействовать с контентом на dev.to. Мне не придется создавать все самому. Кроме того, dev.to уже генерирует больше трафика, чем только что созданный блог.

Если вы хотите получить все посты из своего аккаунта в блог, вам понадобится ключ API. В разделе «Настройки > Расширения» вы найдете ключ.

Пожалуйста, убедитесь, что ваш API-ключ безопасен и надежен. Не публикуйте ключ в своем хранилище. Мы сделаем это через файл окружения. Давайте добавим поддержку файла окружения.

Запустите npm install dotenv.

Создайте файл env.example с DEVTO_API_KEY=, не заполняйте ключ API. Этот файл показывает, какие ключи существуют. Зафиксируйте этот файл в вашем репозитории.

После этого скопируйте файл env.example в .env и вставьте ключ API. Убедитесь, что вы включили .env в .gitignore.

Импортируйте dotenv в файл .eleventy.js:

После включения ключа API в код. Вы можете получать статьи из своего аккаунта dev.to без необходимости публиковать ключ.

Теперь нам нужен способ получить статьи и использовать их на наших страницах. К счастью, Eleventy предоставляет простой способ хранения данных ответа API. Когда мы посмотрим на наш проект, мы увидим папку data. Создайте в ней новый файл posts.js.

// posts.js

const EleventyFetch = require("@11ty/eleventy-fetch");

module.exports = async function() {
  try {
    let url = "https://dev.to/api/articles/me";
    let posts = await EleventyFetch(url, {
        duration: "1d",
        type: "json",
        fetchOptions: {
            headers: {
                "api-key": process.env.DEVTO_API_KEY
            }
        }
    });

    return posts;
    } catch(e) {
        console.log('Failed to fetch articles. Return empty array.');
        return [];
    }
};
Вход в полноэкранный режим Выйдите из полноэкранного режима

Файл posts выполняет запрос с ключом API и извлекает все опубликованные статьи из вашего аккаунта. Поскольку имя файла называется posts, переменная для доступа к статьям также будет называться posts.

Убедитесь, что вы используете пакет @11ty/eleventy-fetch.

npm install @11ty/eleventy-fetch

Этот пакет гарантирует, что статьи будут кэшироваться локально, и в результате API dev.to не будет вызываться при каждой сборке проекта.

Сборка

Мы установили все, чтобы начать сборку наших страниц. Вы можете скопировать мои или легко доработать страницы самостоятельно с помощью Tailwind. Давайте создадим главную страницу, статьи и страницу одной статьи. Измените следующие файлы:

//_includes/layouts/base.njk

<body class="min-h-full flex flex-col h-screen">
    <header class="flex justify-between items-center p-4 container mx-auto">

        <nav class="w-2/5">
            <ul>
                {%- for entry in collections.all | eleventyNavigation %}
                    <li class="md:inline mb-3 md:mb-0 hover:text-indigo-600 {% if entry.url == page.url %}text-indigo-600 font-bold{% endif %}">
                        <a  href="{{ entry.url | url }}" class="py-3 pr-3 md:pl3">{{ entry.title }}</a>
                    </li>
                {%- endfor %}
            </ul>
        </nav>

        <a href="{{ '/' | url }}" class="font-bold text-3xl flex-1 text-center" rel="home">{{ metadata.site_name }}</a>

        <nav class="w-2/5">
            <ul class="text-right">
                <li class="md:inline mb-3 md:mb-0 md:ml-3 hover:text-indigo-600">
                    <a href="https://twitter.com/j1sc2s" target="_blank">Twitter</a>
                </li>
                <li class="md:inline mb-3 md:mb-0 md:ml-3 hover:text-indigo-600">
                    <a href="https://github.com/jstnjs" target="_blank">GitHub</a>
                </li>
            </ul>
        </nav>
    </header>

    <main{% if templateClass %} class="{{ templateClass }} flex-1"{% endif %}>
        <div class="container mx-auto p-4">
            <div class="max-w-lg mx-auto">
                {{ content  |  safe }}
            </div>
        </div>
    </main>
</body>
Войти в полноэкранный режим Выйти из полноэкранного режима
//_includes/layouts/home.njk

<section>
    <header class="flex items-center">
        <h2 class="font-bold text-2xl my-4">About</h2>
    </header>

    <p class="text-slate-700">Add your fantastic intro here!</p>
</section>

<section class="my-8">
    <header class="flex items-center">
        <h2 class="font-bold text-2xl my-4">Posts</h2>
    </header>

    {% include "postslist.njk" %}
</section>
Войти в полноэкранный режим Выйти из полноэкранного режима
// _includes/postslist.njk

<div>
    {% for post in posts | reverse %}
        <article class="first:mt-0 first:pt-0 mt-6 pt-6 border-t border-gray-300 first:border-none">

            {% if post.cover_image %}
                <img class="mb-4 rounded" width="512" height="215" alt="Cover image for {{post.title}}" src="{{post.cover_image}}"/>
            {% endif %}

            <header class="text-sm text-gray-500">
                <time datetime="{{ post.published_at }}">{{ post.published_at  |  readableDate }}</time>
            </header>

            <a href="/posts/{{post.slug}}/" class="mt-2 block">
                <p class="text-xl font-semibold text-gray-900">{{post.title}}</p>
                <p class="mt-3 text-base text-gray-500">{{post.description}}</p>
            </a>

            <footer class="mt-3">
                <a href="/posts/{{post.slug}}/" class="block text-base font-semibold text-indigo-600 hover:text-indigo-500">Read full story</a>
            </footer>
        </article>
    {% endfor %}
</div>
Войти в полноэкранный режим Выход из полноэкранного режима

Как видите, благодаря нашему файлу данных posts.js мы можем получить доступ к переменной posts. Если в вашем аккаунте есть опубликованные посты, то они будут показаны на главной странице.

Используя пермалинки, Eleventy позволяет нам динамически генерировать URL для отдельного поста. API dev.to предоставляет свойство slug. Это название поста, преобразованное в путь URL. Давайте используем это в нашем блоге, чтобы вам не нужно было перенаправляться на dev.to каждый раз, когда пользователь нажимает на превью из статьи. Заставьте пользователей взаимодействовать с вашим контентом, удерживая их на странице.

Добавьте следующий файл post.njk в корень сайта и добавьте его также в массив content в вашем tailwind.config.js.

---
pagination:
  data: posts
  size: 1
  alias: post
permalink: posts/{{ post.slug }}/
type: article
eleventyComputed:
  title: "{{ post.title }}"
  description: "{{ post.description }}"
layout: layouts/base.njk
templateClass: tmpl-post
---

<article class="prose prose-slate max-w-none">
    <time datetime="{{ post.published_at }}" class="text-sm text-gray-500">{{ post.published_at | readableDate }}</time>
    <h1>{{ post.title }}</h1>

    {{ post.body_markdown | markdown | safe }}
</article>
Вход в полноэкранный режим Выход из полноэкранного режима

В этом файле мы используем фильтр markdown. Удалите библиотеку markdown из файла .eleventy.js и добавьте фильтр markdown.

const md = new markdownIt({
    html: true,
    breaks: true,
});

eleventyConfig.addFilter("markdown", (content) => {
    return  md.render(content);
});
Вход в полноэкранный режим Выход из полноэкранного режима

Поскольку уценка, возвращаемая из API, находится в одном свойстве, мы не можем стилизовать каждый элемент по отдельности с помощью Tailwind. К счастью, Tailwind создал плагин, который решает эту проблему. Выполните следующую команду и добавьте плагин в файл конфигурации.

npm install -D @tailwindcss/typography

// tailwind.config.js

plugins: [
    require('@tailwindcss/typography'),
],
Войти в полноэкранный режим Выйти из полноэкранного режима

В этом файле также используется фильтр readableDate. Замените фильтр на следующий:

eleventyConfig.addFilter("readableDate", dateObj => {
    if(dateObj) {
        return DateTime.fromISO(dateObj, {zone: 'utc'}).toFormat("dd LLL yyyy");
    }

    return DateTime.now().toFormat("dd LLL yyyy");
});
Войти в полноэкранный режим Выйти из полноэкранного режима

Обновите страницу. Выглядит неплохо, не так ли? Плагин форматирует HTML, который вы не можете контролировать, используя класс prose.

Производительность

Производительность блога уже хорошая благодаря статической генерации сайта, но ее еще можно улучшить. HTML и CSS пока не минифицированы. Давайте начнем с HTML.

npm install html-minifier

Импортируйте пакет в конфиг eleventy.

Добавьте следующее преобразование в конфиг и адаптируйте его под свои нужды.

// .eleventy.js

eleventyConfig.addTransform("htmlmin",  function(content,  outputPath) {
    if(outputPath && outputPath.endsWith(".html")) {
        let minified = htmlmin.minify(content, {
            useShortDoctype: true,
            removeComments: true,
            collapseWhitespace: true
        });
        return minified;
    }

    return content;
});
Войти в полноэкранный режим Выйти из полноэкранного режима

Вот и все! Все HTML-файлы на выходе минифицированы.

Следующий шаг — CSS. Здесь все немного иначе, потому что мы используем Tailwind с PostCSS. К счастью, Tailwind предоставляет оптимизацию для производственной страницы. Давайте начнем с установки cssnano.

npm install cssnano

Обновите конфигурацию:

// postcss.config.js

module.exports = {
    plugins: {
        tailwindcss: {},
        autoprefixer: {},
        ...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {})
    }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Стандартная сборка Eleventy — это сборка, готовая к производству, но она не запустит cssnano, потому что окружение ноды не находится в производстве. Есть несколько способов исправить это. Один из них — создать производственный скрипт. В файл package.json добавьте следующий скрипт "build:prod": "NODE_ENV=production npx @11ty/eleventy" Каждый раз, когда вы запускаете npm run build:prod, сайт будет собираться с минифицированным CSS.

Хорошо, минификация выполнена. Что еще? Знаете ли вы, что HTML, CSS и JS можно передавать в сжатом виде? Многие сайты до сих пор используют gzip, но есть еще Brotli. Brotli специально создан для веб и в большинстве случаев сжимает намного лучше, чем gzip.

К счастью, я развертываю свой блог на Netlify, который поддерживает Brotli по умолчанию!

Завершение

Спасибо, что прочитали. Надеюсь, вы нашли его полезным. Структура проекта могла бы быть намного лучше, и это также можно регулировать с помощью Eleventy.

Мой блог останется с открытым исходным кодом и будет улучшаться со временем. Если структура проекта не соответствует структуре блога, пожалуйста, проверьте историю коммитов.

Ниже перечислены некоторые проблемы, с которыми вы можете столкнуться. Если у вас есть вопросы, не стесняйтесь их задавать.

Дублированный контент

Во время работы над моим блогом коллега заметил, что я создаю дублированный контент, публикуя его на dev.to и в своем блоге. Это влияет на SEO. К счастью, с помощью тега ссылки вы можете заставить Google узнать о дублированном контенте.

<link rel="canonical" href="https://iamjustin.dev/slug-from-article-here"/>

Dev.to поддерживает канонический URL. Это позволяет мне сохранить трафик на dev.to без ущерба для моего блога.

Статические динамические данные?

Вы можете заметить, что пост не обновляется после развертывания. Если я сделаю обновление статьи на dev.to, оно не будет отражено в моем блоге. Сначала необходимо выполнить этап сборки, чтобы данные были получены и сгенерированы снова.

Можно избежать этой проблемы, создав cronjob через GitHub Action, который каждую ночь развертывает Netlify.

Оцените статью
devanswers.ru
Добавить комментарий