Композиция сложных трансформаций

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

Композиция плагинов строится на трёх принципах:

  1. Разделение ответственности — каждый плагин выполняет одно действие, будь то изменение структуры узла, добавление атрибутов или фильтрация контента.
  2. Паспорт узлов (node passing) — узлы, изменённые одним плагином, передаются следующему. Это гарантирует, что последовательность обработки остаётся предсказуемой.
  3. Обратимость и отладка — небольшие плагинные функции легче тестировать и отлаживать, чем монолитный скрипт обработки всего документа.

Структура сложного плагина

Сложные трансформации часто требуют вложенных проходов по дереву AST. В Remark/Rehype это реализуется через использование методов visit или transform. Стандартная структура плагина выглядит так:

function examplePlugin(options) {
  return (tree, file) => {
    visit(tree, 'text', (node) => {
      if (node.value.includes(options.keyword)) {
        node.value = node.value.replace(options.keyword, options.replacement);
      }
    });
  };
}

В более сложных случаях один плагин может создавать вспомогательные узлы, которые затем обрабатываются другими плагинами. Например, плагин может выделять цитаты из текста, превращать их в блоки <blockquote> и добавлять к ним идентификаторы для ссылок.

Последовательность применения плагинов

Remark и Rehype используют цепочку плагинов, где порядок имеет критическое значение. Например:

import {remark} from 'remark';
import remarkParse from 'remark-parse';
import remarkSlug from 'remark-slug';
import remarkAutolinkHeadings from 'remark-autolink-headings';
import remarkCustom from './remarkCustom.js';

remark()
  .use(remarkParse)
  .use(remarkSlug)
  .use(remarkAutolinkHeadings)
  .use(remarkCustom, { keyword: 'TODO', replacement: '✔' })
  .processSync(markdown);

Здесь:

  • remarkParse строит базовое дерево Markdown.
  • remarkSlug добавляет идентификаторы заголовкам.
  • remarkAutolinkHeadings генерирует ссылки на заголовки.
  • remarkCustom выполняет пользовательскую замену текста.

Если поменять порядок плагинов, результат может измениться, что подчёркивает важность контроля последовательности обработки.

Вложенные и условные трансформации

Для сложных сценариев часто применяются условные преобразования в плагинах. Например, в зависимости от уровня заголовка или типа узла можно выбирать разные стратегии обработки:

function conditionalPlugin() {
  return (tree) => {
    visit(tree, (node) => {
      if (node.type === 'heading' && node.depth === 2) {
        node.children.push({ type: 'text', value: ' - Важный раздел' });
      }
    });
  };
}

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

Сочетание Remark и Rehype

Remark работает с Markdown, Rehype — с HTML. Для комплексной обработки используют связывающий слой через remark-rehype:

import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';

remark()
  .use(remarkParse)
  .use(remarkCustom)
  .use(remarkRehype)
  .use(rehypeStringify)
  .processSync(markdown);

Сценарии включают:

  • Преобразование Markdown в HTML с добавлением кастомных атрибутов.
  • Фильтрацию элементов по типу (например, скрыть <script>).
  • Композицию визуальных эффектов и стилизацию через Rehype.

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

  • Минимизировать состояние плагина: чем меньше глобальных переменных, тем проще отлаживать конвейер.
  • Использовать visit и visitParents для точного контроля над деревом AST.
  • Тестировать каждый плагин отдельно, чтобы убедиться, что изменения узлов корректны.
  • Документировать порядок плагинов, особенно в больших проектах с десятками трансформаций.
  • Проверять обратимость изменений, если есть риск повредить исходный Markdown/HTML.

Комплексная композиция Remark и Rehype превращает обработку текстового контента в управляемый, модульный процесс, позволяющий реализовать любые трансформации — от простых замен до генерации интерактивных HTML-документов.