Автоматизируйте синдикацию вашего контента с помощью Eleventy, dev.to и GitHub Actions

Эта статья была написана как тестовый пост для моего выступления на Eleventy Meetup на прошлой неделе.

Вот сопутствующий слайд-деск: iamdeveloper.com/11tyMeetupMay2022.

Во время выступления я не смог рассказать о себе так подробно, как хотел бы, поэтому эта запись в блоге дополняет выступление.

Вот мой порядок написания записей в блоге. Я создаю и публикую их на сайте dev.to (DEV) с помощью редактора DEV. Каждый вечер в 8 вечера по восточному времени запускается действие на GitHub, которое обновляет разметку моих записей в блоге и связанные с ними активы. Если есть изменения, основная ветка обновляется и начинается развертывание на Netlify.

Давайте разберем весь процесс.

Кэширование

Разметка записи блога

Я вызываю DEV API, который получает все мои записи в блоге. На момент написания статьи функция для этого выглядит следующим образом. Не стесняйтесь заглянуть в полный исходный код.

/**
 * Retrieves the latest blog posts from dev.to.
 *
 * @returns {Promise<object[]>} A promise that resolves to an array of blog posts.
 */
async function getDevPosts() {
  const response = await fetch(DEV_TO_API_URL + '/articles/me/published?per_page=1000', {
    headers: {
      'api-key': DEV_API_KEY,
    },
  });
  const posts = await response.json();

  return posts.filter(isValidPost);
}
Вход в полноэкранный режим Выход из полноэкранного режима

Я отфильтровываю определенные посты с помощью функции isValidPost(post). Я отфильтровываю сообщения для обсуждения, сообщения для кулера и т.д., поскольку мне нравится иметь их на DEV, но не в моем блоге.

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

Манипулирование разметкой и шорткодами

DEV использует жидкие теги для вставки контента в посты блога. Для тех, кому интересно, вот полный список поддерживаемых вложений с помощью жидкого тега DEV {%embed url %}.

Я использую короткие коды в Eleventy, которые имеют тот же синтаксис, что и жидкие теги. В прошлом у DEV были специальные жидкие теги для разных вложений. Например, чтобы вставить репозиторий GitHub, вы использовали жидкий тег {% github url %}. Тег liquid по-прежнему поддерживается, но теперь у них есть общий жидкий тег embed, {% embed url %}, который определяет тип встраивания на основе URL.

