Блог в формате Markdown с помощью EJS

Эта статья посвящена созданию блога в формате Markdown с помощью EJS.

Введение

Написать содержимое поста с помощью HTML не так просто, как с помощью Markdown.
С тех пор, как я вернулся к истокам статического сайта, я пишу содержание своих постов с помощью Markdown.
Через некоторое время после этого я открыл для себя Deta Cloud и смог попробовать все, что я узнал о Node.js, на живом сервере.
Мой любимый язык шаблонов — EJS, и я хотел создать простой блог, объединив содержимое файла Markdown с шаблоном EJS.
После множества исследований и попыток и неудач, я понял механизм, необходимый для достижения моей цели.
Я нашел несколько руководств по этой теме, и это руководство вдохновлено последним, на которое я наткнулся, Building A Markdown Blog App with Express and EJS, с некоторыми улучшениями и всеми деталями.

Требования

Для выполнения нашего волшебного заклинания нам понадобятся следующие пакеты :

  1. EJS, для шаблонизации нашего приложения
  2. Express, веб-фреймворк для Node.js
  3. gray-matter, для разбора основного текста из файлов Markdown
  4. markdown-it, для разбора содержимого файлов Markdown.

Чтобы установить их одной командой:

npm i ejs express gray-matter markdown-it

Настройка сервера

В ваш главный файл сервера, мой — index.js, поместите следующее:

// /index.js

const express = require("express")
const app = express()

// Built-in module to access and interact with the file system
const fs = require("fs")
// To parse front matter from Markdown files
const matter = require("gray-matter")

app.set("view engine", "ejs")
app.use(express.static("public"))

const getPosts = () => {
    // Get the posts from their directory
    const posts = fs.readdirSync(__dirname + "/views/posts").filter((post) => post.endsWith(".md"))
    // Set the post content as an empty array
    const postContent = []
    // Inject into the post content array the front matter
    posts.forEach((post) => {
        postContent.push(matter.read(__dirname + "/views/posts/" + post))
    })

    /**
     * 1- Return a list of posts as a two dimensional array containing for each one :
     * . the post filename with it's extension (e.g : postFilename.md)
     * . the post content as an object {content:"Markdown content as a string", data:{front matter}, excerpt:""}
     * 2- Return each array as an object and create a Date instance from it's date front matter
     * 3- Sort posts by publication's date in descending order (newest to oldest)
     */
    const postsList = posts
        .map(function (post, i) {
            return [post, postContent[i]]
        })
        .map((obj) => {
            return { ...obj, date: new Date(obj[1].data.date) }
        })
        .sort((objA, objB) => Number(objB.date) - Number(objA.date))

    return postsList
}

// Render the list of posts on the main route
app.get("/", (req, res) => {
    res.render("postsList", {
        posts: getPosts(),
    })
})

// Using a route parameter to render each post on a route matching it's filename
app.get("/posts/:post", (req, res) => {
    const postTitle = req.params.post // Get the Markdown filename

    // Read the Markdown file and parse it's front matter
    const post = matter.read(__dirname + "/views/posts/" + postTitle + ".md")

    // Convert the Markdown file content to HTML with markdown-it
    const md = require("markdown-it")({ html: true }) // Allows HTML tags inside the Markdown file
    const content = post.content // Read the Markdown file content
    const html = md.render(content) // Convert the Markdown file content to HTML

    // Render the postsTemplate for each post and pass it's front matter as a data object into postsTemplate
    res.render("postsTemplate", {
        title: post.data.title,
        date: post.data.date,
        postContent: html,
    })
})

// Launching the application on port 3000
app.listen(3000, () => {
    console.log(`App 🚀 @ http://localhost:3000`)
})
Войти в полноэкранный режим Выйти из полноэкранного режима

Как вы можете видеть, все подробно объяснено.
Nota bene : Я напрямую использую папку views в качестве местоположения файлов шаблона, нет необходимости объявлять ее, Express определяет ее по умолчанию, а файлы Markdown находятся в папке views в другой папке под названием posts.

Я хочу обратить ваше внимание на один конкретный момент.
Когда мы создаем postTemplate и передаем Markdown front matter и контент как объект данных, мы можем добавлять и передавать столько пар key: value, сколько захотим, но мы не можем вызвать неопределенный key внутри postTemplate!
Таким образом, если вы добавите description: my post description во front matter Markdown файла, вы не сможете вызвать его непосредственно внутри postTemplate без добавления в объект данных.
Nota bene : Нет необходимости объявлять расширение .ejs для файла шаблона, Express определяет его по умолчанию.

Рендеринг фронтенда

Как вы видели в index.js, я вывожу список постов на главном маршруте из шаблона postsList.ejs. Добавьте в этот файл следующее :

<!-- /views/postsList.ejs -->
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Home | My blog</title>
    </head>
    <body>
        <h1>
            Welcome to my blog
            <br />
            List of recent posts
        </h1>
        <% posts.forEach(post => { %>
        <!-- Get the Markdown filename without it's extension -->
        <% const postFilename = post[0].replace(/.[^/.]+$/, "") %>
        <!-- Get the Markdown post title from it's front matter -->
        <% const postTitle = post[1].data.title %>
        <!-- Render the title as a link to the post -->
        <h2><a href="/posts/<%= postFilename %>"><%= postTitle%></a></h2>
        <% }) %>
    </body>
</html>
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь каждый пост имеет ту же структуру, один файл шаблона под названием postsTemplate.ejs. Добавьте в этот файл следующее :

<!-- /views/postsTemplate.ejs -->
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title><%= title %> | My blog</title>
    </head>
    <body>
        <h1><%= title %></h1>
        <p><%= date %></p>
        <div><%- postContent %></div>
    </body>
</html>
Войти в полноэкранный режим Выйти из полноэкранного режима

Все готово, теперь мы можем писать наши посты с помощью Markdown в папке views под папкой posts 🥳.

Я создал два файла, чтобы вы могли увидеть результат, если попробуете это сделать:

---
title: "My first article"
date: 2022/07/23
---

This is the content of my first article

<!--- /views/posts/my-first-article.md -->
Вход в полноэкранный режим Выход из полноэкранного режима
---
title: "A second post"
date: 2022/07/25
---

Here goes the content of my second post

<!--- /views/posts/a-second-post.md -->
Войти в полноэкранный режим Выход из полноэкранного режима

Структура приложения выглядит как следующее дерево :

// App's structure without the node_modules folder
├── index.js
├── package-lock.json
├── package.json
└── views
  ├── posts
  │  ├── a-second-post.md
  │  └── my-first-article.md
  ├── postsList.ejs
  └── postsTemplate.ejs
Вход в полноэкранный режим Выход из полноэкранного режима

Я надеюсь, что это руководство будет полезным для всех, кто пытается создать Markdown блог с EJS.

Если у вас возникли вопросы, не стесняйтесь.

Спасибо, что прочитали до сих пор 💗

SYA,
LebCit

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