Написание первого плагина

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

Структура плагина

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

function myPlugin(options) {
  return (tree, file) => {
    // обработка дерева
  };
}
  • options — объект с настройками плагина, которые можно задавать при подключении.
  • tree — синтаксическое дерево Markdown (Remark) или HTML (Rehype).
  • file — объект, содержащий информацию о файле, включая метаданные и ошибки.

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

Работа с деревом

Дерево синтаксиса представляет собой вложенную структуру узлов. Узлы имеют свойства type, children и специфические атрибуты в зависимости от типа (value, tagName, properties). Например, для Markdown:

{
  type: 'heading',
  depth: 2,
  children: [
    { type: 'text', value: 'Пример заголовка' }
  ]
}

Для HTML через Rehype:

{
  type: 'element',
  tagName: 'p',
  children: [
    { type: 'text', value: 'Абзац текста' }
  ]
}

Обход дерева часто выполняется с помощью библиотеки unist-util-visit:

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

function myPlugin() {
  return (tree) => {
    visit(tree, 'text', (node) => {
      node.value = node.value.toUpperCase();
    });
  };
}

В этом примере каждый узел текста преобразуется в верхний регистр.

Использование опций

Плагины обычно принимают конфигурацию через объект options, чтобы поведение можно было изменять без переписывания кода:

function replaceTextPlugin(options = {}) {
  const { find = '', replace = '' } = options;

  return (tree) => {
    visit(tree, 'text', (node) => {
      node.value = node.value.replace(new RegExp(find, 'g'), replace);
    });
  };
}

Подключение и использование:

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

const processor = unified()
  .use(remarkParse)
  .use(replaceTextPlugin, { find: 'foo', replace: 'bar' })
  .use(remarkStringify);

const result = processor.processSync('foo baz').toString();
// Результат: 'bar baz'

Асинхронные плагины

Плагины могут быть асинхронными, если требуется выполнять внешние запросы или сложные вычисления:

function asyncPlugin() {
  return async (tree) => {
    const nodes = [];
    visit(tree, 'text', (node) => {
      nodes.push(node);
    });

    for (const node of nodes) {
      const newValue = await fetchSomeData(node.value);
      node.value = newValue;
    }
  };
}

Для интеграции с асинхронными плагинами необходимо использовать process вместо processSync.

Модификация HTML через Rehype

Rehype-плагины строятся аналогично, но работают с узлами element и text:

function addClassToParagraphs() {
  return (tree) => {
    visit(tree, 'element', (node) => {
      if (node.tagName === 'p') {
        node.properties = node.properties || {};
        node.properties.className = [
          ...(node.properties.className || []),
          'custom-paragraph'
        ];
      }
    });
  };
}

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

Комбинация Remark и Rehype

Remark и Rehype можно комбинировать через remark-rehype:

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

const processor = unified()
  .use(remarkParse)
  .use(replaceTextPlugin, { find: 'foo', replace: 'bar' })
  .use(remarkRehype)
  .use(addClassToParagraphs)
  .use(rehypeStringify);

В этом примере Markdown обрабатывается Remark-плагином, затем преобразуется в HTML и модифицируется Rehype-плагином.

Рекомендации по написанию плагинов

  • Всегда использовать visit или unist-util-visit-parents для обхода дерева.
  • Сохранять неизменными узлы, которые не требуют обработки, чтобы минимизировать побочные эффекты.
  • Поддерживать опции конфигурации, чтобы плагин был гибким и переиспользуемым.
  • Проверять совместимость с асинхронной обработкой, если выполняются внешние операции.
  • При работе с Rehype использовать свойства properties для модификации атрибутов HTML.

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