Remark и Rehype используют плагинную архитектуру, позволяющую расширять функциональность процессоров Markdown и HTML. Каждый плагин представляет собой функцию, принимающую опциональные параметры и возвращающую функцию-трансформер, которая будет вызвана на дереве AST (Abstract Syntax Tree). Понимание точек входа плагина критично для отладки:
Ошибки часто возникают, если предполагаемый тип узла не совпадает с реальным, либо если обход дерева реализован некорректно. Логирование каждого шага обхода помогает выявить расхождения.
Для выявления ошибок в плагинах важна возможность отслеживания изменения AST. Наиболее удобные методы:
console.log на ключевых шагах:
function myPlugin() {
return (tree) => {
console.log('Начало обработки дерева:', tree);
};
}util.inspect для глубокого анализа структуры:
import util from 'util';
console.log(util.inspect(tree, { depth: null }));Пошаговый обход узлов: Использование
unist-util-visit:
import { visit } from 'unist-util-visit';
visit(tree, 'text', (node, index, parent) => {
console.log('Обнаружен текстовый узел:', node.value);
});Важный момент — вывод большого AST требует увеличения глубины
рекурсии в util.inspect или использование
специализированных визуализаторов AST, чтобы не потерять контекст.
Ошибки плагинов часто связаны с неверными ожиданиями структуры AST. Решения:
TypeScript: определение интерфейсов для узлов повышает надёжность.
import { Node } from 'unist';
interface TextNode extends Node {
type: 'text';
value: string;
}Проверка типа узла перед обработкой:
if (node.type === 'paragraph') {
// безопасная работа с paragraph
}Встроенные утилиты
unist-util-is:
import is from 'unist-util-is';
if (is(node, 'heading')) { ... }Эти методы предотвращают ошибки типа
Cannot read property 'value' of undefined.
Для сложных плагинов важно создавать тестовые AST, чтобы проверить поведение без реального Markdown. Пример:
import { unified } from 'unified';
import remarkParse from 'remark-parse';
const tree = unified()
.use(remarkParse)
.parse('# Заголовок\nТекст абзаца');
console.log(tree);
Тестовые деревья позволяют локализовать проблему до конкретного узла, выявляя несоответствие ожиданий и реальной структуры.
Некорректная работа плагина может быть не только функциональной, но и производительной. Для анализа:
console.time и console.timeEnd измеряют
время обработки дерева:
console.time('transform');
transform(tree);
console.timeEnd('transform');Плагин, обходящий большое дерево без фильтрации по типу узлов,
может замедлить процессор. Использование unist-util-visit с
уточнением типов узлов существенно улучшает производительность.
Для сложных трансформаций полезно логировать результат каждого шага:
visit(tree, (node) => {
// изменение узла
node.processed = true;
console.log('После обработки узла:', node);
});
Такой подход позволяет увидеть, как изменяется AST и где плагин может вести себя не так, как ожидалось.
Для больших деревьев текстовое логирование часто недостаточно. Существуют библиотеки визуализации AST:
Использование этих инструментов сокращает время нахождения ошибок и помогает понять сложные преобразования.
Плагины Rehype работают аналогично Remark, но на HTML-дереве. Особенности:
tagName, properties и
children.unist-util-visit с типом
'element'.Пример обхода всех ссылок в HTML:
visit(tree, 'element', (node) => {
if (node.tagName === 'a') {
console.log('Ссылка:', node.properties.href);
}
});
Remark и Rehype поддерживают последовательное применение плагинов. Возможные источники ошибок:
const processor = unified()
.use(remarkParse)
.use(plugin1)
.use(() => (tree) => console.log('После plugin1:', tree))
.use(plugin2);
Такой подход позволяет локализовать проблемный плагин в цепочке.
Плагины могут выбрасывать исключения при неожиданных узлах или значениях. Практики:
Оборачивать ключевые участки кода в
try/catch.
Логировать контекст ошибки:
try {
visit(tree, 'text', (node) => { ... });
} catch (err) {
console.error('Ошибка при обработке узла:', err, node);
}Создавать понятные сообщения, чтобы сразу понимать тип и местоположение узла, вызвавшего ошибку.
Для плагинов создаются юнит-тесты с разными типами Markdown и HTML. Используются: