Создайте отличный генератор статических сайтов и блог на основе Markdown

Мой блог был построен с использованием GatsbyJS, замечательного генератора статических сайтов для React. Что ж, он был построен с использованием Гэтсби, но в итоге я переключился на этот проект, как вы узнаете в конце. Gatsby было довольно легко подобрать, и все, что мне нужно было сделать, это настроить gatsby-blog-starter, чтобы получить что-то отличное.

Но мне было любопытно, как работает генератор, поэтому я решил попробовать создать простой генератор статических сайтов с голыми костями, используя Node.js. Не стесняйтесь следить за кодом на GitHub. Так что давайте перейдем к делу.

Почему простые генераторы статических сайтов?

Мне очень нравятся генераторы статических сайтов, потому что они позволяют использовать любой тяжелый / раздутый фреймворк, который вы хотите, но конечный результат все равно будет простым и легким HTML и CSS. Это дает ощущение свободы, которого у нас обычно не было бы, если бы мы работали, например, с приложением create-react-app.

Только для этого проекта посмотрите оценки Lighthouse:

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

Настройка

Итак, приступим! Откройте командную строку, перейдите туда, где вы хотите создать свой проект, а затем используйте следующие команды для создания нового проекта Node.js (это для Windows, но я уверен, что вы можете перевести их в свою ОС. ):

mkdir node-ssg && cd node-ssg
npm init -y

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

  • front-matter за извлечение основной темы YAML из наших сообщений.
  • marked для преобразования Markdown в HTML.
  • highlight.js для выделения синтаксиса в коде.

Мы можем установить все это с помощью следующей команды:

npm i front-matter marked highlight.js

Хорошо, теперь мы можем начать наш проект.

Скрипт сборки

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

Прежде всего, давайте создадим сценарий, который будет запускаться при вызове build. Мы поместим весь наш исходный код в каталог src, так что сделайте это в корне вашего проекта. Затем откройте проект в своем любимом редакторе кода (я использую VS Code) и добавьте файл JavaScript с именем index.js. Теперь мы можем добавить наш build скрипт к нашему package.json, просто используя node для запуска нашего index.js файла. Ваш package.json теперь должен выглядеть так:

Большой! Теперь мы можем вызвать npm run build в нашем проекте, и он запустит наш index.js файл. Единственная проблема в том, что наш файл пока ничего не делает.

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

Принимаемые решения

Хорошо, перед тем как начать, вам нужно принять множество решений. Например, как хранить сообщения? Должны ли они храниться в отдельной папке или просто как .md файл? Где вы храните изображения? И многое другое.

Но поскольку объем этого проекта не очень велик, я собираюсь использовать очень простое файловое дерево. Все сообщения будут храниться в каталоге содержимого в виде файлов Markdown (.md), а другие ресурсы (например, изображения) могут храниться в ./public/assets/. Эти решения были приняты, чтобы упростить чтение и запись файлов для этого конкретного сценария, но вы всегда можете изменить их на то, что лучше подходит для вашего проекта.

Файл config.js

Мы можем поместить принятые нами решения в config.js файл, чтобы мы могли получить к нему доступ из любого места, просто потребовав его. Я поместил их в объект dev, потому что есть другие свойства, которые мы добавим позже. Вот как это выглядит сейчас:

Получение сообщений

Хорошо, давайте начнем с получения всех сообщений из каталога содержания. Мы можем сделать это с помощью fs API, который предоставляет нам Node.js. Итак, прежде всего, мы импортируем fs и создаем его экземпляр:

const fs = require(“fs”);

Теперь мы можем использовать методы, которые предоставляет fs в этом синтаксисе: fs.methodName(). Для получения сообщений мы можем использовать метод readdirSync(), который предоставляет fs. Итак, давайте посмотрим, как бы это выглядело, если бы мы просто получили все сообщения и занесли их в консоль:

