Рубрики
Без рубрики

Как автоматизировать процесс публикации вашего блога в TypeScript

Поскольку я пытаюсь построить писать привычку, ну, я пишу все больше и больше. Несмотря на то, что я использую публикацию блогов, таких как Medium, Dev.to, и Hashnode, я люблю публиковать свой контент в своем собственном блоге. Как я хотел построить простой веб-сайт, этот блог в основном

Поскольку я пытаюсь построить писать привычку, ну, я пишу все больше и больше. Хотя я использую издательские блоги, такие как Средний , dev.to и Hashnode. Мне нравится публиковать свой контент на Мой собственный блог также.

Как я хотел построить простой веб-сайт, этот блог в основном HTML и CSS с очень маленьким JavaScript. Но дело в том, что мне нужно было улучшить процесс публикации.

Так как это работает сейчас?

Я управляю RoadMap блога по понятию. Похоже, это выглядит:

Это простой тип доски канбана. Мне нравится эта доска, потому что я могу получить все свои идеи в физическое (или цифровое?) Представление. Я также использую его, чтобы построить черновик, польский проект и сделать его лучше и лучше, а затем опубликовать его в блоге.

Поэтому я пишу свой блог, используя идею. После того, как я заканчиваю его, я копирую запись, написав и вставьте его в онлайн-инструмент для преобразования уценки в HTML. И тогда я могу использовать этот HTML для создания фактического поста.

Но это просто тело, контент для страницы. Мне всегда нужно создать весь HTML с содержанием головы, телом и нижним колонтитулом.

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

Функции

Моя основная идея должна была иметь целую статью HTML, готовую опубликовать. Как я упоминал ранее, и

Разделы не меняются. Итак, я мог бы использовать тех, кто как «шаблон».

С помощью этого шаблона у меня есть данные, которые могут измениться для каждой статьи, которую я пишу и публикую. Эти данные являются переменной в шаблоне с этим представлением {{variabblename}} Отказ Пример:

{{ title }}

Теперь я могу использовать шаблон и заменить переменные с помощью реальных данных – конкретную информацию для каждой статьи.

Вторая часть – это тело, настоящий пост. В шаблоне он представлен {{статья}} Отказ Эта переменная будет заменена HTML, сгенерированным пометку понятия.

Когда мы копируем и вставьте заметки от понятия, мы получаем вид стиль уценки. Этот проект преобразует эту разметку в HTML и использовать его как Статья переменная в шаблоне.

Чтобы создать идеальный шаблон, я посмотрел все переменные, которые мне нужно было создать:

  • заглавие
  • описание
  • Дата
  • теги
  • Ivealt
  • воплощение
  • Фотографурл
  • Фотограпежмерием
  • статья
  • ключевые слова

С этими переменными я создал Шаблон Отказ

Чтобы пройти часть этой информации, чтобы построить HTML, я создал JSON Файл в качестве конфигурации статьи: article.config.json Отказ Там у меня есть что-то подобное:

{
  "title": "React Hooks, Context API, and Pokemons",
  "description": "Understanding how hooks and the context api work",
  "date": "2020-04-21",
  "tags": [
    "javascript",
    "react"
  ],
  "imageAlt": "The Ash from Pokemon",
  "photographerUrl": "",
  "photographerName": "kazuh.yasiro",
  "articleFile": "article.md",
  "keywords": "javascript,react"
}

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

Шаблон первым:

const templateContent: string = await getTemplateContent();

Поэтому мы в основном должны реализовать getTemplateContent функция.

import fs, { promises } from 'fs';
import { resolve } from 'path';

const { readFile } = promises;

const getTemplateContent = async (): Promise => {
  const contentTemplatePath = resolve(__dirname, '../examples/template.html');
  return await readFile(contentTemplatePath, 'utf8');
};

решить с __dirname Получите абсолютный путь к каталогу из исходного файла, который работает. А потом иди к Примеры/template.html файл. readfile Будет лисинхронно читать и вернуть контент с пути шаблона.

Теперь у нас есть контент шаблона. И нам нужно сделать то же самое для конфигурации статьи.

const getArticleConfig = async (): Promise => {
  const articleConfigPath = resolve(__dirname, '../examples/article.config.json');
  const articleConfigContent = await readFile(articleConfigPath, 'utf8');
  return JSON.parse(articleConfigContent);
};

Здесь происходит два разных веща:

  • Как article.config.json Имеет формат JSON, нам нужно преобразовать эту строку JSON в объект JavaScript после прочтения файла
  • Возвращение контента конфигурации статьи будет Articleconfig Как я определил в функции возвращаемого типа. Давайте построим это.
