Расширение типов узлов

В экосистеме Remark и Rehype каждый документ представляется в виде дерева абстрактного синтаксиса (AST). Узлы AST имеют определённые типы, соответствующие элементам Markdown, HTML или другим форматам. Однако для сложных задач обработки текста стандартного набора типов может быть недостаточно. Здесь вступает в силу возможность расширения типов узлов, что позволяет создавать собственные структуры и обогащать AST дополнительной информацией.

Основные принципы расширения узлов

  1. Тип узла (type) Каждому узлу AST соответствует строковый идентификатор типа, например, paragraph, heading, link. Для создания собственного узла необходимо определить уникальное имя типа. Например:

    const customNode = {
      type: 'fancyBox',
      title: 'Важное сообщение',
      content: 'Текст внутри коробки'
    };
  2. Структура узла Узлы могут содержать стандартные поля children для вложенных элементов и дополнительные поля для хранения информации, специфичной для конкретного расширения. Например:

    {
      type: 'tooltip',
      text: 'Наведи на меня',
      children: [
        { type: 'text', value: 'Информация о подсказке' }
      ]
    }
  3. Совместимость с процессорами Расширенные узлы должны корректно интегрироваться с плагинами Remark или Rehype. Для этого рекомендуется:

    • Использовать уникальные типы, чтобы не конфликтовать с существующими.
    • Обрабатывать дополнительные поля через плагины-переводчики, чтобы обычные обработчики не ломались.

Создание и использование плагина для новых типов узлов

Плагины Remark и Rehype позволяют изменять дерево AST во время парсинга или трансформации. Пример плагина для добавления узла fancyBox:

import { visit } from 'unist-util-visit';

function remarkFancyBox() {
  return (tree) => {
    visit(tree, 'paragraph', (node, index, parent) => {
      if (node.children.some(child => child.value.includes('[box]'))) {
        const content = node.children.map(child => child.value).join(' ').replace('[box]', '');
        parent.children[index] = {
          type: 'fancyBox',
          title: 'Box',
          content,
          children: []
        };
      }
    });
  };
}

Здесь:

  • Используется функция visit из unist-util-visit для обхода AST.
  • Идентифицируется обычный параграф с особым маркером [box].
  • Заменяется стандартный узел на новый тип fancyBox.

Расширение типов узлов для Rehype

Для Rehype узлы представляют собой HTML-структуры. Расширение типов может использоваться для добавления семантических элементов или нестандартных атрибутов:

const customElement = {
  type: 'element',
  tagName: 'highlight',
  properties: { color: 'yellow' },
  children: [
    { type: 'text', value: 'Выделенный текст' }
  ]
};
  • type: 'element' — стандартный Rehype-тип для HTML-элементов.
  • tagName — имя тега, которое может быть нестандартным.
  • properties — объект с атрибутами HTML.
  • children — массив вложенных узлов.

Для обработки таких элементов создаются плагины Rehype, которые могут преобразовывать новые узлы в стандартный HTML или другой формат:

function rehypeHighlight() {
  return (tree) => {
    visit(tree, 'element', (node) => {
      if (node.tagName === 'highlight') {
        node.tagName = 'span';
        node.properties.style = `background-color: ${node.properties.color}`;
      }
    });
  };
}

Обработка новых типов узлов при сериализации

После расширения типов узлов важно корректно обрабатывать их при конвертации в Markdown или HTML:

  • Для Remark: при использовании remark-stringify необходимо определить, как новый узел превращается в Markdown. Обычно для этого создаются функции-конвертеры, которые возвращают массив стандартных узлов или текст.
  • Для Rehype: при использовании rehype-stringify новые элементы преобразуются в HTML через tagName и properties.

Практические рекомендации

  • Всегда документировать структуру пользовательских узлов. Это упрощает поддержку больших проектов.
  • Использовать namespacing в типах, например, myPlugin/fancyBox, чтобы избегать конфликтов с другими плагинами.
  • Проверять совместимость с AST-плагинами для обхода и модификации дерева.
  • Для сложных проектов использовать типизацию с TypeScript, чтобы избежать ошибок при работе с расширенными узлами.

Расширение типов узлов предоставляет мощный инструмент для создания кастомных форматов, внедрения семантики и специализированных трансформаций документов. Благодаря гибкости Remark и Rehype можно строить сложные редакторы, генераторы документации и конвертеры, полностью управляя структурой AST.