Библиотеки Remark и Rehype
оперируют с абстрактными синтаксическими деревьями (AST), которые
следуют стандарту Unist. Каждое дерево состоит из узлов
(nodes), где каждый узел имеет тип (type) и может содержать
дочерние узлы (children). Для обработки этих деревьев
необходим удобный инструмент обхода узлов —
unist-util-visit.
unist-util-visit позволяет рекурсивно
проходить через все узлы AST и выполнять заданные действия над узлами
определённого типа. Основные моменты:
type.Структура функции:
visit(tree, [type], visitor)
import { visit } from 'unist-util-visit';
const tree = {
type: 'root',
children: [
{ type: 'paragraph', children: [{ type: 'text', value: 'Пример текста' }] },
{ type: 'heading', depth: 1, children: [{ type: 'text', value: 'Заголовок' }] }
]
};
visit(tree, 'text', node => {
console.log(node.value); // 'Пример текста' и 'Заголовок'
});
Здесь:
tree — корень AST.'text' — тип узлов, которые нужно обойти.node — текущий узел текста, доступен для модификации
или анализа.type может быть массивом строк. Это удобно, если нужно
обработать несколько типов узлов одновременно:
visit(tree, ['text', 'heading'], node => {
node.value && console.log(node.value);
});
Функция visitor может принимать три аргумента:
visit(tree, 'paragraph', (node, index, parent) => {
console.log(parent.type); // 'root'
console.log(index); // индекс текущего параграфа в parent.children
});
parent.children.Это особенно важно, если требуется модифицировать дерево, удалять или заменять узлы.
Для остановки обхода можно вернуть специальное значение
visit.EXIT:
import { visit, EXIT } from 'unist-util-visit';
visit(tree, 'text', node => {
if (node.value.includes('стоп')) return EXIT;
});
После этого обход всех дочерних узлов прекращается.
unist-util-visit позволяет напрямую изменять узлы.
Например, преобразуем все текстовые узлы в верхний регистр:
visit(tree, 'text', node => {
node.value = node.value.toUpperCase();
});
Результатом будет модифицированное дерево:
{
"type": "root",
"children": [
{ "type": "paragraph", "children": [{ "type": "text", "value": "ПРИМЕР ТЕКСТА" }] },
{ "type": "heading", "depth": 1, "children": [{ "type": "text", "value": "ЗАГОЛОВОК" }] }
]
}
Чтобы удалить узлы из дерева, можно использовать
parent.children.splice(index, 1):
visit(tree, 'paragraph', (node, index, parent) => {
if (node.children.length === 0) {
parent.children.splice(index, 1);
}
});
Важно помнить: при модификации массива children индекс
текущего узла может смещаться, поэтому в сложных случаях стоит
использовать обратный обход или аккуратно управлять индексами.
Иногда необходимо обрабатывать узлы с более сложными условиями. Можно использовать функцию-фильтр вместо строки или массива типов:
visit(tree, node => node.type === 'text' && node.value.includes('важно'), node => {
node.value = node.value.replace('важно', 'ВАЖНО');
});
Таким образом, обход можно адаптировать под любые требования: проверка свойств узлов, наличие дочерних элементов, определённые значения.
В Remark unist-util-visit часто
используют для:
remark-parse).remark-slug).В Rehype аналогично обходят HTML AST для:
Пример плагина Remark, заменяющего все ссылки на заголовки:
import { visit } from 'unist-util-visit';
function remarkLinkToHeading() {
return (tree) => {
visit(tree, 'link', (node) => {
if (node.url.startsWith('#')) {
node.url = node.url.toLowerCase();
}
});
};
}
children во время обхода без корректного управления
индексами.parent и
index: для сложных операций удаления или вставки
узлов.visit.EXIT при необходимости остановить обработку
дерева.unist-util-visit является универсальным инструментом для
анализа и трансформации AST в экосистеме Remark и Rehype, предоставляя
гибкий и удобный способ обхода узлов с точной фильтрацией и
модификацией.