Пользовательская подсветка

Библиотека Marked — это мощный инструмент для парсинга Markdown в JavaScript, обеспечивающий высокую скорость и гибкость. Одной из наиболее востребованных возможностей является пользовательская подсветка кода, позволяющая интегрировать собственные механизмы для выделения синтаксиса внутри блоков кода.

Настройка подсветки через highlight

Marked поддерживает опцию highlight, которая вызывается для каждого блока кода. Она принимает два параметра: code и language. Результатом работы функции должна быть строка с HTML-разметкой, где код подсвечен по выбранному синтаксису.

import { marked } from 'marked';
import hljs from 'highlight.js';

marked.setOptions({
  highlight: function(code, language) {
    if (language && hljs.getLanguage(language)) {
      return hljs.highlight(code, { language }).value;
    }
    return hljs.highlightAuto(code).value;
  }
});

Ключевые моменты:

  • Если язык задан (language), библиотека highlight.js использует его напрямую.
  • Если язык не указан, применяется автоматическое определение синтаксиса через highlightAuto.
  • Функция должна возвращать безопасный HTML, готовый для вставки на страницу.

Поддержка пользовательских языков

Для специальных языков или диалектов можно расширить возможности подсветки, зарегистрировав новый язык в библиотеке подсветки:

hljs.registerLanguage('mylang', function(hljs) {
  return {
    keywords: 'если иначе пока',
    contains: [
      hljs.C_LINE_COMMENT_MODE,
      hljs.C_BLOCK_COMMENT_MODE,
      hljs.QUOTE_STRING_MODE
    ]
  };
});

marked.setOptions({
  highlight: function(code, language) {
    if (language === 'mylang') {
      return hljs.highlight(code, { language: 'mylang' }).value;
    }
    return hljs.highlightAuto(code).value;
  }
});

Такой подход позволяет интегрировать любой кастомный синтаксис, обеспечивая точное выделение элементов кода.

Асинхронная подсветка

Marked поддерживает асинхронное выполнение функции подсветки через промисы. Это полезно, если подсветка требует внешних ресурсов или сложных вычислений:

marked.setOptions({
  async highlight(code, language) {
    const result = await fetchHighlight(code, language);
    return result;
  }
});

fetchHighlight может быть любой функцией, возвращающей промис с HTML-кодом. Это расширяет возможности интеграции с внешними сервисами подсветки или WebAssembly-движками.

Использование renderer для кастомного вывода

Кроме опции highlight, библиотека позволяет создавать собственные рендереры, которые управляют выводом HTML для блоков кода:

const renderer = new marked.Renderer();

renderer.code = function(code, language) {
  const highlighted = hljs.highlightAuto(code).value;
  return `<pre class="custom-code"><code>${highlighted}</code></pre>`;
};

marked.setOptions({ renderer });

Преимущества использования кастомного рендерера:

  • Полный контроль над обёрткой <pre> и <code>.
  • Возможность добавлять дополнительные атрибуты, классы или данные.
  • Интеграция с системами скролла, нумерации строк, кнопками копирования.

Безопасность и экранирование

При создании пользовательской подсветки необходимо учитывать безопасность:

  • Не вставлять необработанный код в HTML, если он поступает из ненадёжного источника.
  • Использовать функции экранирования символов (escapeHTML) для защиты от XSS.
  • Проверять, что внешние библиотеки подсветки не добавляют уязвимые теги или скрипты.

Пример безопасного рендеринга:

function escapeHTML(html) {
  return html
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

renderer.code = function(code, language) {
  const highlighted = hljs.highlightAuto(code).value;
  return `<pre><code>${escapeHTML(highlighted)}</code></pre>`;
};

Поддержка многострочных подсветок и шаблонов

Иногда необходимо подсвечивать сложные шаблонные строки или многорядные фрагменты кода. Для этого рекомендуется:

  • Использовать регулярные выражения в пользовательских языках.
  • Разбивать текст на отдельные блоки и подсвечивать их последовательно.
  • Интегрировать с библиотеками, поддерживающими вложенные языки (например, HTML с CSS и JS внутри).

Оптимизация производительности

Большие документы Markdown с множеством блоков кода могут замедлять рендеринг. Рекомендуется:

  • Использовать highlight.js с минимальной конфигурацией языков.
  • Кешировать результат подсветки для повторяющихся фрагментов.
  • Выполнять асинхронную подсветку параллельно через промисы.

Хотите, я подготовлю следующую статью по теме Расширенные возможности рендерера Marked с примерами сложной интеграции и плагинов?