Парсинг изображений из Markdown

Markdown предоставляет простой синтаксис для вставки изображений:

![Альтернативный текст](путь/к/изображению.jpg "Заголовок")

Для обработки таких изображений на уровне AST (Abstract Syntax Tree) в экосистеме JavaScript используются библиотеки Remark (для Markdown) и Rehype (для HTML). Remark преобразует Markdown в AST, который затем можно анализировать, изменять и конвертировать в другие форматы, включая HTML через Rehype.


Структура AST для изображений

В Remark каждый элемент Markdown представлен узлом (node). Изображения имеют тип image и обладают следующими ключевыми свойствами:

  • type"image"
  • url – путь к изображению
  • alt – альтернативный текст
  • title – заголовок (опционально)

Пример AST для изображения:

{
  "type": "image",
  "title": "Заголовок",
  "url": "путь/к/изображению.jpg",
  "alt": "Альтернативный текст"
}

Обработка изображений с помощью Remark

Remark предоставляет возможность создавать плагины для модификации AST. Основная задача при работе с изображениями — изменение URL, добавление атрибутов, обогащение метаданных или конвертация изображений в оптимизированные форматы.

Пример плагина для преобразования всех локальных изображений в абсолютные URL:

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

function remarkImageAbsoluteUrl(baseUrl) {
  return (tree) => {
    visit(tree, 'image', (node) => {
      if (!/^https?:\/\//.test(node.url)) {
        node.url = baseUrl + node.url;
      }
    });
  };
}

Здесь visit позволяет рекурсивно обойти все узлы AST и модифицировать только те, у которых type === 'image'.


Конвертация Markdown в HTML с Rehype

После обработки Markdown с Remark можно использовать Rehype для генерации HTML. Обычно процесс включает два шага:

  1. Преобразование Markdown AST в HTML AST с помощью remark-rehype.
  2. Генерация HTML через rehype-stringify.

Пример цепочки обработки:

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import remarkImageAbsoluteUrl from './remarkImageAbsoluteUrl.js';

const markdown = '![Example](images/pic.jpg "Заголовок")';

const html = await unified()
  .use(remarkParse)
  .use(remarkImageAbsoluteUrl, { baseUrl: 'https://example.com/' })
  .use(remarkRehype)
  .use(rehypeStringify)
  .process(markdown);

console.log(String(html));

Результат:

Example

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

  1. Добавление классов и стилей Можно создать плагин для Rehype, который обходит все узлы element с тегом img и добавляет CSS-классы:
import { visit } from 'unist-util-visit';

function rehypeImageClass(className) {
  return (tree) => {
    visit(tree, 'element', (node) => {
      if (node.tagName === 'img') {
        node.properties = node.properties || {};
        node.properties.className = node.properties.className || [];
        node.properties.className.push(className);
      }
    });
  };
}
  1. Lazy Loading Для оптимизации загрузки можно автоматически добавлять loading="lazy":
function rehypeLazyLoadImages() {
  return (tree) => {
    visit(tree, 'element', (node) => {
      if (node.tagName === 'img') {
        node.properties = node.properties || {};
        node.properties.loading = 'lazy';
      }
    });
  };
}
  1. Оптимизация изображений Можно интегрировать процесс с sharp или другими инструментами для генерации WebP или создания нескольких размеров изображений, модифицируя url и добавляя srcset.

Применение плагинов в единой цепочке

Пример объединения всех вышеописанных шагов:

const html = await unified()
  .use(remarkParse)
  .use(remarkImageAbsoluteUrl, { baseUrl: 'https://example.com/' })
  .use(remarkRehype)
  .use(rehypeImageClass, 'responsive-image')
  .use(rehypeLazyLoadImages)
  .use(rehypeStringify)
  .process(markdown);

console.log(String(html));

Результат будет содержать HTML с абсолютными ссылками, классами для стилей и атрибутом loading="lazy".


Важные моменты при работе с изображениями

  • Разделение обработки: Remark лучше использовать для работы с синтаксисом Markdown, а Rehype — для HTML-специфичных операций.
  • Безопасность URL: Проверка url на внешние и локальные ресурсы позволяет избежать загрузки небезопасного контента.
  • Метаданные изображений: alt и title должны обрабатываться корректно для доступности и SEO.
  • Интеграция с сборкой: В проектах с Vite, Webpack или Next.js можно комбинировать Remark и Rehype с плагинами для автоматической оптимизации изображений.