Парсинг MDX в дерево

Библиотеки Remark и Rehype образуют мощный стек для работы с Markdown и HTML в экосистеме JavaScript. Основная цель при работе с MDX — преобразовать текст в абстрактное синтаксическое дерево (AST), которое позволяет анализировать, модифицировать и преобразовывать контент.


Основные концепции

  • AST (Abstract Syntax Tree) — структура данных, представляющая документ в виде дерева. Каждый узел дерева соответствует элементу Markdown или HTML: заголовок, параграф, список, ссылка, код и т.д.
  • Remark — библиотека для парсинга Markdown. Она превращает Markdown в дерево MDAST (Markdown AST).
  • Rehype — библиотека для парсинга и трансформации HTML. Она работает с деревом HAST (HTML AST).

MDX объединяет возможности Markdown и JSX. При парсинге MDX важно учитывать как стандартные элементы Markdown, так и JSX-компоненты, встроенные в текст.


Установка и подключение библиотек

npm install remark remark-parse remark-mdx rehype rehype-parse unified
  • unified — ядро, обеспечивающее конвейер обработки документов. Он позволяет объединять плагины Remark и Rehype для последовательной трансформации контента.

Пример подключения в коде:

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkMdx from 'remark-mdx';
import rehype from 'rehype';
import rehypeParse from 'rehype-parse';

Парсинг Markdown в MDAST

Создание MDAST начинается с построения процессора:

const processor = unified()
  .use(remarkParse)   // Парсер стандартного Markdown
  .use(remarkMdx);    // Добавление поддержки MDX

const mdxContent = `
# Заголовок 1

Текст параграфа с **жирным текстом**.

<MyComponent prop="value" />
`;

const tree = processor.parse(mdxContent);

console.log(JSON.stringify(tree, null, 2));

Особенности узлов MDAST:

  • type — тип узла (heading, paragraph, text, mdxJsxFlowElement).
  • children — массив дочерних узлов.
  • value — содержимое текста для узлов типа text.

Пример узла для MDX-компонента:

{
  "type": "mdxJsxFlowElement",
  "name": "MyComponent",
  "attributes": [
    {
      "type": "mdxJsxAttribute",
      "name": "prop",
      "value": "value"
    }
  ],
  "children": []
}

Конвертация MDAST в HAST

Чтобы интегрировать HTML-рендеринг, используется remark-rehype — плагин, который преобразует дерево Markdown в дерево HTML:

import remarkRehype from 'remark-rehype';

const htmlTree = unified()
  .use(remarkParse)
  .use(remarkMdx)
  .use(remarkRehype)  // Конвертация MDAST в HAST
  .parse(mdxContent);

console.log(JSON.stringify(htmlTree, null, 2));

Структура узлов HAST:

  • typeelement, text или comment.
  • tagName — имя HTML-тега (p, h1, strong).
  • properties — атрибуты HTML-элемента.
  • children — массив дочерних узлов.

Обработка JSX-компонентов

MDX позволяет встраивать JSX-компоненты. При парсинге важно учитывать:

  • mdxJsxFlowElement — блоковые компоненты.
  • mdxJsxTextElement — inline-компоненты внутри текста.
  • Атрибуты компонентов превращаются в массив объектов attributes.
  • Для трансформации MDX в HTML JSX-компоненты нужно транслировать или рендерить с использованием React.

Пример трансформации:

import { toHast } from 'mdast-util-to-hast';

const hastTree = toHast(tree, {
  allowDangerousHtml: true  // Сохраняет JSX как raw HTML
});

Модификация AST

Одним из преимуществ работы с AST является возможность модификации дерева до генерации HTML. Типичные задачи:

  • Добавление классов к заголовкам:
import visit from 'unist-util-visit';

visit(tree, 'heading', node => {
  node.data = node.data || {};
  node.data.hProperties = { className: 'custom-heading' };
});
  • Замена MDX-компонента на HTML:
visit(tree, 'mdxJsxFlowElement', node => {
  if (node.name === 'MyComponent') {
    node.type = 'element';
    node.tagName = 'div';
    node.properties = { className: 'my-component' };
    node.children = [{ type: 'text', value: 'Содержимое компонента' }];
  }
});

Обработка MDX с плагинами

Remark и Rehype поддерживают обширную экосистему плагинов:

  • remark-slug — добавляет id к заголовкам.
  • remark-autolink-headings — создаёт ссылки на заголовки.
  • rehype-highlight — подсветка синтаксиса кода.
  • rehype-sanitize — очистка HTML для безопасного рендеринга.

Использование:

import remarkSlug from 'remark-slug';
import remarkAutolinkHeadings from 'remark-autolink-headings';

const processor = unified()
  .use(remarkParse)
  .use(remarkMdx)
  .use(remarkSlug)
  .use(remarkAutolinkHeadings);

Рекомендации по производительности

  • Парсинг больших MDX-файлов выполняется быстрее при разделении на шаги: сначала MDAST, затем HAST, потом рендер.
  • Для серверного рендеринга React можно использовать @mdx-js/mdx, который строит AST и сразу генерирует JSX-функцию.
  • Для массовых модификаций дерева эффективнее использовать unist-util-visit или unist-util-visit-parents.

Эта система позволяет гибко обрабатывать MDX, обеспечивая полный контроль над деревом документа и интеграцию с HTML и React-компонентами. Использование Remark и Rehype превращает работу с Markdown и MDX из простой генерации HTML в мощный инструмент анализа и трансформации контента.