Для работы с Markdown в приложении на Express часто используют библиотеку Marked. Установка выполняется через npm:
npm install marked
После установки подключение в коде выглядит следующим образом:
const express = require('express');
const { marked } = require('marked');
const fs = require('fs');
const path = require('path');
const app = express();
const PORT = 3000;
Основная задача — преобразовать содержимое Markdown-файла в HTML,
который можно отдать клиенту. Для этого удобно использовать встроенный
модуль fs:
app.get('/page/:name', (req, res) => {
const filePath = path.join(__dirname, 'markdown', `${req.params.name}.md`);
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
res.status(404).send('Файл не найден');
return;
}
const htmlContent = marked(data);
res.send(`
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>${req.params.name}</title>
</head>
<body>
${htmlContent}
</body>
</html>
`);
});
});
В этом примере:
:name для динамического
определения файла Markdown.marked(data) конвертирует Markdown в HTML.Библиотека Marked позволяет тонко настраивать процесс парсинга. Примеры наиболее часто используемых опций:
marked.setOptions({
renderer: new marked.Renderer(), // Пользовательский рендерер
gfm: true, // Поддержка GitHub Flavored Markdown
breaks: true, // Разрывы строк как в GitHub
sanitize: false, // Отключение автоматической очистки HTML
smartLists: true, // Улучшенные списки
smartypants: true // "Умные" кавычки и тире
});
Примечания к опциям:
renderer позволяет переопределять способ генерации
отдельных элементов, таких как ссылки, изображения, заголовки.sanitize больше не является обязательной мерой
безопасности — для безопасного рендеринга рекомендуется использовать
внешние библиотеки вроде DOMPurify.Можно создавать кастомные рендереры для изменения HTML-вывода:
const renderer = new marked.Renderer();
renderer.heading = (text, level) => {
return `<h${level} class="custom-heading">${text}</h${level}>`;
};
renderer.link = (href, title, text) => {
return `<a href="${href}" target="_blank" rel="noopener">${text}</a>`;
};
marked.setOptions({ renderer });
Такой подход позволяет:
Marked поддерживает промисы, что удобно при интеграции с асинхронными источниками данных:
app.get('/async/:name', async (req, res) => {
const filePath = path.join(__dirname, 'markdown', `${req.params.name}.md`);
try {
const data = await fs.promises.readFile(filePath, 'utf8');
const htmlContent = await marked.parse(data);
res.send(htmlContent);
} catch (err) {
res.status(404).send('Ошибка при чтении файла');
}
});
Использование marked.parse вместо синхронного
marked(data) позволяет интегрировать библиотеку в
асинхронные цепочки кода без блокировки сервера.
Marked хорошо сочетается с шаблонизаторами вроде EJS, Pug, Handlebars. Пример с EJS:
app.set('view engine', 'ejs');
app.get('/ejs/:name', async (req, res) => {
const filePath = path.join(__dirname, 'markdown', `${req.params.name}.md`);
try {
const data = await fs.promises.readFile(filePath, 'utf8');
const htmlContent = marked(data);
res.render('template', { content: htmlContent });
} catch (err) {
res.status(404).send('Файл не найден');
}
});
В шаблоне template.ejs:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Документ</title>
</head>
<body>
<%- content %>
</body>
</html>
Использование <%- %> гарантирует, что HTML от
Marked не будет экранирован и корректно отобразится на странице.
Markdown-файлы могут содержать вредоносный HTML. Чтобы защитить приложение:
sanitize с осторожностью, так как
она не всегда полностью блокирует XSS.Если приложение работает с большим количеством статических Markdown-файлов, имеет смысл кешировать HTML:
const cache = {};
app.get('/cached/:name', async (req, res) => {
const name = req.params.name;
if (cache[name]) {
res.send(cache[name]);
return;
}
const filePath = path.join(__dirname, 'markdown', `${name}.md`);
try {
const data = await fs.promises.readFile(filePath, 'utf8');
const htmlContent = marked(data);
cache[name] = htmlContent;
res.send(htmlContent);
} catch (err) {
res.status(404).send('Файл не найден');
}
});
Кеширование значительно уменьшает нагрузку на сервер при частых запросах к одним и тем же страницам.
Для больших приложений удобно выделять отдельный роутер для работы с Markdown:
const router = express.Router();
router.get('/:name', async (req, res) => {
const filePath = path.join(__dirname, 'markdown', `${req.params.name}.md`);
try {
const data = await fs.promises.readFile(filePath, 'utf8');
res.send(marked(data));
} catch {
res.status(404).send('Файл не найден');
}
});
app.use('/docs', router);
Такой подход структурирует проект, облегчает поддержку и масштабирование, отделяя логику рендеринга Markdown от основной бизнес-логики.