Библиотека Marked в JavaScript предоставляет мощный
механизм для обработки Markdown, позволяя подключать и комбинировать
различные расширения. Каждое расширение может изменять поведение
парсера, добавлять новые синтаксические конструкции или модифицировать
существующие. Для начала работы с множественными расширениями необходимо
понимать структуру объекта конфигурации, который принимает
marked.use().
import { marked } from 'marked';
import myExtension from './myExtension.js';
import anotherExtension from './anotherExtension.js';
marked.use([myExtension, anotherExtension]);
В этом примере массив расширений позволяет подключать несколько модулей одновременно. Порядок подключения влияет на последовательность применения правил: первый элемент массива обрабатывается раньше остальных.
Расширения можно классифицировать по типу воздействия на Markdown:
Лексические расширения (lexer) –
изменяют или добавляют токены при разборе текста. Пример: поддержка
кастомных блоков или специальных синтаксических конструкций.
Парсерные расширения (parser) –
преобразуют токены в HTML. Используются для генерации специфической
разметки, например, для таблиц нестандартного формата или подсветки
кода.
Рендереры (renderer) –
переопределяют стандартные методы рендеринга. Это позволяет полностью
контролировать конечный HTML, например, оборачивать все заголовки в
кастомные элементы или добавлять атрибуты к ссылкам.
Для реализации собственного расширения необходимо определить объект с одной или несколькими функциями:
const customExtension = {
extensions: [
{
name: 'highlightBlocks',
level: 'block',
start(src) { return src.match(/```highlight/)?.index; },
tokenizer(src) {
const match = /^```highlight\n([\s\S]+?)\n```/.exec(src);
if (match) {
return { type: 'highlight', raw: match[0], text: match[1] };
}
},
renderer(token) {
return `<div class="highlighted">${token.text}</div>`;
}
}
]
};
marked.use(customExtension);
В данном примере создается блок highlight, который
позволяет оборачивать текст в <div> с классом
highlighted. Каждый блок расширения имеет уникальное имя и
поддерживает методы tokenizer и renderer.
При подключении нескольких расширений важно учитывать, что они могут влиять друг на друга. Существует несколько стратегий:
Пример последовательного применения:
marked.use([
{
renderer: {
link(href, title, text) {
return `<a href="${href}" target="_blank">${text}</a>`;
}
}
},
{
extensions: [
{
name: 'spoiler',
level: 'inline',
start(src) { return src.indexOf('!!') },
tokenizer(src) {
const match = /^!!(.+?)!!/.exec(src);
if (match) return { type: 'spoiler', raw: match[0], text: match[1] };
},
renderer(token) {
return `<span class="spoiler">${token.text}</span>`;
}
}
]
}
]);
В этом примере сначала переопределяется рендеринг ссылок, затем добавляется обработка спойлеров. При такой конфигурации спойлеры корректно обрабатываются даже внутри ссылок.
Основные проблемы при работе с множественными расширениями:
block или
inline), иначе возможны ошибки при токенизации.Решение конфликтов:
Marked поддерживает возможность добавления и удаления расширений на лету:
// Добавление
marked.use(dynamicExtension);
// Отключение
marked.use({ extensions: [] });
Это позволяет создавать адаптивные редакторы, где набор расширений меняется в зависимости от контекста, например, для режима предпросмотра или режима редактирования.
Эти принципы позволяют создавать мощные и гибкие конфигурации Markdown-парсера, полностью контролируя процесс разбора и рендеринга текста.