Работа с внешними изображениями

Remark и Rehype — это мощные инструменты для парсинга и трансформации Markdown и HTML в экосистеме JavaScript. Работа с изображениями, особенно с внешними ресурсами, требует понимания структуры AST (Abstract Syntax Tree) и возможностей плагинов для корректной обработки ссылок, оптимизации и интеграции с другими системами.


1. Основы работы с изображениями в Markdown через Remark

Markdown представляет изображения с помощью синтаксиса:

![Alt текст](URL "Заголовок")

В дереве AST, которое создаёт Remark, каждая запись изображения хранится как узел типа image:

{
  "type": "image",
  "url": "https://example.com/image.jpg",
  "alt": "Описание изображения",
  "title": "Заголовок"
}

Основные свойства узла:

  • url — путь к изображению. Может быть локальным или внешним.
  • alt — альтернативный текст для доступности.
  • title — необязательный заголовок, который отображается при наведении.

Remark позволяет перебирать узлы с помощью утилит unist-util-visit и модифицировать их, например, добавлять префиксы, менять домен или подготавливать изображения для дальнейшей оптимизации.


2. Преобразование Markdown в HTML через Rehype

После обработки Markdown через Remark можно использовать Rehype для генерации HTML. В этом процессе узлы типа image преобразуются в HTML-теги <img>:

<img src="https://example.com/image.jpg" alt="Описание изображения" title="Заголовок">

Rehype предоставляет API для манипуляции DOM-подобной структурой, что удобно для:

  • добавления lazy-loading,
  • вставки srcset для адаптивных изображений,
  • интеграции с CDN или внешними сервисами оптимизации.

Пример плагина для добавления lazy-loading:

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

function rehypeLazyLoad() {
  return (tree) => {
    visit(tree, 'element', (node) => {
      if (node.tagName === 'img') {
        node.properties.loading = 'lazy';
      }
    });
  };
}

3. Обработка внешних изображений

Внешние изображения требуют особого внимания:

  1. Проверка доступности URL Перед рендерингом можно проверять, что изображение реально существует, чтобы избежать битых ссылок. Например, через fetch или специальные утилиты для prefetching.

  2. Оптимизация размеров и форматов Использование сервисов типа Cloudinary, Imgix или Squoosh API позволяет подгружать оптимизированные изображения. В Remark/Rehype это реализуется через преобразование url узлов:

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

function optimizeExternalImages() {
  return (tree) => {
    visit(tree, 'image', (node) => {
      if (node.url.startsWith('https://example.com')) {
        node.url = node.url + '?format=webp&width=800';
      }
    });
  };
}
  1. Добавление атрибутов для SEO и доступности

    • alt всегда должен присутствовать.
    • title полезен для подсказок.
    • loading="lazy" уменьшает время первичной загрузки страницы.
    • decoding="async" улучшает производительность.

4. Использование Remark и Rehype вместе для внешних изображений

Часто применяют цепочку плагинов:

const { unified } = require('unified');
const remarkParse = require('remark-parse');
const remarkRehype = require('remark-rehype');
const rehypeStringify = require('rehype-stringify');

unified()
  .use(remarkParse)
  .use(optimizeExternalImages) // кастомный плагин для оптимизации
  .use(remarkRehype)
  .use(rehypeLazyLoad)        // добавление lazy-loading
  .use(rehypeStringify)
  .process(markdownContent)
  .then((file) => {
    console.log(String(file));
  });

В такой цепочке Markdown сначала анализируется, затем модифицируется для оптимизации изображений, преобразуется в HTML и получает дополнительные атрибуты для повышения производительности.


5. Обработка различных форматов и CDN

Remark/Rehype легко интегрируются с внешними CDN. Для этого в плагине можно проверять расширение файла (.jpg, .png, .svg) и менять URL под требования сервиса. Например, для WebP:

if (node.url.endsWith('.jpg')) {
  node.url = node.url.replace('.jpg', '.webp');
}

Для SVG можно добавить class или role="img" для доступности:

if (node.url.endsWith('.svg')) {
  node.data = node.data || {};
  node.data.hProperties = node.data.hProperties || {};
  node.data.hProperties.role = 'img';
}

6. Интеграция с React и Next.js

При использовании React можно конвертировать узлы в компоненты <Image> (например, Next.js) через кастомный рендерер:

import Image from 'next/image';

function renderImage(node) {
  return (
    <Image
      src={node.url}
      alt={node.alt}
      title={node.title}
      width={800}
      height={600}
      loading="lazy"
    />
  );
}

Использование Rehype с кастомными обработчиками позволяет автоматически подменять теги <img> на React-компоненты с полной поддержкой оптимизации и SSR.


7. Особенности безопасности

При работе с внешними изображениями важно учитывать:

  • XSS-атаки через URL — всегда проверять, что URL начинается с https:// или доверенного домена.
  • CORS — некоторые изображения могут быть недоступны для скриптов, если сервер не разрешает cross-origin запросы.
  • Размеры — при большом числе внешних изображений может замедляться рендеринг, особенно на мобильных устройствах.

Эффективная работа с внешними изображениями в Remark и Rehype строится на комбинации анализа AST, модификации узлов, оптимизации URL и добавления атрибутов для производительности и доступности. Это позволяет создавать современные, быстрые и безопасные сайты с Markdown-контентом.