Библиотеки Remark и Rehype работают
с деревьями абстрактного синтаксиса (AST — Abstract Syntax Tree),
которые представляют структуру Markdown и HTML соответственно. Каждое
дерево состоит из узлов, где каждый узел описывает элемент документа:
текст, параграф, заголовок, ссылку или любой другой компонент. Узлы
имеют тип (type), могут содержать свойства
(data, props) и дочерние элементы
(children).
Основные типы узлов:
Дерево строится иерархически: узлы могут содержать другие узлы в
children. Понимание структуры и доступ к нужным узлам
требует грамотной навигации по дереву.
Навигация по AST может выполняться рекурсивно или с использованием утилитных функций. В Remark и Rehype есть два основных подхода:
Рекурсивный обход
Простейший способ — рекурсивно проходить children
каждого узла. Это удобно для поиска всех узлов определённого типа или
модификации текста.
function walk(node, callback) {
callback(node);
if (node.children) {
node.children.forEach(child => walk(child, callback));
}
}
Использование библиотек unist-util-visit и
unist-util-visit-parents
unist-util-visit позволяет посещать каждый узел
определённого типа и применять функцию.unist-util-visit-parents дополнительно предоставляет
путь от корня до текущего узла, что важно для понимания контекста.Пример поиска всех заголовков:
import { visit } from 'unist-util-visit';
visit(tree, 'heading', node => {
console.log(node.depth, node.children[0].value);
});
Каждый узел имеет структуру:
{
type: 'тип_узла',
children: [...],
value: 'текстовое содержимое',
data: { ... },
position: { start: {...}, end: {...} }
}
type — определяет роль узла.children — массив дочерних узлов. Если узел текстовый
(text), children отсутствует.value — хранит текстовое содержимое для узлов типа
text или code.data — объект для хранения дополнительной информации
(метаданные).position — объект с координатами начала и конца узла в
исходном документе.Добавление узлов: узел можно добавить в
children родительского узла.
const newNode = { type: 'paragraph', children: [{ type: 'text', value: 'Новый параграф' }] };
root.children.push(newNode);
Удаление узлов: узел удаляется из
children родителя с использованием методов массива
(filter, splice).
Изменение узлов: рекурсивно или с помощью
visit изменяются свойства узлов:
visit(tree, 'text', node => {
node.value = node.value.toUpperCase();
});
Часто требуется не только обработать узел, но и понимать его
окружение. unist-util-visit-parents возвращает массив
родителей:
import { visitParents } from 'unist-util-visit-parents';
visitParents(tree, 'text', (node, parents) => {
const parentType = parents[parents.length - 1].type;
console.log(`Текст "${node.value}" находится внутри узла типа ${parentType}`);
});
Такой подход полезен для трансформаций, зависящих от структуры документа, например, замена текста только внутри заголовков определённого уровня.
Навигация по дереву часто комбинируется с фильтрацией узлов:
function findNodesByType(node, type) {
let result = [];
if (node.type === type) result.push(node);
if (node.children) {
node.children.forEach(child => {
result = result.concat(findNodesByType(child, type));
});
}
return result;
}
Так можно извлекать все ссылки, изображения, кодовые блоки и другие элементы для последующей обработки.
Remark и Rehype используют плагины для трансформаций. Плагины получают AST и навигируют по нему с помощью методов выше. Понимание дерева и его структуры критично для:
Для анализа и отладки можно использовать
unist-util-inspect или простые функции печати:
import { inspect } from 'unist-util-inspect';
console.log(inspect(tree, { colors: true }));
Это позволяет увидеть иерархию, типы узлов и их содержимое, облегчая навигацию и манипуляции с AST.
type, children и/или
value.visit.visitParents позволяет учитывать контекст и
родительские узлы.