Создание Rehype-плагина для корректировки HTML-кода при конвертировании из Markdown

23 мая 2025 г.

Простейший код конвертирования Markdown-разметки в HTML-код при помощи библиотек Remark/Rehype выглядит примерно так:

javascript
import { unified } from 'unified';
 
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
 
const getHtml = async (markdown) => {
  const vfile = await unified()
    .use(remarkParse)
    .use(remarkRehype)
    .use(rehypeStringify)
    .process(markdown);
 
  return vfile.value;
};

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

В инфраструктуре Remark/Rehype можно найти плагин почти на любой случай. Тем не менее, в этом предложении ключевое слово "почти". В моей практике нередко приходилось сталкиваться с ситуацией, когда я не мог найти подходящий плагин. В этом случае можно создать собственный плагин, который будет выполнять ровно то, что необходимо.

Далее я покажу пример создания простого Rehype-плагина, который оборачивает все элементы img в дополнительный элемент a, для того, чтобы разметка соответствовала требованиям для подключения популярной библиотеки fslightbox, раскрывающей изображения на весь экран.

javascript
import { unified } from 'unified';
import { visit } from 'unist-util-visit';
 
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
 
const rehypeImages = () => {
  return (tree) => {
    visit(tree, 'element', (node, index, parent) => {
      if (node.tagName === 'img') {
        node.properties.className = ['rounded-md'];
 
        parent.children[index] = {
          type: 'element',
          tagName: 'a',
          properties: { href: node.properties.src, 'data-fslightbox': '' },
          children: [node]
        };
      }
    });
  };
};
 
const getHtml = async (markdown) => {
  const vfile = await unified()
    .use(remarkParse)
    .use(remarkRehype)
    .use(rehypeImages)
    .use(rehypeStringify)
    .process(markdown);
 
  return vfile.value;
};

Таким образом, все элементы img в HTML-коде, которые выглядели так:

html
<img src="image.png" alt="description" />

после подключения плагина будут такими:

html
<a href="image.png" data-fslightbox="">
  <img src="image.png" class="rounded-md" alt="description" />
</a>

Рабочий пример этого решения можно посмотреть здесь.