Remark и Rehype оперируют деревьями абстрактного синтаксиса (AST,
Abstract Syntax Tree), где каждый элемент документа представлен
отдельным узлом (node). Узлы имеют тип
(type), содержимое (value или
children) и дополнительные свойства (data,
position, attributes). Понимание структуры
узла и иерархии дерева является ключевым для добавления и модификации
контента.
В Remark все узлы соответствуют Markdown-сущностям:
paragraph, heading, text,
link, list, listItem. В Rehype
узлы соответствуют HTML-элементам: element,
text, comment. Узлы могут быть
листовыми (не имеют детей, например text)
и контейнерными (могут содержать другие узлы, например
paragraph или element).
Для создания нового узла используют простой объект с обязательными свойствами:
const paragraphNode = {
type: 'paragraph',
children: [
{ type: 'text', value: 'Это новый параграф.' }
]
};
Ключевые моменты:
type задаёт тип узла, определяющий его роль в
дереве.children содержит массив дочерних узлов, если узел
контейнерный.text или
html, достаточно указать value.Rehype использует аналогичный подход, но вместо Markdown-типов применяет HTML-типизацию:
const divNode = {
type: 'element',
tagName: 'div',
properties: { className: ['highlight'] },
children: [
{ type: 'text', value: 'Содержимое блока' }
]
};
Здесь добавляются свойства:
tagName — имя HTML-тега.properties — объект с атрибутами элемента.children — дочерние узлы, которые могут включать текст
или другие элементы.Для вставки нового узла в дерево используют прямое изменение массива
children родительского узла. Например, добавление параграфа
в конец документа Remark:
import { unified } from 'unified';
import remarkParse from 'remark-parse';
const tree = unified().use(remarkParse).parse('# Заголовок');
const newNode = {
type: 'paragraph',
children: [{ type: 'text', value: 'Новый параграф в документе.' }]
};
tree.children.push(newNode);
Для Rehype добавление элемента в существующий контейнер:
const divNode = { type: 'element', tagName: 'div', children: [] };
const textNode = { type: 'text', value: 'Привет, мир!' };
divNode.children.push(textNode);
Важные аспекты:
push,
unshift, splice) позволяет гибко управлять
порядком узлов.properties для корректного
рендеринга HTML.Часто требуется вставлять узлы не только в корень документа, но и в конкретные контейнеры, например, в определённый параграф или список:
const listNode = {
type: 'list',
ordered: true,
children: [
{ type: 'listItem', children: [{ type: 'paragraph', children: [{ type: 'text', value: 'Первый пункт' }] }] }
]
};
const newListItem = {
type: 'listItem',
children: [{ type: 'paragraph', children: [{ type: 'text', value: 'Второй пункт' }] }]
};
listNode.children.push(newListItem);
Здесь важно поддерживать иерархию типов: list →
listItem → paragraph → text.
Нарушение этой структуры может привести к некорректной интерпретации
Markdown или HTML при генерации.
Remark и Rehype имеют обширный экосистемный набор пакетов для удобной работы с AST:
Пример создания параграфа с использованием
unist-builder:
import { u } from 'unist-builder';
const paragraphNode = u('paragraph', [u('text', 'Текст параграфа')]);
unist-builder упрощает создание узлов, автоматически
подставляя массив children и проверяя корректность
структуры.
Для генерации узлов с переменным содержимым используют функции:
function createHeading(level, text) {
return {
type: 'heading',
depth: level,
children: [{ type: 'text', value: text }]
};
}
tree.children.push(createHeading(2, 'Подзаголовок второго уровня'));
Это позволяет программно формировать документы, генерировать списки, таблицы и сложные структуры без ручного создания каждого узла.
element с
properties.unist-util-visit вместо ручного обхода.unist-builder,
hastscript) для уменьшения числа ошибок при создании
узлов.