Библиотека unified предоставляет гибкую архитектуру для работы с деревьями синтаксиса (AST) в экосистеме JavaScript. Центральной концепцией в этой архитектуре является middleware-подход, который позволяет строить конвейеры обработки данных в виде цепочки плагинов. Каждый плагин в этой цепочке выступает как middleware, изменяя или анализируя AST и передавая результат следующему звену.
В основе подхода лежит идея цепочки обработки:
Каждый трансформер можно рассматривать как middleware: он принимает AST, модифицирует его и передает дальше.
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';
const processor = unified()
.use(remarkParse) // Парсер Markdown в AST
.use(() => tree => { // Простой middleware-трансформер
tree.children.push({
type: 'paragraph',
children: [{ type: 'text', value: 'Добавленный текст.' }]
});
})
.use(remarkStringify); // Компилятор AST обратно в Markdown
Ключевая идея: каждый .use()
регистрирует middleware, который будет вызван при обработке
документа.
Middleware в unified реализуются как функции с одной из двух сигнатур:
Трансформер с доступом к AST:
function transformer(tree, file) {
// tree — AST документа
// file — объект VFile с метаданными
}Фабрика middleware (с настройками):
function plugin(options) {
return function transformer(tree, file) {
// использование options для конфигурации
}
}Это позволяет создавать параметризованные плагины, которые можно переиспользовать в разных проектах.
Unified поддерживает асинхронные плагины, что особенно важно при интеграции с внешними сервисами или при выполнении операций ввода-вывода.
import { unified } from 'unified';
import remarkParse from 'remark-parse';
async function asyncTransformer(tree, file) {
await someAsyncOperation();
tree.children.push({ type: 'paragraph', children: [{ type: 'text', value: 'Асинхронный текст' }] });
}
const processor = unified()
.use(remarkParse)
.use(asyncTransformer);
Middleware могут быть смешанными: одни синхронные, другие асинхронные. Unified корректно обрабатывает их в единой цепочке.
fileКаждое middleware получает объект file (VFile), который
содержит:
file.path — путь к исходному файлу.file.contents — исходный текст.file.data — объект для хранения промежуточных данных
между middleware.Пример использования:
function logTransformer(tree, file) {
console.log('Обработка файла:', file.path);
file.data.processed = true;
}
Middleware могут использовать file.data для передачи
информации между собой, формируя сквозной контекст
обработки.
Unified позволяет объединять разные экосистемы через конвертеры:
Middleware цепочка может выглядеть так:
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
const processor = unified()
.use(remarkParse)
.use(myRemarkMiddleware)
.use(remarkRehype)
.use(myRehypeMiddleware)
.use(rehypeStringify);
Важно понимать, что middleware, работающие с AST Markdown, не смогут напрямую работать с AST HTML, и наоборот. Конвертеры выступают границей между экосистемами.
Пример условной активации:
function conditionalPlugin(options) {
return (tree, file) => {
if (file.path.endsWith('.md')) {
tree.children.push({ type: 'paragraph', children: [{ type: 'text', value: options.text }] });
}
};
}
Middleware-паттерн упрощает пошаговую отладку:
unist-util-inspect) для проверки промежуточного
состояния.file.data для отслеживания
изменений.import { inspect } from 'unist-util-inspect';
function debugTransformer(tree, file) {
console.log(inspect(tree));
}
Middleware может интегрироваться с API, базами данных или другими сервисами. Асинхронные плагины позволяют:
async function fetchDataMiddleware(tree) {
const data = await fetchSomeData();
tree.children.push({ type: 'paragraph', children: [{ type: 'text', value: data }] });
}
file для передачи данных между
плагинами.Middleware-подход обеспечивает модульность, масштабируемость и чистоту архитектуры, что особенно важно при построении сложных систем обработки текстового контента.