Миграция кастомного кода

Архитектура Remark и Rehype

Remark и Rehype строятся вокруг концепции дерева синтаксического анализа (AST). Remark работает с Markdown-структурами, превращая текст в дерево MDAST (Markdown AST). Rehype оперирует деревом HAST (HTML AST), которое представляет собой уже HTML-документ в виде узлов. Ключевой момент при миграции кастомного кода — понимание структуры AST, с которой предстоит работать.

Каждый узел AST имеет обязательное поле type и может содержать children — массив дочерних узлов, а также дополнительные свойства, специфичные для типа узла, например value, tagName, properties.

{
  type: 'paragraph',
  children: [
    { type: 'text', value: 'Пример текста' }
  ]
}

Плагины как основной инструмент миграции

Remark и Rehype используют плагины, которые изменяют AST. В процессе миграции кастомного кода важно выделять следующие категории:

  1. Синтаксические плагины — добавляют поддержку нестандартных синтаксисов Markdown или HTML.
  2. Плагины-трансформеры — модифицируют существующие узлы AST.
  3. Плагины генерации — создают новые узлы на основе данных, например для автогенерации оглавлений.

Для перехода кастомного функционала из старой реализации в Remark/Rehype необходимо разложить код на эти три типа плагинов и определить точки интеграции в AST.

Прямое преобразование узлов

Основная стратегия миграции кастомного кода заключается в обработке узлов AST на лету. Например, если ранее существовал парсер, который добавлял кастомный тег в HTML, в Rehype это реализуется через трансформацию HAST:

function customTagPlugin() {
  return (tree) => {
    visit(tree, 'element', (node) => {
      if (node.tagName === 'span' && node.properties.className?.includes('highlight')) {
        node.tagName = 'mark';
        delete node.properties.className;
      }
    });
  };
}

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

Сохранение совместимости с существующим кодом

При миграции необходимо разделять логику парсинга и визуализации. Ранее кастомные функции могли напрямую работать с HTML-строкой. В Remark/Rehype рекомендуется:

  • Переводить текст в AST с помощью remark.parse или rehype.parse.
  • Применять трансформации через плагины.
  • Генерировать окончательный HTML или Markdown через remark.stringify или rehype.stringify.

Это обеспечивает детерминированность изменений и предотвращает ошибки при расширении функционала.

Использование универсального AST

В случаях, когда код должен работать с Markdown и HTML одновременно, полезно применять unified-подход, где Remark и Rehype интегрируются через remark-rehype:

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';

const processor = unified()
  .use(remarkParse)
  .use(remarkRehype)
  .use(rehypeStringify);

const html = processor.processSync('# Заголовок').toString();

Такой подход позволяет переносить кастомные трансформации из Markdown в HTML, не меняя логику работы с узлами.

Практика миграции кастомных функций

  1. Разбор функций: идентифицировать все кастомные обработчики текста или тегов.
  2. Классификация по типу AST: определить, относятся ли они к MDAST или HAST.
  3. Создание плагинов: переписать каждую функцию как плагин, работающий через visit или unist-util-modify.
  4. Тестирование узлов: проверять на небольших AST-фрагментах до применения на полном документе.
  5. Интеграция в pipeline: подключить плагины к цепочке unified для последовательной обработки.

Примеры типичных кастомных миграций

  • Кастомные маркеры: преобразование ::note:: в <aside class="note">.
  • Автогенерация ссылок: добавление идентификаторов к заголовкам Markdown для якорной навигации.
  • Обработка нестандартных атрибутов HTML: перенос атрибутов в соответствующие свойства HAST, чтобы они сохранялись при сериализации в HTML.

Особенности производительности

При обработке больших документов важно учитывать:

  • Глубину дерева: избыточный рекурсивный обход может замедлять работу.
  • Количество плагинов: каждый плагин добавляет накладные расходы, особенно при множественных трансформациях.
  • Проверку структуры узлов: оптимальнее фильтровать узлы по type перед изменением, чтобы избежать лишних проверок.

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