Создание 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>