Эта статья посвящена созданию блога в формате Markdown с помощью EJS.
Введение
Написать содержимое поста с помощью HTML не так просто, как с помощью Markdown.
С тех пор, как я вернулся к истокам статического сайта, я пишу содержание своих постов с помощью Markdown.
Через некоторое время после этого я открыл для себя Deta Cloud и смог попробовать все, что я узнал о Node.js, на живом сервере.
Мой любимый язык шаблонов — EJS, и я хотел создать простой блог, объединив содержимое файла Markdown с шаблоном EJS.
После множества исследований и попыток и неудач, я понял механизм, необходимый для достижения моей цели.
Я нашел несколько руководств по этой теме, и это руководство вдохновлено последним, на которое я наткнулся, Building A Markdown Blog App with Express and EJS, с некоторыми улучшениями и всеми деталями.
Требования
Для выполнения нашего волшебного заклинания нам понадобятся следующие пакеты :
- EJS, для шаблонизации нашего приложения
- Express, веб-фреймворк для Node.js
- gray-matter, для разбора основного текста из файлов Markdown
- 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