Добавление атрибутов к изображениям

Библиотеки Remark и Rehype позволяют работать с Markdown и HTML на уровне AST (Abstract Syntax Tree), что открывает гибкие возможности для модификации содержимого документа. Одной из часто востребованных задач является добавление атрибутов к изображениям, таких как alt, title, class, loading или width/height.

1. Основные концепции AST

В Remark изображения представлены узлами типа image. Пример узла:

{
  "type": "image",
  "title": "Пример изображения",
  "url": "/images/example.png",
  "alt": "Описание изображения"
}

Каждое поле может быть использовано для добавления или изменения атрибутов. В Rehype аналогичные структуры представляют HTML-теги, например, <img> с соответствующими атрибутами.

2. Добавление простых атрибутов с помощью плагинов

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

Пример плагина, добавляющего класс и атрибут loading="lazy":

const visit = require('unist-util-visit');

function addImageAttributes() {
  return (tree) => {
    visit(tree, 'image', (node) => {
      if (!node.data) node.data = {};
      if (!node.data.hProperties) node.data.hProperties = {};

      node.data.hProperties.class = 'responsive-image';
      node.data.hProperties.loading = 'lazy';
    });
  };
}

module.exports = addImageAttributes;

Здесь используется unist-util-visit для обхода AST и добавления атрибутов через hProperties, которые позже будут преобразованы в HTML.

3. Поддержка расширенных атрибутов через синтаксис Markdown

Для расширенной функциональности часто используют синтаксис с фигурными скобками для Markdown:

![Описание изображения](/images/example.png){.responsive-image loading=lazy width=600}

Для обработки такого синтаксиса применяют плагин remark-attr, который разбирает атрибуты в фигурных скобках и добавляет их в AST.

Пример настройки:

const remark = require('remark');
const remarkAttr = require('remark-attr');

remark()
  .use(remarkAttr)
  .process('![Описание изображения](/images/example.png){.responsive-image loading=lazy}', (err, file) => {
    console.log(String(file));
  });

После обработки атрибуты будут включены в HTML тег <img>:

<img src="/images/example.png" alt="Описание изображения" class="responsive-image" loading="lazy">

4. Использование Rehype для окончательной генерации HTML

Remark работает с Markdown, а Rehype отвечает за HTML. Для интеграции Remark и Rehype применяют remark-rehype:

const remark = require('remark');
const rehype = require('rehype');
const remarkRehype = require('remark-rehype');
const rehypeStringify = require('rehype-stringify');

remark()
  .use(remarkAttr)
  .use(remarkRehype)
  .use(rehypeStringify)
  .process('![Описание изображения](/images/example.png){.responsive-image loading=lazy}', (err, file) => {
    console.log(String(file));
  });

В результате получается готовый HTML с заданными атрибутами:

<img src="/images/example.png" alt="Описание изображения" class="responsive-image" loading="lazy">

5. Динамическое добавление атрибутов

Для более сложных случаев, например, когда атрибуты зависят от имени файла или других условий, можно использовать функцию, которая проверяет URL изображения и добавляет соответствующие атрибуты:

const visit = require('unist-util-visit');

function dynamicImageAttributes() {
  return (tree) => {
    visit(tree, 'image', (node) => {
      node.data = node.data || {};
      node.data.hProperties = node.data.hProperties || {};

      if (node.url.endsWith('.png')) {
        node.data.hProperties.class = 'png-image';
      } else if (node.url.endsWith('.jpg')) {
        node.data.hProperties.class = 'jpg-image';
      }

      node.data.hProperties.loading = 'lazy';
    });
  };
}

Такой подход позволяет гибко управлять визуальным стилем и производительностью за счет ленивой загрузки изображений.

6. Обработка локальных и внешних изображений

Иногда необходимо различать локальные и внешние изображения, например, для добавления referrerpolicy или crossorigin:

visit(tree, 'image', (node) => {
  node.data = node.data || {};
  node.data.hProperties = node.data.hProperties || {};

  if (node.url.startsWith('http')) {
    node.data.hProperties.crossorigin = 'anonymous';
  } else {
    node.data.hProperties.loading = 'lazy';
  }
});

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

7. Интеграция с Tailwind или другими CSS-фреймворками

Добавление классов через hProperties.class упрощает интеграцию с CSS-фреймворками. Например, для Tailwind:

node.data.hProperties.class = 'rounded-lg shadow-lg';

Это делает Markdown более выразительным без ручного редактирования HTML.


Эти методы позволяют полностью контролировать атрибуты изображений на уровне AST, обеспечивая гибкую кастомизацию и интеграцию с фронтенд-стилями и оптимизацией производительности.