Теперь запустите npm run build в консоли, и вы должны увидеть список сообщений, если вы все сделали правильно. Причина, по которой мы используем slice() в коде, состоит в том, чтобы избавиться от расширения .md. Позже вы поймете, почему мы должны это сделать.

Анализ разметки публикации

Если вы помните, в начале мы установили пакет NPM под названием front-matter. Это помогает нам извлекать содержимое YAML из файлов. Что такое фронт-дело YAML? Что ж, это удивительная вещь, которая позволяет вам добавлять дополнительные данные YAML в ваши файлы, используя — - до и после них, чтобы отделить их от остального контента. Вот пример сообщения в блоге с использованием фронтальной части YAML:

---
title: Post One
date: “1583826864020”
description: My reasons for starting a blog.
---
# This is an amazing blog post.
Really it’s just great

Поскольку мы получили сообщения на предыдущем шаге, теперь мы можем проанализировать их с помощью front-matter. Мы собираемся поместить весь этот связанный с публикацией код в posts.js, чтобы у нас была более чистая рабочая среда. Итак, давайте начнем с получения содержания из наших файлов.

Мы можем сделать это с помощью предоставленного метода fs.readFile(). Вот как это будет выглядеть, если просто записать содержимое файла в консоль:

console.log(fs.readFileSync(“./foo.md”))

Но поскольку нам нужен код многократного использования, который мы можем использовать для каждой отдельной публикации в цикле, мы поместим его в функцию с именем createPost(). Эта функция будет использовать front-matter, чтобы взять содержимое файла и передать нам объект. Этот объект будет иметь основные свойства, которые мы установили в свойстве с именем attributes, а остальное содержимое будет в свойстве с именем body. Мы можем использовать front-matter, создав для него экземпляр с помощью require, а затем вызывая его для наших данных, как только мы прочитаем его из файла.

Вот как это будет выглядеть:

Если вы посмотрите код, то увидите, что я звоню marked в теле нашего сообщения. Все, что это делает, - это преобразование Markdown в HTML, чтобы мы могли легко отобразить его на нашем веб-сайте позже. Я также добавил путь к записи в качестве дополнительного свойства, потому что он нам понадобится позже.

Теперь давайте воспользуемся этим методом в index.js и просто запишем вывод:

Настройка пометок и подсветки синтаксиса

Поскольку мы хотели бы использовать highlight.js для выделения нашего кода, мы можем сделать это, используя marked и его объект конфигурации. Создайте файл с именем marked.js, и в нем мы создадим экземпляр marked, настроим его и затем экспортируем. Вот как это выглядит:

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

Создание HTML-страниц для сообщений

Теперь мы начнем с генерации самой страницы. Для начала мы хотим создать общую папку. Если его еще нет, мы можем сделать это с помощью функций fs.mkdirSync() и fs.existsSync(). Добавим это в наш index.js файл:

if (!fs.existsSync(config.dev.outdir)) fs.mkdirSync(config.dev.outdir);

Теперь давайте создадим в нашем posts.js файле функцию createPosts(), которая будет создавать и записывать файлы HTML в общий каталог. Но перед этим нам нужна вспомогательная функция с именем posthtml, которая будет принимать объект JSON сообщения и возвращать полную HTML-страницу, которую мы можем просто записать в файл. Мы воспользуемся мощью шаблонных литералов, чтобы облегчить себе жизнь в этой функции. Вот как это выглядит:

Причина, по которой я создаю new Date() при добавлении даты в сообщение, заключается в том, что все даты имеют согласованный формат. Это довольно самоуверенный способ сделать это, поскольку он требует, чтобы дата, указанная во вступительной части, была «числом, представляющим миллисекунды, прошедшие с эпохи UNIX». Тем не менее, я не возражаю, чтобы быстро Date.now() в инструментах разработчика браузера получить этот номер перед публикацией. Вы можете изменить это в коде, если хотите.