type ArticleConfig = {
  title: string;
  description: string;
  date: string;
  tags: string[];
  imageCover: string;
  imageAlt: string;
  photographerUrl: string;
  photographerName: string;
  articleFile: string;
  keywords: string;
};

Когда мы получаем этот контент, мы также используем этот новый тип.

const articleConfig: ArticleConfig = await getArticleConfig();

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

templateContent.replace('title', articleConfig.title)

Но некоторые переменные появляются более одного раза в шаблоне. Regex к спасению. С этим:

new RegExp('\\{\\{(?:\\\\s+)?(title)(?:\\\\s+)?\\}\\}', 'g');

… я получаю все строки, которые соответствуют {{title}} Отказ Таким образом, я могу построить функцию, которая получает параметр, который можно найти и использовать его в заголовом месте.

const getPattern = (find: string): RegExp =>
  new RegExp('\\{\\{(?:\\\\s+)?(' + find + ')(?:\\\\s+)?\\}\\}', 'g');

Теперь мы можем заменить все матчи. Пример для переменной заголовка:

templateContent.replace(getPattern('title'), articleConfig.title)

Но мы не хотим заменять только переменную заголовка, но все переменные из конфигурации статьи. Заменить все!

const buildArticle = (templateContent: string) => ({
  with: (articleConfig: ArticleAttributes) =>
    templateContent
      .replace(getPattern('title'), articleConfig.title)
      .replace(getPattern('description'), articleConfig.description)
      .replace(getPattern('date'), articleConfig.date)
      .replace(getPattern('tags'), articleConfig.articleTags)
      .replace(getPattern('imageCover'), articleConfig.imageCover)
      .replace(getPattern('imageAlt'), articleConfig.imageAlt)
      .replace(getPattern('photographerUrl'), articleConfig.photographerUrl)
      .replace(getPattern('photographerName'), articleConfig.photographerName)
      .replace(getPattern('article'), articleConfig.articleBody)
      .replace(getPattern('keywords'), articleConfig.keywords)
});

Теперь я заменяю все! Мы используем это так:

const article: string = buildArticle(templateContent).with(articleConfig);

Но мы пропускаем две части здесь:

  • теги
  • статья

В файле config json, Теги это список. Итак, для списка:

['javascript', 'react'];

Final HTML будет:

Итак, я создал еще один шаблон: tag_template.html с {{tag}} Переменная. Нам просто нужно сопоставить Теги Список и создайте каждый шаблон HTML-тега.

const getArticleTags = async ({ tags }: { tags: string[] }): Promise => {
  const tagTemplatePath = resolve(__dirname, '../examples/tag_template.html');
  const tagContent = await readFile(tagTemplatePath, 'utf8');
  return tags.map(buildTag(tagContent)).join('');
};

Мы тут:

  • Получить путь шаблона тега
  • Получить содержимое шаблона тега
  • карта через Теги и построить окончательный тег HTML на основе шаблона тега

buildtag это функция, которая возвращает другую функцию.

const buildTag = (tagContent: string) => (tag: string): string =>
  tagContent.replace(getPattern('tag'), tag);

Это получает TagContent – Это содержимое шаблона тегов – и возвращает функцию, которая принимает тег и создает окончательный тег HTML. И теперь мы называем это, чтобы получить теги статьи.

const articleTags: string = await getArticleTags(articleConfig);

О статье сейчас. Похоже, это выглядит:

const getArticleBody = async ({ articleFile }: { articleFile: string }): Promise => {
  const articleMarkdownPath = resolve(__dirname, `../examples/${articleFile}`);
  const articleMarkdown = await readFile(articleMarkdownPath, 'utf8');
  return fromMarkdownToHTML(articleMarkdown);
};

Это получает Статьи Мы пытаемся получить путь, прочитайте файл и получите содержимое отметки. Затем пройдите этот контент в frommarkdowntohtml Функция для преобразования уценки в HTML.

Для этой части я использую внешнюю библиотеку под названием Showdown Отказ Он обрабатывает каждый маленький угловой корпус для преобразования уценки в HTML.

import showdown from 'showdown';

const fromMarkdownToHTML = (articleMarkdown: string): string => {
  const converter = new showdown.Converter()
  return converter.makeHtml(articleMarkdown);
};

И теперь у меня есть теги и статья HTML:

const templateContent: string = await getTemplateContent();
const articleConfig: ArticleConfig = await getArticleConfig();
const articleTags: string = await getArticleTags(articleConfig);
const articleBody: string = await getArticleBody(articleConfig);

