Поскольку я пытаюсь построить писать привычку, ну, я пишу все больше и больше. Хотя я использую издательские блоги, такие как Средний , 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, но и вносить хорошее представление и документацию всех данных проекта. Данные контракты действительно хорошая вещь.
- Инструментальное мышление. Это одна из вещей, которые я действительно люблю о программировании. Создайте инструменты для автоматизации повторяющихся задач и облегчают жизнь.
Я надеюсь, что это было хорошее чтение! Продолжайте учиться и кодировать!
Этот пост был изначально опубликован в моем блоге Отказ
Ресурсы
- Инструменты для публикации: исходный код
- Думать в контрактах данных
- Teamscript Shartings
- Закрытия, карри и крутые абстракции
- Узнать реагировать, создавая приложение
Оригинал: “https://www.freecodecamp.org/news/automating-my-blog-posts-publishing-process-with-typescript/”