Remark и Rehype — это мощные инструменты для работы с Markdown и HTML в экосистеме JavaScript. Они предоставляют гибкие возможности для разбора и трансформации документов. Одной из ключевых задач является работа с кастомными блоками — нестандартными конструкциями, которые не поддерживаются стандартным синтаксисом Markdown.
Remark превращает Markdown в абстрактное синтаксическое дерево (AST,
Abstract Syntax Tree). Узлы дерева имеют типы root,
paragraph, heading, list,
code и другие. Для обработки кастомных блоков важно
понимать:
type) — определяет роль
элемента (например, code для блоков кода).children) — содержат
вложенные элементы.data, value) —
могут хранить дополнительную информацию, например, язык блока кода или
метаданные.Для Rehype структура AST аналогична, но предназначена для HTML: узлы
имеют tagName, properties и
children.
Кастомные блоки часто оформляются специальными маркерами. Например:
:::tip
Это полезный совет
:::
Чтобы Remark распознавал такой блок, нужно создать плагин:
import { visit } from 'unist-util-visit';
export function remarkCustomBlocks() {
return (tree) => {
visit(tree, 'paragraph', (node, index, parent) => {
const match = /^:::(\w+)\s*$/.exec(node.children[0].value);
if (match) {
const blockType = match[1];
const contentNodes = [];
let i = index + 1;
while (i < parent.children.length) {
const nextNode = parent.children[i];
if (/^:::$/.test(nextNode.children?.[0]?.value)) break;
contentNodes.push(nextNode);
i++;
}
const customBlockNode = {
type: 'customBlock',
data: { blockType },
children: contentNodes,
};
parent.children.splice(index, i - index + 1, customBlockNode);
}
});
};
}
Ключевые моменты:
visit(tree, 'paragraph', ...) — обход всех
параграфов.customBlock с типом блока и
содержимым.После парсинга кастомного блока Markdown можно трансформировать его в HTML через Rehype. Для этого Remark-узлы нужно конвертировать в HTML-узлы:
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import { remarkCustomBlocks } from './remarkCustomBlocks.js';
unified()
.use(remarkParse)
.use(remarkCustomBlocks)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStringify)
.process(':::tip\nЭто полезный совет\n:::')
.then((file) => {
console.log(String(file));
});
В результате customBlock можно преобразовать в HTML с
нужным тегом:
export function rehypeCustomBlocks() {
return (tree) => {
visit(tree, 'element', (node) => {
if (node.tagName === 'customBlock') {
node.tagName = 'div';
node.properties = { className: [node.data.blockType] };
}
});
};
}
data можно
хранить метаданные (уровень важности, автор заметки, дата).unist-util-visit вместо ручного обхода дерева.| Тип блока | Markdown | HTML после Rehype |
|---|---|---|
| tip | :::tip\nСовет\n::: |
<div class="tip">Совет</div> |
| warning | :::warning\nПредупреждение\n::: |
<div class="warning">Предупреждение</div> |
| note | :::note\nЗаметка\n::: |
<div class="note">Заметка</div> |
Такой подход позволяет гибко расширять синтаксис Markdown и создавать интерактивные, стилизованные блоки, которые легко интегрируются в статические сайты, блоги и документацию.