Инкрементальная регенерация (Incremental Regeneration) — это подход, позволяющий перерабатывать только изменённые части документа вместо полного перебора всего дерева AST (Abstract Syntax Tree). В контексте библиотек Remark и Rehype это становится особенно актуально при работе с большими Markdown- и HTML-документами, где полная трансформация может быть дорогостоящей по времени и памяти.
Процесс инкрементальной регенерации можно разделить на несколько этапов:
Парсинг исходного контента Remark и Rehype используют отдельные парсеры для Markdown и HTML соответственно. Результатом является дерево AST, где каждый узел соответствует блоку документа или элементу HTML. Ключевой момент: AST должен быть стабильным и предсказуемым, чтобы можно было отслеживать изменения на уровне узлов.
Отслеживание изменений Для инкрементальной обработки необходимо определить механизм, который фиксирует, какие узлы были изменены. Это может быть реализовано через:
unist-diff.Выборочная трансформация После определения изменённых узлов, применяются плагины Remark/Rehype только к этим частям дерева. Это позволяет:
Сборка итогового документа Обновлённые узлы
интегрируются обратно в AST, после чего дерево конвертируется в
строковое представление Markdown или HTML с помощью
remark-stringify или
rehype-stringify.
Remark предоставляет удобный интерфейс для работы с AST через
unified. Для реализации инкрементальной обработки часто
используют следующий подход:
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';
import { diff } from 'unist-diff';
const oldTree = await unified().use(remarkParse).parse(oldContent);
const newTree = await unified().use(remarkParse).parse(newContent);
const changes = diff(oldTree, newTree);
for (const change of changes) {
// Обработка изменённых узлов
}
Ключевые моменты:
unist-diff позволяет получать список изменений между
двумя AST.Rehype работает аналогично, но для HTML-деревьев. Особенность Rehype заключается в том, что HTML часто содержит сложную вложенность, поэтому инкрементальная регенерация требует аккуратного контроля родительских и дочерних узлов.
import { unified } from 'unified';
import rehypeParse from 'rehype-parse';
import rehypeStringify from 'rehype-stringify';
const oldTree = unified().use(rehypeParse).parse(oldHtml);
const newTree = unified().use(rehypeParse).parse(newHtml);
// Например, используем пользовательскую функцию для диффинга
const changedNodes = getChangedNodes(oldTree, newTree);
changedNodes.forEach(node => {
transformNode(node); // Применяем плагины только к изменённым элементам
});
const updatedHtml = unified().use(rehypeStringify).stringify(newTree);
Особенности Rehype:
Для проектов на Next.js, Astro или других генераторах статических сайтов инкрементальная регенерация через Remark/Rehype позволяет:
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import { diff } from 'unist-diff';
function incrementalRender(oldMarkdown, newMarkdown) {
const oldTree = unified().use(remarkParse).parse(oldMarkdown);
const newTree = unified().use(remarkParse).parse(newMarkdown);
const changes = diff(oldTree, newTree);
changes.forEach(node => {
unified()
.use(remarkRehype)
.runSync(node); // Трансформация только изменённого узла
});
return unified().use(remarkRehype).use(rehypeStringify).stringify(newTree);
}
Этот подход демонстрирует ключевую идею: трансформация применяется точечно, а итоговая сборка документа сохраняет целостность HTML-вывода, минимизируя лишние вычисления.