Архитектура плагинов unified

Библиотеки Remark и Rehype строятся на основе экосистемы unified, которая предоставляет унифицированный способ обработки документов через абстрактное синтаксическое дерево (AST). Понимание архитектуры плагинов является ключевым для эффективного использования этих инструментов.


Плагины и их типы

Плагин в unified — это функция, которая принимает опции и возвращает либо функцию трансформации AST, либо объект с методами для модификации дерева. Основные типы плагинов:

  1. Transformer – функция, которая принимает AST и модифицирует его напрямую.
  2. Compiler – функция, которая преобразует AST в конечный формат, например HTML или Markdown.
  3. Parser – функция, которая создает AST из исходного текста.

Каждый плагин регистрируется через метод use, обеспечивая цепочку обработки данных.

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';

const processor = unified()
  .use(remarkParse)
  .use(myCustomPlugin, { option: true })
  .use(remarkStringify);

Контракт плагина

Плагин должен соответствовать следующей структуре:

  • Вход: AST и опции
  • Выход: модифицированное AST или функция-компилятор
  • Побочные эффекты: могут включать добавление новых узлов, изменение свойств существующих или вставку ссылок на внешние ресурсы

Строгий контракт позволяет комбинировать плагины в произвольном порядке без потери совместимости.


Обработка AST

В Remark используется дерево MDAST (Markdown AST), в Rehype — HAST (HTML AST). Структура AST схожа: каждый узел имеет тип (type) и список дочерних узлов (children). Плагины обычно работают с узлами рекурсивно.

Пример обхода дерева:

function visitNodes(node, callback) {
  callback(node);
  if (node.children) {
    node.children.forEach(child => visitNodes(child, callback));
  }
}

Применение:

visitNodes(tree, node => {
  if (node.type === 'heading') {
    node.depth += 1; // увеличить уровень заголовка
  }
});

Параметры плагина

Плагины принимают опции, позволяя настраивать поведение:

  • Простейшие флаги (true / false)
  • Конфигурации в виде объектов ({ level: 2, prefix: '!' })
  • Функции обратного вызова для динамической модификации узлов

Например, плагин для добавления якорей к заголовкам может принимать функцию генерации идентификатора:

function remarkAddAnchors(options = {}) {
  return (tree) => {
    visitNodes(tree, node => {
      if (node.type === 'heading') {
        node.data = node.data || {};
        node.data.id = options.generateId
          ? options.generateId(node)
          : node.children.map(c => c.value).join('-').toLowerCase();
      }
    });
  };
}

Композиция плагинов

Unifed строит цепочку плагинов, где каждый следующий получает AST после предыдущего. Это обеспечивает:

  • Разделение ответственности: каждый плагин решает свою задачу
  • Возможность многократного применения одного и того же плагина с разными опциями
  • Простое тестирование и отладку каждого этапа
unified()
  .use(remarkParse)
  .use(pluginA)
  .use(pluginB, { option: 123 })
  .use(remarkStringify);

Обратные вызовы (Visitors)

Стандартный паттерн — использование visitor-функций для обхода дерева:

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

visit(tree, 'link', node => {
  node.url = node.url.replace('http://', 'https://');
});

Преимущества:

  • Можно модифицировать узлы без изменения основной структуры
  • Легко фильтровать узлы по типу (heading, paragraph, link)
  • Совместимо с любым AST, который поддерживает children

Расширение функциональности

Плагины могут:

  • Добавлять новые узлы (например, footnote)
  • Удалять или изменять существующие узлы
  • Создавать промежуточные AST для промежуточных форматов
  • Генерировать метаданные, которые потом используются компилятором

Эти возможности делают unified гибкой платформой для построения сложных пайплайнов обработки текста.


Практические советы по созданию плагинов

  1. Минимизировать побочные эффекты — лучше возвращать новый узел, чем изменять глобальные структуры.
  2. Использовать visitor-функции — упрощает навигацию и модификацию дерева.
  3. Поддерживать опции по умолчанию — повышает переносимость и повторное использование.
  4. Документировать типы узлов — это облегчает интеграцию с другими плагинами.

Эта архитектура позволяет строить сложные цепочки обработки текста, объединяя разбор, трансформацию и генерацию конечного формата через модульные и легко тестируемые плагины. Remark и Rehype используют этот подход для работы с Markdown и HTML, делая unified универсальным инструментом для любых текстовых преобразований.