const article: string = buildArticle(templateContent).with({
  ...articleConfig,
  articleTags,
  articleBody
});

Я пропустил еще одну вещь! Раньше я ожидал, что мне всегда нужно добавить путь к оболочке изображения в файл конфигурации статьи. Что-то вроде этого:

{
  "imageCover": "an-image.png",
}

Но мы могли бы предположить, что имя изображения будет крышка . Задача была расширением. Это может быть .PNG , .jpg. , .jpeg или .gif Отказ

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

Я начал с «существующей» части.

fs.existsSync(`${folder}/${fileName}.${extension}`);

Здесь я использую Existsync Функция, чтобы найти файл. Если он существует в папке, он возвращает true. В противном случае ложь.

Я добавил этот код в функцию:

const existsFile = (folder: string, fileName: string) => (extension: string): boolean =>
  fs.existsSync(`${folder}/${fileName}.${extension}`);

Почему я сделал это таким образом?

Используя эту функцию, мне нужно пройти Папка , Имя файла и расширение . Папка и Имя файла всегда одинаковы. Разница в Расширение Отказ

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

const hasFileWithExtension = existsFile(examplesFolder, imageName);

hasFileWithExtension('jpeg'); // true or false
hasFileWithExtension('jpg'); // true or false
hasFileWithExtension('png'); // true or false
hasFileWithExtension('gif'); // true or false

Вся функция выглядела так:

const getImageExtension = (): string => {
  const examplesFolder: string = resolve(__dirname, `../examples`);
  const imageName: string = 'cover';
  const hasFileWithExtension = existsFile(examplesFolder, imageName);

  if (hasFileWithExtension('jpeg')) {
    return 'jpeg';
  }

  if (hasFileWithExtension('jpg')) {
    return 'jpg';
  }

  if (hasFileWithExtension('png')) {
    return 'png';
  }

  return 'gif';
};

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

enum ImageExtension {
  JPEG = 'jpeg',
  JPG = 'jpg',
  PNG = 'png',
  GIF = 'gif'
};

И функция теперь использует наш новый Enum Imageexextence. :

const getImageExtension = (): string => {
  const examplesFolder: string = resolve(__dirname, `../examples`);
  const imageName: string = 'cover';
  const hasFileWithExtension = existsFile(examplesFolder, imageName);

  if (hasFileWithExtension(ImageExtension.JPEG)) {
    return ImageExtension.JPEG;
  }

  if (hasFileWithExtension(ImageExtension.JPG)) {
    return ImageExtension.JPG;
  }

  if (hasFileWithExtension(ImageExtension.PNG)) {
    return ImageExtension.PNG;
  }

  return ImageExtension.GIF;
};

Теперь у меня есть все данные, чтобы заполнить шаблон. Большой!

Поскольку HTML сделан, я хочу создать реальный HTML-файл с этими данными. Я в основном нужно получить правильный путь, HTML и использовать WeightFile Функция для создания этого файла.

Чтобы получить путь, мне нужно было понять образец моего блога. Он организует папку с года, месяц, заголовок и файл называется index.html Отказ

Пример будет:

2020/04/publisher-a-tooling-to-blog-post-publishing/index.html

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

Но еще одна интересная идея состояла в том, чтобы сделать вывод пути некоторых данных, которые мы уже имеем в файле конфигурации статьи. У нас есть Дата (например "2020-04-21" ) и заглавие (e.g. "Издатель: инструмент для автоматизации публикации в блоге" ).

С даты я могу получить год и месяц. Из названия я могу генерировать папку статьи. index.html Файл всегда постоянен.

Строка выглядела так:

`${year}/${month}/${slugifiedTitle}`

На дату это действительно просто. Я могу разделить на - и разрушение:

const [year, month]: string[] = date.split('-');

Для SlugiedTitle Я построил функцию:

const slugify = (title: string): string =>
  title
    .trim()
    .toLowerCase()
    .replace(/[^\\w\\s]/gi, '')
    .replace(/[\\s]/g, '-');

Он удаляет белые пробелы с самого начала и конца струны. Затем ничего не входит в строку. Затем удалите все специальные символы (сохраните только символы Word и WhiteSpace). И, наконец, замените все пробелы с помощью - Отказ

Вся функция выглядит так:

const buildNewArticleFolderPath = ({ title, date }: { title: string, date: string }): string => {
  const [year, month]: string[] = date.split('-');
  const slugifiedTitle: string = slugify(title);

  return resolve(__dirname, `../../${year}/${month}/${slugifiedTitle}`);
};

Эта функция пытается получить папку «Статья». Это не генерирует новый файл. Вот почему я не добавил /index.html до конца последней строки.

