Библиотеки Remark и Rehype являются мощными инструментами для работы с Markdown и HTML в экосистеме JavaScript. Ключевой особенностью их архитектуры является возможность как синхронной, так и асинхронной обработки деревьев синтаксиса (AST), что позволяет гибко интегрировать плагины и оптимизировать производительность.
Синхронная обработка подразумевает выполнение всех шагов парсинга, трансформации и генерации контента в одном потоке без ожидания асинхронных операций. Такой подход подходит для большинства простых задач, где плагины не делают сетевых запросов или не используют асинхронные операции ввода-вывода.
Пример синхронной цепочки обработки с Remark:
import { remark } from 'remark';
import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';
const processor = remark()
.use(remarkParse)
.use(remarkStringify);
const markdown = '# Заголовок\n\nТекст параграфа';
const result = processor.processSync(markdown);
console.log(result.toString());
В этом примере processSync выполняет все шаги сразу:
парсинг Markdown, применение плагинов и генерацию конечного
Markdown-кода. Синхронный подход гарантирует предсказуемый порядок
выполнения, но ограничен отсутствием поддержки асинхронных операций
внутри плагинов.
Плюсы синхронной обработки:
async/await.Минусы:
Асинхронная обработка позволяет включать в цепочку плагины, которые
возвращают промисы или используют async функции. В этом
случае Remark и Rehype автоматически обрабатывают эти промисы, ожидая их
завершения перед переходом к следующему шагу.
Пример асинхронной обработки с Remark:
import { remark } from 'remark';
import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';
async function processMarkdown() {
const processor = remark()
.use(remarkParse)
.use(async function customPlugin() {
return async (tree) => {
// Асинхронная операция внутри плагина
await new Promise(resolve => setTimeout(resolve, 100));
tree.children.push({
type: 'paragraph',
children: [{ type: 'text', value: 'Асинхронный параграф' }]
});
};
})
.use(remarkStringify);
const markdown = '# Заголовок\n\nТекст параграфа';
const result = await processor.process(markdown);
console.log(result.toString());
}
processMarkdown();
В этом примере плагин выполняет асинхронную задержку с помощью
setTimeout. Метод process автоматически
ожидает завершения всех асинхронных операций. Это позволяет:
Особенности асинхронной обработки:
async функции.await для получения результата; синхронные методы
(processSync) не могут обрабатывать асинхронные
плагины.Выбор подхода зависит от характера задач и используемых плагинов:
| Характеристика | Синхронная обработка | Асинхронная обработка |
|---|---|---|
| Использование асинхронных операций | Нет | Да |
| Простота кода | Высокая | Средняя |
| Производительность на больших текстах без IO | Выше | Ниже из-за ожидания промисов |
| Поддержка плагинов с внешними запросами | Нет | Да |
| Применение в серверных и сборочных процессах | Часто | Часто |
Для статических сайтов, где все плагины работают локально, синхронная обработка обеспечивает максимальную скорость. Для генерации контента с внешних источников или динамической обработки Markdown предпочтительнее использовать асинхронную цепочку.
Remark работает с Markdown AST (MDAST), а Rehype — с HTML AST (HAST). При необходимости преобразования Markdown в HTML обычно используется комбинация remark → rehype → html, при этом обе библиотеки поддерживают синхронные и асинхронные цепочки:
import { remark } from 'remark';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
async function markdownToHtml(markdown) {
const result = await remark()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify)
.process(markdown);
return result.toString();
}
В этом процессе возможны асинхронные плагины на любом этапе: парсинг, трансформация MDAST → HAST, генерация HTML.
processSync только если все плагины
синхронные; при наличии асинхронных операций использовать
process с await.При работе с асинхронными плагинами часто возникает ситуация, когда
изменения AST не применяются из-за некорректного возвращения функции.
Рекомендуется всегда возвращать либо tree напрямую, либо
оборачивать функцию в async и использовать
await для всех внутренних промисов. Это предотвращает
неожиданные пустые результаты и ошибки при генерации Markdown/HTML.
Асинхронная и синхронная обработка в Remark и Rehype позволяет строить гибкие цепочки трансформации контента, сочетая высокую производительность и возможность интеграции внешних данных. Разделение на синхронные и асинхронные этапы обеспечивает баланс между скоростью и функциональностью.