Работа с Markdown в TypeScript: от парсинга до кастомных плагинов
Markdown остаётся одним из самых популярных языков разметки для документации, блогов и образовательных платформ. При разработке на TypeScript возникает необходимость не только отображать Markdown, но и гибко управлять процессом его преобразования. В этой статье мы разберём три ключевые библиотеки, которые помогут вам интегрировать Markdown в ваш TypeScript-проект, и покажем, как создавать собственные расширения.
Почему TypeScript важен для работы с Markdown?
TypeScript добавляет строгую типизацию, что критически важно при обработке пользовательского контента. Вы точно знаете, какие данные приходят из Markdown-файлов, и можете избежать ошибок времени выполнения. Кроме того, TypeScript упрощает создание плагинов и middleware для парсеров.
Обзор популярных библиотек
- marked — самый быстрый и простой парсер. Минимальная конфигурация, отличная производительность.
- unified / remark — экосистема для синтаксического анализа. Позволяет создавать сложные цепочки преобразований (AST -> HTML -> React).
- showdown — лёгкая альтернатива с поддержкой расширений, хорошо документирована.
Пример 1: Базовый парсинг с marked
Библиотека marked идеальна для быстрого старта. Установите её через npm:
npm install marked
Типы для TypeScript поставляются в комплекте. Пример использования:
import { marked } from 'marked';
const markdownContent = `# Привет, TypeScript!
Это **жирный текст** и `код`.
- Элемент списка
- Ещё один`;
const html = marked.parse(markdownContent);
console.log(html);
// Вывод: <h1>Привет, TypeScript!</h1>
// <p>Это <strong>жирный текст</strong> и <code>код</code>.</p>
// <ul><li>Элемент списка</li><li>Ещё один</li></ul>
Для асинхронной работы используйте marked.parseAsync(). Это полезно при загрузке файлов из сети.
Пример 2: Кастомный рендерер с типизацией
Иногда нужно изменить стандартное поведение. Например, добавить классы к ссылкам или обработать изображения. Создадим кастомный рендерер на TypeScript:
import { marked, Renderer } from 'marked';
interface CustomRenderer extends Renderer {
link(href: string, title: string | null, text: string): string;
}
const renderer: Partial<CustomRenderer> = {
link({ href, title, text }) {
return `<a href="${href}" target="_blank" rel="noopener noreferrer" class="external-link">${text}</a>`;
}
};
marked.use({ renderer });
const markdown = 'Посети [PythonLib](https://pythonlib.ru)';
const result = marked.parse(markdown);
// <a href="https://pythonlib.ru" target="_blank" class="external-link">PythonLib</a>
Тип Partial<CustomRenderer> гарантирует, что мы переопределяем только нужные методы.
Пример 3: Работа с AST через unified и remark
Если вам нужен полный контроль над структурой документа, используйте unified и remark. Установка:
npm install unified remark-parse remark-stringify remark-html
Пример парсинга в синтаксическое дерево (AST) и обратно:
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';
import remarkHtml from 'remark-html';
const markdown = '# Заголовок\nЭто **важно**.';
const file = await unified()
.use(remarkParse) // Парсинг в AST
.use(remarkStringify) // Обратно в Markdown (для проверки)
.process(markdown);
console.log(String(file)); // Исходный Markdown
const htmlFile = await unified()
.use(remarkParse)
.use(remarkHtml) // Преобразование в HTML
.process(markdown);
console.log(String(htmlFile)); // <h1>Заголовок</h1><p>Это <strong>важно</strong>.</p>
Вы можете вставлять свои плагины между remarkParse и remarkHtml, например, для подсветки кода или проверки ссылок.
Пример 4: Кастомный плагин для remark
Создадим плагин, который заменяет все ссылки на