Remark и Rehype — это две взаимодополняющие библиотеки для работы с текстом и HTML в экосистеме JavaScript. Remark специализируется на обработке Markdown, а Rehype — на HTML-деревьях. Обе библиотеки используют формат unist для представления дерева синтаксического анализа, что позволяет легко трансформировать контент, внедрять плагины и создавать кастомные обработки текста.
Ключевой момент: Remark превращает Markdown в AST (abstract syntax tree), Rehype — HTML в AST. После трансформации AST можно модифицировать с помощью плагинов и затем генерировать обратно Markdown или HTML.
В основе Remark и Rehype лежит единая структура unist, состоящая из узлов с полями:
type — тип узла (text,
paragraph, heading, element и т.
д.)children — массив дочерних узловvalue — значение узла (только для листовых узлов,
например, text)Пример структуры узла для абзаца Markdown:
{
"type": "paragraph",
"children": [
{
"type": "text",
"value": "Это пример текста в абзаце."
}
]
}
Для HTML элемент в Rehype выглядит как:
{
"type": "element",
"tagName": "p",
"properties": {},
"children": [
{
"type": "text",
"value": "Это пример параграфа HTML."
}
]
}
Remark предоставляет удобный API для обработки Markdown. Основные методы:
remark().use(plugin) — подключение плагинаprocessSync(markdown) — синхронная обработка
Markdownprocess(markdown) — асинхронная обработкаПример простой трансформации:
import { remark } from 'remark';
import remarkGfm from 'remark-gfm';
const markdown = '# Заголовок\n\nНекоторый текст';
const result = remark()
.use(remarkGfm)
.processSync(markdown)
.toString();
console.log(result);
Ключевой момент: Плагины могут изменять AST, добавлять узлы, модифицировать текст или внедрять новые возможности, такие как таблицы или чекбоксы.
Rehype работает аналогично, но с HTML:
rehype().use(plugin) — подключение плагинаparse(html) — парсинг HTML в ASTstringify(tree) — генерация HTML из ASTПример:
import { rehype } from 'rehype';
import rehypeParse from 'rehype-parse';
import rehypeStringify from 'rehype-stringify';
const html = '<h1>Заголовок</h1><p>Текст абзаца</p>';
const result = rehype()
.use(rehypeParse, { fragment: true })
.use(rehypeStringify)
.processSync(html)
.toString();
console.log(result);
Оптимистичная трансформация означает изменение AST с минимальными проверками на существование узлов и типов, с ожиданием, что структура документа в целом корректна. Подобный подход ускоряет обработку и уменьшает количество кода для проверок.
Пример: добавление CSS-класса ко всем параграфам в HTML через Rehype:
import { visit } from 'unist-util-visit';
import { rehype } from 'rehype';
import rehypeParse from 'rehype-parse';
import rehypeStringify from 'rehype-stringify';
const html = '<p>Первый абзац</p><p>Второй абзац</p>';
function addClassToParagraphs() {
return (tree) => {
visit(tree, 'element', (node) => {
if (node.tagName === 'p') {
node.properties = node.properties || {};
node.properties.className = 'highlight';
}
});
};
}
const result = rehype()
.use(rehypeParse, { fragment: true })
.use(addClassToParagraphs)
.use(rehypeStringify)
.processSync(html)
.toString();
console.log(result);
Пояснение: Здесь нет проверок на наличие дочерних
узлов children, потому что visit обходит
дерево и корректно применяет функцию только к элементам p.
Такой подход позволяет быстро вносить массовые изменения.
Для полного цикла обработки Markdown → HTML → модификации → HTML используют комбинацию:
remark для Markdownremark-rehype для конвертации в HTMLrehype для дополнительных трансформацийrehype-stringify для генерации HTMLПример цепочки:
import { remark } from 'remark';
import remarkGfm from 'remark-gfm';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
const markdown = '# Заголовок\n\nТекст абзаца';
const result = remark()
.use(remarkGfm)
.use(remarkRehype)
.use(() => (tree) => {
// Оптимистичная трансформация: все h1 становятся h2
tree.children.forEach(node => {
if (node.tagName === 'h1') node.tagName = 'h2';
});
})
.use(rehypeStringify)
.processSync(markdown)
.toString();
console.log(result);
Ключевой момент: Оптимистичные трансформации позволяют быстро модифицировать структуру документа без сложной валидации дерева.
unist-util-visit — рекурсивно обходит все узлы
указанного типаunist-util-map — создает новое дерево с изменениями без
мутацийunist-util-filter — фильтрует узлы по условиюПример использования visit для изменения текста всех
заголовков:
import { visit } from 'unist-util-visit';
function emphasizeHeadings() {
return (tree) => {
visit(tree, 'heading', node => {
node.children.forEach(child => {
if (child.type === 'text') child.value = child.value.toUpperCase();
});
});
};
}
visit для обхода: это
стандартный подход для массовых изменений AST.Этот подход позволяет строить сложные конвейеры обработки текста, трансформировать Markdown и HTML динамически и создавать собственные плагины без излишней нагрузки на проверку структуры документа.