Remark и Rehype — это мощные инструменты для работы с синтаксическим деревом Markdown и HTML соответственно. Одна из ключевых особенностей этих библиотек — возможность рекурсивной обработки вложенных структур, что позволяет гибко трансформировать документы любого уровня вложенности.
Обе библиотеки используют представление документа в виде абстрактного синтаксического дерева (AST, Abstract Syntax Tree). Каждый узел дерева содержит определённый тип и набор свойств:
type — тип узла (root,
paragraph, heading, text,
link и т.д.)children — массив дочерних узлов для вложенных
структурvalue для текста, url для ссылок)Для обработки таких деревьев применяются функции-плагины, которые получают узел и могут его модифицировать или заменять.
Для рекурсивной обработки удобно определить универсальную функцию обхода дерева:
function traverse(node, visitor) {
if (!node) return;
visitor(node);
if (node.children && node.children.length > 0) {
node.children.forEach(child => traverse(child, visitor));
}
}
Пояснение:
node — текущий узел AST.visitor — функция, вызываемая для каждого узла. Она
может проверять тип узла и выполнять трансформации.Для превращения Markdown-документа в AST и его рекурсивного обхода используется следующая схема:
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';
const markdown = `
# Заголовок 1
Параграф с **жирным текстом** и [ссылкой](https://example.com).
- Элемент списка 1
- Вложенный элемент 1
`;
const processor = unified()
.use(remarkParse)
.use(remarkStringify);
const tree = processor.parse(markdown);
traverse(tree, node => {
if (node.type === 'text') {
node.value = node.value.toUpperCase();
}
});
const newMarkdown = processor.stringify(tree);
console.log(newMarkdown);
Особенности:
Rehype использует аналогичный подход, но работает с HTML-документами:
import { unified } from 'unified';
import rehypeParse from 'rehype-parse';
import rehypeStringify from 'rehype-stringify';
const html = `
<div>
<p>Текст <strong>выделенный</strong></p>
<ul>
<li>Элемент 1</li>
<li>Элемент 2 <ul><li>Вложенный элемент</li></ul></li>
</ul>
</div>
`;
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(rehypeStringify);
const tree = processor.parse(html);
traverse(tree, node => {
if (node.tagName === 'li') {
node.children.push({ type: 'text', value: ' (обработано)' });
}
});
const newHtml = processor.stringify(tree);
console.log(newHtml);
Ключевые моменты:
tagName вместо
type в Markdown.Часто требуется обработка только определённых уровней или типов узлов. Для этого можно использовать дополнительные условия:
function traverseFiltered(node, visitor, condition) {
if (!node) return;
if (condition(node)) {
visitor(node);
}
if (node.children && node.children.length > 0) {
node.children.forEach(child => traverseFiltered(child, visitor, condition));
}
}
// Пример: обработка только параграфов
traverseFiltered(tree, node => {
node.children.push({ type: 'text', value: ' [добавлено]' });
}, node => node.type === 'paragraph');
Такой подход позволяет создавать гибкие плагины и выполнять сложные преобразования с сохранением структуры документа.
Рекурсивная обработка вложенных структур позволяет:
Эта техника является основой для построения мощных систем анализа и генерации контента, включая конвертацию Markdown в HTML, обогащение текста метаданными и автоматическое добавление структурных элементов.