В моем проекте есть шорткоды для конкретных вложений, например, {% github url %}, {% twitter url %} и т. д. У меня есть старые посты, которые используют старые жидкие теги DEV, но более новые посты используют жидкий тег {% embed url %}. На моей стороне я манипулирую разметкой, чтобы преобразовать, например, {% embed https://twitter.com/nickytonline/status/1521650477674471424 %} в {% twitter https://twitter.com/nickytonline/status/1521650477674471424 %}.

На данный момент я поддерживаю не все вставки. Например, вставки комментариев и тегов. Одно время у меня был DEV comment embeds, но это оказалось проблематично для встраивания комментариев в Tweets или любой другой embed. Я так мало использовал их в записях блога, что взял за правило создавать гиперссылку на комментарий. Что касается вставки тегов, то я почти не использовал их, поэтому взял за правило не ссылаться на тег в DEV, а если ссылаюсь, то создавать вместо него гиперссылку.

Есть и другие манипуляции с разметкой, о которых я, вероятно, забыл. Теперь разметка записи блога из DEV находится в состоянии, которое может быть использовано Eleventy.

Увеличение ссылок

Вы можете заметить, что во всех моих записях в блоге есть ссылка Boost on DEV, а в некоторых также есть ссылка Boost on Hashnode. Я получил эту идею от Стефани Эклз, отдавая должное заслугам.

Эти ссылки создаются в разметке с помощью шорткода boostLink.

/**
 * Generates markup for a boost on DEV button.
 *
 * @param {string} fileSlug A pages file slug.
 * @param {string} url A pages URL.
 *
 * @returns {string} Markup for a boost links on DEV and Hashnode.
 */
function boostLink(title, fileSlug, url) {
  if (!url.startsWith('/posts/')) {
    return '';
  }

  let hashnodeBoosterLink = '';
  const hashnodeUrl = hashnodeData[fileSlug];

  if (hashnodeUrl) {
    hashnodeBoosterLink =
      `<a href="${hashnodeUrl}" class="boost-link">Boost on Hashnode</a>` +
      hashnodeBoosterLink;
  }

  const intentToTweet = `<a class="boost-link" href="https://twitter.com/intent/tweet?text=${encodeURIComponent(
    `${title} by ${site.authorHandle} ${site.url}${url}`
  )}">Share on Twitter</a>`;

  return `<a href="https://dev.to/nickytonline/${fileSlug}" class="boost-link">Boost on DEV</a>${hashnodeBoosterLink}${intentToTweet}`;
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Исходный код шорткода boostLink на GitHub.

Одним из параметров является slug записи блога. Когда я беру пост из DEV, тот же slug будет использоваться для записи в моем блоге, так что это тривиальная генерация обратной ссылки на DEV. Для Hashnode я в настоящее время импортирую посты DEV с помощью их импортера DEV, поэтому мне нужно изменить некоторые вещи, такие как slug, чтобы он был единым для DEV и моего блога.

Я сохраняю список URL-адресов постов блога Hashnode с помощью RSS-ленты Hashnode, поскольку не все посты из моего блога находятся на Hashnode. Поэтому только некоторые посты имеют ссылку на Hashnode boost.

Изображения

Любые изображения в сообщениях блога, не включенные в мой список пропусков, извлекаются и фиксируются в хранилище. В настоящее время я опускаю только изображения с giphy.com. Все остальное — это мои изображения или изображения Unsplash, которые я приписал автору в соответствии с правилами Unsplash.

Прежде чем загрузить любое изображение, я проверяю, существует ли оно уже в хранилище. Если нет, я загружаю и сохраняю их.

/**
 * Saves a markdown image URL to a local file and returns the new image URL.
 * TODO: Fix mixing two concerns.
 * @param {string} markdownImageUrl
 *
 * @returns {string} Returns the new image URL.
 */
async function saveMarkdownImageUrl(markdownImageUrl = null) {
  let newMarkdownImageUrl = null;

  if (markdownImageUrl) {
    const imageUrl = new URL(markdownImageUrl);
    const imagefilename = imageUrl.pathname.replaceAll('/', '_');
    const localCoverImagePath = path.join(POSTS_IMAGES_DIRECTORY, imagefilename);

    newMarkdownImageUrl = generateNewImageUrl(imageUrl);

    if (!(await fileExists(localCoverImagePath))) {
      console.log(`Saving image ${imageUrl} to ${localCoverImagePath}`);
      await saveImageUrl(markdownImageUrl, localCoverImagePath);
    }
  }

  return newMarkdownImageUrl;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Встроенные статьи

В некоторых своих записях в блоге DEV я даю ссылки на статьи DEV. Они также сохраняются в моем репозитории. Они хранятся в файле embeddedPostsMarkup.json, который я создаю с помощью функции updateBlogPostEmbeds(embeds, filepaths).

async function updateBlogPostEmbeds(embeds, filePaths) {
  let blogPostEmbedsMarkup = {};

  for (const [url] of embeds) {
    // You can't use the dev.to API to grab an article by slug, so we need to use the URL instead
    // to fetch the markup of the article page to extract the article ID.
    // This is only an issue for article embeds.
    const response = await fetch(url);
    const html = await response.text();
    const match = html.match(/data-article-id="(?<blogPostId>.+?)"/);

    if (match) {
      const {blogPostId} = match.groups;
      const {
        body_html,
        body_markdown,
        comments_count,
        public_reactions_count,
        positive_reactions_count,
        ...data
      } = await getDevPost(blogPostId);

      blogPostEmbedsMarkup[url] = data;
    } else {
      throw new Error(`Could not find blog post at ${url}`);
    }
  }

  const data = JSON.stringify(blogPostEmbedsMarkup, null, 2);

  await fs.writeFile(filePaths, data, () =>
    console.log(`Saved image ${imageUrl} to ${imageFilePath}!`)
  );
}
Вход в полноэкранный режим Выход из полноэкранного режима

Источник для updateBlogPostsEmbeds на GitHub.

При фиксации всех файлов в репозитории развертывание начнется, если будут зафиксированы какие-либо изменения.

Весь исходный код является открытым, поэтому не стесняйтесь копировать мой рабочий процесс. 😎

nickytonline / iamdeveloper.com

Исходный код для моего сайта iamdeveloper.com

Добро пожаловать

Это исходный код веб-сайта Ника Тейлора, созданного с помощью генератора статических сайтов Eleventy. Он использует шаблон Hylia Энди Белла. Я вносил в него изменения и, вероятно, буду вносить еще больше со временем, но просто хотел поблагодарить Энди за его потрясающий шаблон.

Команды терминала

Сначала установите зависимости

npm install
Войдите в полноэкранный режим Выйти из полноэкранного режима

Открыть сайт локально в режиме просмотра

npm start
Войти в полноэкранный режим Выйти из полноэкранного режима

Создайте производственную версию сайта

npm run production
Войти в полноэкранный режим Выйти из полноэкранного режима

Локальное тестирование производственного сайта

cd dist
npx serve
Войти в полноэкранный режим Выйти из полноэкранного режима
Просмотр на GitHub

Развертывание

Как только что-то обновляется в основной ветке репозитория, начинается развертывание на Netlify. Именно здесь Eleventy приступает к работе.

Благодаря нашим усилиям по кэшированию, вся разметка находится в состоянии, которое Eleventy теперь может использовать. В настоящее время я использую последнюю Canary версию Eleventy вместе с экспериментальной версией Node 16, если вам интересно, почему я запускаю Eleventy с помощью node directlty.

"production": "NODE_ENV=production node --experimental-fetch node_modules/.bin/eleventy"

Потенциальные улучшения

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

  • Я еще не копался в этом, но иногда действие GitHub выдает ошибку. Сейчас меня это не слишком беспокоит, так как обычно все проходит. Тем не менее, было бы неплохо, если бы он восстанавливался или обрабатывал эту ошибку более изящно.

  • В настоящее время я вручную публикую сообщения блога в Hashnode с помощью функции импорта из DEV, добавляю связанные теги, устанавливаю канонический URL и сохраняю сообщение. Было бы неплохо публиковать их, когда в моем блоге создается новый пост. Они не используют жидкие теги. Они используют другой формат, такой как %[some url], поэтому мне нужно будет обработать это.

  • Было бы неплохо поддерживать больше вкраплений из DEV в моем блоге.

  • Автоматическая публикация моих последних постов в социальных сетях — это было бы здорово, но я пока сомневаюсь. Мне нравится писать эти твиты или короткие сообщения вручную, вместо того, чтобы писать какой-то общий твит или сообщение на LinkedIn.

Если вы дошли до этого момента, вы — молодец.

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