Почему это сделало это? Потому что, прежде чем писать новый файл, нам всегда нужно создать папку. Я использовал Мкдир С этой папкой путь к его созданию.

const newArticleFolderPath: string = buildNewArticleFolderPath(articleConfig);
await mkdir(newArticleFolderPath, { recursive: true });

И теперь я мог бы использовать папку создать новый файл статьи в нем.

const newArticlePath: string = `${newArticleFolderPath}/index.html`;
await writeFile(newArticlePath, article);

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

Для 2020/04/Publisher-A-A-To-blog-Post-Post-Post/index.html Пример, крышка изображения будет в папке активов:

2020/04/publisher-a-tooling-to-blog-post-publishing/assets/cover.png

Для этого мне нужны две вещи:

  • Создать новый Активы папка с мкдир
  • Скопируйте файл изображения и вставьте его в новую папку с помощью копировать файл

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

Для папки, как у меня NewArticleFolderPath Мне просто нужно объединить этот путь к папке активов.

const assetsFolder: string = `${newArticleFolderPath}/assets`;

Для текущего пути изображения у меня есть ImageCoverFilename с правильным расширением. Мне просто нужно получить путь обложки изображения:

const imageCoverExamplePath: string = resolve(__dirname, `../examples/${imageCoverFileName}`);

Чтобы получить будущий путь Image, мне нужно объединить путь к оболочке изображения и имя файла изображения:

const imageCoverPath: string = `${assetsFolder}/${imageCoverFileName}`;

Со всеми этими данными я могу создать новую папку:

await mkdir(assetsFolder, { recursive: true });

И скопируйте и вставьте файл крышки изображения:

await copyFile(imageCoverExamplePath, imageCoverPath);

Как я реализовал это Пути Часть, я увидела, что могу группировать их все в функцию PaintPaths Отказ

const buildPaths = (newArticleFolderPath: string): ArticlePaths => {
  const imageExtension: string = getImageExtension();
  const imageCoverFileName: string = `cover.${imageExtension}`;
  const newArticlePath: string = `${newArticleFolderPath}/index.html`;
  const imageCoverExamplePath: string = resolve(__dirname, `../examples/${imageCoverFileName}`);
  const assetsFolder: string = `${newArticleFolderPath}/assets`;
  const imageCoverPath: string = `${assetsFolder}/${imageCoverFileName}`;

  return {
    newArticlePath,
    imageCoverExamplePath,
    imageCoverPath,
    assetsFolder,
    imageCoverFileName
  };
};

Я также создал ArticlePaths тип:

type ArticlePaths = {
  newArticlePath: string;
  imageCoverExamplePath: string;
  imageCoverPath: string;
  assetsFolder: string;
  imageCoverFileName: string;
};

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

const {
  newArticlePath,
  imageCoverExamplePath,
  imageCoverPath,
  assetsFolder,
  imageCoverFileName
}: ArticlePaths = buildPaths(newArticleFolderPath);

Последняя часть алгоритма сейчас! Я хотел быстро подтвердить созданный пост. Так что, если я смогу открыть созданный пост на вкладке браузера? Это было бы потрясающе!

Так что я сделал это:

await open(newArticlePath);

Здесь я использую открыть Библиотека для моделирования команды открытия терминала.

И это было!

Что я выучил

Этот проект было очень весело! Я выучил несколько классных вещей через этот процесс. Я хочу перечислять их здесь:

  • Как я Учебное число Я хотел быстро подтвердить код, который я писал. Так что я настроил Номемон Чтобы скомпилировать и запустить код в каждом файле сохранить. Это круто, чтобы сделать процесс разработки настолько динамичным.
  • Я пытался использовать новый узел ФС ‘s обещания : readfile , мкдир , WeightFile и copyfile Отказ Это на Стабильность: 2 Отказ
  • Я сделал много Carrying Для некоторых функций, чтобы сделать их многоразовой.
  • Enums и Типы Являются хорошими способами, чтобы сделать государство последовательное в Teadercript, но и вносить хорошее представление и документацию всех данных проекта. Данные контракты действительно хорошая вещь.
  • Инструментальное мышление. Это одна из вещей, которые я действительно люблю о программировании. Создайте инструменты для автоматизации повторяющихся задач и облегчают жизнь.

Я надеюсь, что это было хорошее чтение! Продолжайте учиться и кодировать!

Этот пост был изначально опубликован в моем блоге Отказ

Мой Twitter и Github Отказ

Ресурсы

Оригинал: “https://www.freecodecamp.org/news/automating-my-blog-posts-publishing-process-with-typescript/”