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

Для обработки таких изображений на уровне AST (Abstract Syntax Tree) в экосистеме JavaScript используются библиотеки Remark (для Markdown) и Rehype (для HTML). Remark преобразует Markdown в AST, который затем можно анализировать, изменять и конвертировать в другие форматы, включая HTML через Rehype.
В Remark каждый элемент Markdown представлен узлом
(node). Изображения имеют тип image и обладают
следующими ключевыми свойствами:
"image"Пример AST для изображения:
{
"type": "image",
"title": "Заголовок",
"url": "путь/к/изображению.jpg",
"alt": "Альтернативный текст"
}
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 с Remark можно использовать Rehype для генерации HTML. Обычно процесс включает два шага:
remark-rehype.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 = '';
const html = await unified()
.use(remarkParse)
.use(remarkImageAbsoluteUrl, { baseUrl: 'https://example.com/' })
.use(remarkRehype)
.use(rehypeStringify)
.process(markdown);
console.log(String(html));
Результат:

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);
}
});
};
}
loading="lazy":function rehypeLazyLoadImages() {
return (tree) => {
visit(tree, 'element', (node) => {
if (node.tagName === 'img') {
node.properties = node.properties || {};
node.properties.loading = 'lazy';
}
});
};
}
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".
url на
внешние и локальные ресурсы позволяет избежать загрузки небезопасного
контента.alt и
title должны обрабатываться корректно для доступности и
SEO.