MDX — это расширение Markdown, которое позволяет интегрировать JSX-компоненты напрямую в текстовые документы. При работе с нестандартными требованиями к разметке часто возникает необходимость создания кастомного парсера, который обрабатывает специфические конструкции и преобразует их в нужные JSX-компоненты.
MDX основан на экосистеме remark и rehype, что делает его крайне гибким для трансформации AST (Abstract Syntax Tree). Основная идея кастомного парсера — внедрить свои правила в процесс разбора Markdown/MDX и при необходимости модифицировать структуру AST перед рендерингом.
В MDX каждый документ преобразуется в дерево узлов, где каждый узел представляет элемент Markdown или JSX. Основные типы узлов:
root — корневой узел документа.paragraph — абзац текста.heading — заголовок, с уровнем depth.text — текстовый контент.inlineCode — инлайн-код.code — блок кода с указанием языка.jsx — узел, содержащий JSX-разметку.Каждый узел имеет свойства type, value (для
текстовых узлов), а также children, если узел может
содержать вложенные элементы. Понимание структуры AST является ключевым
при написании кастомного парсера, так как трансформации выполняются
именно на этом уровне.
MDX использует систему плагинов remark и rehype для обработки Markdown и HTML соответственно. Для создания кастомного парсера необходимо написать remark-плагин, который внедряет новые синтаксические правила или изменяет существующие узлы AST.
Пример базовой структуры плагина:
function remarkCustomParser() {
return (tree) => {
// Обход всех узлов AST
visit(tree, 'text', (node) => {
if (node.value.includes('::highlight::')) {
node.type = 'jsx';
node.value = `${node.value.replace('::highlight::', '')} `;
}
});
};
}
В этом примере все текстовые узлы, содержащие метку
::highlight::, преобразуются в JSX-компонент
.
visit и
обходом дереваДля обхода AST используется функция visit из пакета
unist-util-visit. Она позволяет рекурсивно проходить по
всем узлам и выполнять операции только над нужными типами:
import { visit } from 'unist-util-visit';
visit(tree, 'heading', (node) => {
if (node.depth === 2) {
node.children.push({
type: 'text',
value: ' ?'
});
}
});
В этом примере к каждому заголовку второго уровня добавляется специальный символ. Такой подход позволяет гибко модифицировать AST перед генерацией финального MDX.
Кастомный парсер позволяет вводить свои синтаксические конструкции.
Например, можно определить блок :::note для заметок:
function remarkCustomNote() {
return (tree) => {
visit(tree, 'paragraph', (node, index, parent) => {
const match = /^:::note\s*(.*)/.exec(node.children[0]?.value);
if (match) {
parent.children[index] = {
type: 'jsx',
value: `${match[1]} `,
};
}
});
};
}
Такой подход позволяет интегрировать новые компоненты в MDX-документы без изменения исходного рендерера.
После обработки Markdown-плагинами AST передается в rehype для работы с HTML. Можно создавать rehype-плагины, чтобы:
Пример добавления класса ко всем ссылкам:
function rehypeAddLinkClass() {
return (tree) => {
visit(tree, 'element', (node) => {
if (node.tagName === 'a') {
node.properties = { ...node.properties, className: 'custom-link' };
}
});
};
}
Кастомные плагины подключаются в MDX через
mdxOptions:
import { MDXProvider } from '@mdx-js/react';
import remarkCustomParser from './remarkCustomParser';
Это позволяет автоматически применять кастомный парсер при рендеринге любого MDX-документа, обеспечивая единообразие и гибкость в проекте.