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

Remark и Rehype — это экосистемы для обработки Markdown и HTML в JavaScript, основанные на концепции универсальных синтаксических деревьев (unist). Центральным элементом их работы является композиция плагинов, которая позволяет создавать цепочки обработки текста от парсинга до генерации выходного кода.


Основы плагинов

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

  1. Remark-плагины — работают с Markdown AST (mdast).
  2. Rehype-плагины — работают с HTML AST (hast).

Функция плагина может иметь следующую сигнатуру:

function myPlugin(options) {
  return function transformer(tree, file) {
    // tree — это AST, file — объект VFile
  }
}
  • tree — синтаксическое дерево документа.
  • file — объект VFile, содержащий информацию о файле, включая путь, исходный текст и сообщения об ошибках.

Плагины могут быть синхронными и асинхронными. Асинхронные плагины возвращают Promise.


Механизм композиции

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

  1. Парсинг исходного текста в AST.
  2. Преобразование дерева через один или несколько плагинов.
  3. Генерация финального текста из AST.

Пример цепочки Remark:

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

const processor = remark()
  .use(remarkParse)          // парсинг Markdown в AST
  .use(remarkSlug)           // добавление id заголовкам
  .use(remarkStringify);     // генерация Markdown из AST

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

Здесь remarkSlug действует между парсингом и генерацией, изменяя AST, но не вмешиваясь напрямую в чтение или запись файла.


Важные аспекты композиции

1. Порядок подключения плагинов

Порядок важен, потому что каждый плагин видит AST, которое уже изменено предыдущими плагинами. Например:

  • Плагин, добавляющий ID заголовкам, должен идти после парсинга.
  • Плагин, форматирующий текст, должен идти перед генерацией Markdown или HTML.

2. Передача параметров

Большинство плагинов поддерживают конфигурацию через объект опций:

remarkSlug({ prefix: 'section-' });

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

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

Для асинхронной работы используется .process вместо .processSync:

const output = await remark()
  .use(remarkParse)
  .use(asyncPlugin)
  .use(remarkStringify)
  .process('# Асинхронный пример');

Если хотя бы один плагин асинхронный, вся цепочка должна выполняться асинхронно.


Работа с Rehype

Rehype работает по аналогичному принципу, но с HTML AST:

import { rehype } from 'rehype';
import rehypeParse from 'rehype-parse';
import rehypeStringify from 'rehype-stringify';
import rehypeHighlight from 'rehype-highlight';

const processor = rehype()
  .use(rehypeParse, { fragment: true })   // парсинг HTML
  .use(rehypeHighlight)                   // подсветка кода
  .use(rehypeStringify);                  // генерация HTML

const html = processor.processSync('<pre><code>const x = 1;</code></pre>').toString();

Особенность Rehype в том, что дерево представляет собой HTML-структуру, а не Markdown. Плагины могут изменять узлы, добавлять атрибуты, классы, обрабатывать элементы по селекторам.


Создание собственных цепочек

Композиция позволяет строить сложные пайплайны:

  • Объединение Remark и Rehype через remark-rehype для конвертации Markdown → HTML.
  • Добавление плагинов для кастомной обработки ссылок, таблиц, изображений.
  • Интеграция с ESLint, Prettier или другими инструментами для проверки и форматирования.

Пример объединения:

import remark from 'remark';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';

const processor = remark()
  .use(remarkParse)
  .use(remarkRehype)
  .use(rehypeAutolinkHeadings, { beh * avior: 'wrap' })
  .use(rehypeStringify);

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

Здесь Markdown сначала парсится в mdast, затем конвертируется в hast, и только после этого применяются Rehype-плагины.


Рекомендации по организации плагинов

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

Полезные паттерны

  • Обертка нескольких плагинов в один:
function myCompositePlugin() {
  return function transformer(tree, file) {
    pluginA()(tree, file);
    pluginB()(tree, file);
  }
}
  • Фильтрация узлов по типу или селектору для безопасного изменения только нужных элементов.
  • Ленивая инициализация плагинов, когда подключение ресурсоёмких плагинов выполняется только при необходимости.

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