Теперь мы можем создать функцию с именем createPosts(), которая будет принимать выходные данные функции createPost() и генерировать файл HTML. Вот как это выглядит:

Как видите, он не создает файл с именем postname.html, а создает каталог с именем postname, а затем добавляет в этот каталог index.html, так что путь для этого сообщения в браузере будет yourwebsite/postname, а не yourwebsite/postname.html.

Теперь вызовем его в index.js и посмотрим, сработало ли оно:

Если все работает правильно, вы должны увидеть всплывающий общедоступный каталог с несколькими каталогами в нем (в зависимости от того, сколько у вас было сообщений).

О разделе

Этот блог также будет включать небольшой раздел «О нас» на своей домашней странице для автора, поэтому нам нужно добавить информацию об этом в наш config.js файл. Итак, вот наш исправленный config.js файл:

Домашняя страница

Домашней страницей будет файл index.html в общедоступном каталоге. Он должен иметь заголовок с названием блога и небольшой раздел «Об авторе». Мы можем использовать шаблонные литералы, как и раньше, чтобы сгенерировать для этого HTML. Давайте вызовем функцию homepage() и поместим ее в файл с именем homepage.js. Вот как теперь выглядит этот файл:

Теперь нам нужно создать файл, чтобы мы могли добавить в него этот HTML-код. Мы можем сделать это функцией с именем addHomepage(), а также добавить ее в тот же файл. Вот как это выглядит:

Теперь мы можем просто экспортировать его, используя module.exports = addHomePage, и вызвать его в нашем index.js файле. Вот наш исправленный index.js файл:

Как видите, я также отсортировал сообщения по последней дате, так что последнее сообщение было первым.

Каталог активов

Мы можем хранить любые файлы, которые не должны касаться генератора, в ./public/assets. Например, если вы хотите добавить стиль в этот блог, вы можете добавить в свою homepage функцию следующее:

<link rel=”stylesheet” href=”./assets/main.css” />

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

Here’s an image:
![Wow look at this beautiful thing](../assets/images/wow.png)

Сделать красивый вид

Ах! Теперь о моей любимой части: пришло время сделать так, чтобы это выглядело красиво. Не знаю, как вы, но мне было очень больно смотреть на эти стили HTML по умолчанию. Чтобы упростить себе жизнь, я просто подключу гротеск к проекту и настрою его. Вот файл ./public/assets/styles/main.css:

Как видите, в этом проекте я решил использовать плавный шрифт. Я также ввел grotesk.light.scss и настроил переменные. Вот как теперь выглядят переменные:

Я также настроил файл fonts.scss, поставляемый с grotesk. Вот как это выглядит сейчас:

Как видите, я импортировал два шрифта для этого блога: Lyon Display (размещается локально) и EB Garamond (шрифт Google).

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

Хостинг

Мне лично нравится использовать ZEIT Now для хостинга, но мне также нравятся некоторые другие бесплатные опции: Netlify и GitHub Pages. Поскольку Now так хорошо интегрируется со сценариями сборки NPM, которые выводятся в общедоступный каталог, все, что мне нужно было сделать, это запустить now — prod в корне каталога (когда вы запустите его в первый раз, он задаст вам некоторые вопросы конфигурации, на которые ответ по умолчанию - хорошо). Теперь каждый раз, когда я хочу обновить свой блог, все, что мне нужно запустить, это снова now — prod, и он обновит мой блог, запустив npm run build и разместив его.

Последние мысли

Спасибо, что прочитали эту очень длинную статью. Надеюсь, вы кое-что узнали о Node.js. Я лично много узнал об API fs, и мне очень понравилось это делать. Мне это так понравилось, что я фактически переключил свой личный блог с Гэтсби на это. Это может быть плохое решение, но я всегда смогу решить его позже. Напоминаем: вы можете найти весь исходный код на GitHub, так что не стесняйтесь его разветвлять или открывать проблему, если вы обнаружите, что что-то не так.

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