Remark и Rehype являются мощными инструментами для работы с Markdown и HTML в экосистеме JavaScript. Одна из ключевых задач при работе с этими библиотеками — корректная типизация плагинов, что позволяет избегать ошибок и повышает предсказуемость поведения кода.
Плагин в Remark или Rehype — это функция, которая может принимать опции и возвращать другую функцию, взаимодействующую с AST (Abstract Syntax Tree). Основные формы плагинов:
import { Plugin } from 'unified';
const myPlugin: Plugin = () => {
return (tree) => {
// Обработка дерева
};
};
interface MyOptions {
verbose: boolean;
}
const myPluginWithOptions: Plugin<[MyOptions]> = (options) => {
return (tree) => {
if (options.verbose) {
console.log(tree);
}
};
};
Ключевой момент: тип
Pluginможет принимать кортеж аргументов[Options], что позволяет строго типизировать передаваемые параметры.
Remark использует mdast (Markdown AST), а Rehype —
hast (HTML AST). Для корректной типизации плагина
необходимо импортировать соответствующие типы:
import { Root } from 'mdast';
import { Plugin } from 'unified';
const plugin: Plugin<[{}], Root> = () => {
return (tree) => {
// tree имеет тип Root
};
};
Root — корневой узел дерева.Heading,
Paragraph, Text, Link.Пример обработки заголовков в Markdown:
import { visit } from 'unist-util-visit';
import { Root, Heading } from 'mdast';
import { Plugin } from 'unified';
const headingPlugin: Plugin<[], Root> = () => {
return (tree) => {
visit(tree, 'heading', (node: Heading) => {
node.data = node.data || {};
node.data.customId = node.children.map(c => 'value' in c ? c.value : '').join('-');
});
};
};
Remark и Rehype позволяют передавать несколько аргументов в плагин. Для типизации необходимо использовать кортежи:
interface MyPluginOptions {
prefix: string;
enabled: boolean;
}
const pluginWithTuple: Plugin<[MyPluginOptions, number]> = (options, repeat) => {
return (tree) => {
// options имеет тип MyPluginOptions
// repeat имеет тип number
};
};
Использование кортежей повышает строгость и предотвращает передачу лишних аргументов.
AST узлы могут содержать поле data, которое используется
для хранения промежуточной информации между плагинами. Типизация
data позволяет безопасно использовать его:
import { Node } from 'unist';
interface CustomData {
customId?: string;
}
const plugin: Plugin<[], Node> = () => {
return (tree) => {
(tree.data as CustomData).customId = 'example';
};
};
Без приведения типов TypeScript не сможет корректно распознать поле
customId, поэтому часто используетсяas CustomData.
Функция visit из пакета unist-util-visit
позволяет обходить дерево AST. Ее типизация позволяет точно указать, с
каким узлом работать:
import { visit } from 'unist-util-visit';
import { Heading } from 'mdast';
visit(tree, 'heading', (node: Heading) => {
console.log(node.depth, node.children);
});
Remark и Rehype поддерживают асинхронные плагины. Типизация для
асинхронных функций аналогична синхронной, но возвращается
Promise<void>:
import { Plugin } from 'unified';
import { Root } from 'mdast';
const asyncPlugin: Plugin<[], Root> = async () => {
return async (tree) => {
await doSomething(tree);
};
};
Типизация async-плагинов позволяет интегрировать внешние API или файловые операции без потери проверки типов.
Унифицированная экосистема поддерживает цепочки плагинов:
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';
unified()
.use(remarkParse)
.use(pluginWithTuple, [{ prefix: 'id', enabled: true }, 3])
.use(remarkStringify);
Root,
Heading, Paragraph и т.д.).data для передачи
промежуточной информации.Plugin<[Options], NodeType>.Эти принципы позволяют создавать безопасные, читаемые и масштабируемые плагины для Remark и Rehype, полностью используя возможности TypeScript.