Блочные элементы

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

Определение блочных элементов

Блочные элементы — это элементы, которые занимают всю доступную ширину контейнера и формируют отдельные блоки текста или структуры документа. В Markdown к блочным элементам относятся:

  • Заголовки (# Заголовок)
  • Абзацы
  • Списки (упорядоченные и неупорядоченные)
  • Блоки цитат (> Цитата)
  • Кодовые блоки (fenced code blocks)
  • Горизонтальные линии (--- или ***)

В Rehype блочные элементы соответствуют HTML-тегам уровня блока, таким как <p>, <div>, <section>, <ul>, <ol>, <blockquote>, <pre> и <h1><h6>.

Структура блочного узла в AST

Узел блочного элемента в AST Remark/Rehype имеет стандартные свойства:

  • type — тип узла (paragraph, heading, list, blockquote, code и т.д.)
  • children — массив вложенных узлов (текстовых или других элементов)
  • data — дополнительные данные или метаинформация (часто используется в плагинах)
  • position — информация о позиции в исходном документе (начальная и конечная строки, столбцы)

Пример блочного узла абзаца в Remark:

{
  type: 'paragraph',
  children: [
    {
      type: 'text',
      value: 'Это пример текста абзаца.'
    }
  ],
  position: {
    start: { line: 1, column: 1 },
    end: { line: 1, column: 28 }
  }
}

Создание и модификация блочных элементов

Remark предоставляет возможность создавать узлы программно через утилиту unist-builder или напрямую формируя объекты узлов. Пример создания заголовка и абзаца:

import { u } from 'unist-builder';

const heading = u('heading', { depth: 2 }, [u('text', 'Заголовок второго уровня')]);
const paragraph = u('paragraph', [u('text', 'Содержимое абзаца')]);

Эти узлы затем могут быть добавлены в массив children корневого документа (root), формируя полноценное дерево Markdown-документа.

Работа с вложенными блочными элементами

Блочные элементы могут содержать другие блочные элементы, если это поддерживается спецификацией Markdown. Например, список может содержать несколько абзацев:

{
  type: 'list',
  ordered: false,
  children: [
    {
      type: 'listItem',
      children: [
        {
          type: 'paragraph',
          children: [{ type: 'text', value: 'Первый элемент списка' }]
        },
        {
          type: 'paragraph',
          children: [{ type: 'text', value: 'Дополнительная строка в элементе' }]
        }
      ]
    }
  ]
}

Особенность работы с такими вложениями — необходимость поддерживать корректную структуру AST, чтобы парсеры и рендереры корректно интерпретировали документ.

Трансформации блочных элементов

Remark и Rehype позволяют применять плагины для изменения дерева AST. Типичные операции с блочными элементами включают:

  • Добавление новых элементов (append, prepend)
  • Изменение содержимого (replaceWith, map)
  • Удаление ненужных блоков (filter)
  • Изменение структуры (например, конвертация <blockquote> в <div class="quote">)

Пример плагина для замены всех заголовков третьего уровня на заголовки второго уровня:

function transformHeadings() {
  return (tree) => {
    visit(tree, 'heading', (node) => {
      if (node.depth === 3) {
        node.depth = 2;
      }
    });
  };
}

Связь блочных и встроенных элементов

Блочные элементы могут содержать встроенные элементы (inline), такие как emphasis, strong, link или image. Это позволяет комбинировать структуры: абзац может содержать текст, выделенный жирным или курсивом, ссылки и изображения.

Пример абзаца с встроенными элементами:

{
  type: 'paragraph',
  children: [
    { type: 'text', value: 'Это ' },
    { type: 'strong', children: [{ type: 'text', value: 'важный' }] },
    { type: 'text', value: ' текст с ' },
    { type: 'emphasis', children: [{ type: 'text', value: 'выделением' }] }
  ]
}

Рендеринг блочных элементов

При использовании Rehype блочные элементы конвертируются в соответствующие HTML-теги. Remark может сначала трансформировать Markdown в AST, затем через remark-rehype этот AST преобразуется в HTML. Это обеспечивает строгую типизацию блоков и удобство последующей стилизации:

import { remark } from 'remark';
import remarkRehype from 'remark-rehype';
import { rehypeStringify } from 'rehype-stringify';

const markdown = '# Заголовок\n\nАбзац текста.';
const html = await remark()
  .use(remarkRehype)
  .use(rehypeStringify)
  .process(markdown);

console.log(String(html));
// <h1>Заголовок</h1><p>Абзац текста.</p>

Особенности обработки списков и вложенных блоков

Списки требуют отдельного внимания, так как элементы списка (listItem) могут включать несколько абзацев или вложенные списки. Правильное формирование структуры обеспечивает корректный рендеринг:

  • Упорядоченные списки (ordered: true) сохраняют нумерацию
  • Неупорядоченные списки (ordered: false) отображаются с маркерами
  • Вложенные списки инкапсулируются внутри children соответствующего элемента

Позиционная информация и отладка

Каждый блочный узел содержит координаты начала и конца (position), что позволяет точно определять место элемента в исходном Markdown. Это полезно для инструментов линтинга, подсветки ошибок и интерактивного редактирования документов.


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