Добавление новых типов узлов

Remark и Rehype используют представление документа в виде дерева абстрактного синтаксиса (AST — Abstract Syntax Tree). Каждый элемент документа — это узел (node), который имеет тип (type) и набор свойств (properties). В Remark узлы чаще всего представляют части Markdown, а в Rehype — HTML-структуру. Для расширения возможностей этих библиотек часто требуется создавать новые типы узлов, не ограничиваясь стандартным набором.

Ключевые свойства узла:

  • type — строка, определяющая тип узла.
  • children — массив дочерних узлов (для контейнеров).
  • value — текстовое содержимое узла (для текстовых элементов).
  • position — объект с информацией о позиции узла в исходном документе (опционально, но полезно для линтеров и трансформеров).

Создание собственного узла в Remark

  1. Определение нового типа узла
const myCustomNode = {
  type: 'highlight',    // уникальный идентификатор типа узла
  value: 'Важный текст',
};
  1. Интеграция нового узла в дерево

Remark позволяет использовать плагины для преобразования AST. Простейший способ — воспользоваться функцией transformer:

function highlightPlugin() {
  return (tree) => {
    visit(tree, 'paragraph', (node, index, parent) => {
      if (node.children.some(child => child.value.includes('важно'))) {
        const highlightNode = {
          type: 'highlight',
          value: node.children.map(child => child.value).join(' '),
        };
        parent.children.splice(index, 1, highlightNode);
      }
    });
  };
}

Здесь:

  • visit — функция из unist-util-visit, позволяющая обходить узлы.
  • tree — корневой узел AST.
  • highlightNode — новый тип узла, заменяющий абзац с ключевым словом.

Добавление поддержки нового узла при рендеринге

После создания узла его нужно обработать при генерации HTML или Markdown. В Rehype это делается через кастомные обработчики (handlers):

const rehypeStringify = require('rehype-stringify');

const handlers = {
  highlight: (h, node) => h(node, 'mark', { className: 'highlight' }, node.value),
};

Здесь:

  • h — гипотетический фабричный метод для генерации HTML-узлов.
  • 'mark' — HTML-тег для подсветки.
  • className — CSS-класс для стилизации.
  • node.value — текст, который будет отображаться.

В Remark можно использовать remark-rehype, чтобы трансформировать Markdown-узлы в HTML, при этом кастомные узлы можно добавить в цепочку плагинов.

Совмещение нескольких типов узлов

Часто требуется создавать не один, а несколько новых типов узлов для комплексного синтаксиса. В этом случае рекомендуется:

  • Вести единый реестр типов узлов, чтобы избежать коллизий.
  • Использовать интерфейсы TypeScript или JSDoc для описания структуры узлов.
  • Разделять логику трансформации и рендеринга.

Пример реестра типов:

const NODE_TYPES = {
  HIGHLIGHT: 'highlight',
  NOTE: 'note',
  ALERT: 'alert',
};

Использование в плагине:

visit(tree, 'paragraph', (node, index, parent) => {
  if (node.children.some(child => child.value.startsWith('!'))) {
    const alertNode = { type: NODE_TYPES.ALERT, value: node.children.map(c => c.value).join(' ') };
    parent.children.splice(index, 1, alertNode);
  }
});

Работа с дочерними узлами

Новые узлы могут содержать дочерние элементы. Для этого необходимо:

  • Задавать children как массив узлов.
  • При обходе AST учитывать рекурсивные структуры.
  • Обеспечивать правильное преобразование в конечный формат (Markdown/HTML).

Пример узла с дочерними элементами:

const noteNode = {
  type: 'note',
  children: [
    { type: 'text', value: 'Это заметка: ' },
    { type: 'highlight', value: 'важная часть' },
  ],
};

Проверка и валидация новых узлов

Для комплексных проектов рекомендуется использовать утилиты типа unist-util-visit и unist-util-validate. Основные шаги:

  1. Проверять наличие обязательных полей (type, children/value).
  2. Удостоверяться, что типы дочерних узлов корректны.
  3. Добавлять position для удобства отладки и подсветки ошибок.

Пример проверки:

function validateNode(node) {
  if (!node.type) throw new Error('Node type is required');
  if (node.children && !Array.isArray(node.children)) throw new Error('Children must be an array');
}

Итоговые рекомендации

  • Всегда использовать уникальные строки для type.
  • Разделять логику генерации AST и рендеринга.
  • Поддерживать единый стиль структуры узлов.
  • Для сложных синтаксисов комбинировать несколько плагинов Remark и Rehype.

Расширение дерева узлов с помощью собственных типов позволяет гибко добавлять новые семантические элементы, управлять их визуализацией и интегрировать сложные Markdown/HTML-паттерны без изменения базовых библиотек.