Рендеринг блоков кода

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


Определение блоков кода

В Markdown блок кода задается двумя способами:

  1. Инлайн-код — обрамляется одиночными обратными апострофами (`):
`console.log('Hello, world!')`
  1. Многострочный блок кода — обрамляется тройными обратными апострофами (```), с возможным указанием языка:
```javascript
function sum(a, b) {
    return a + b;
}

Для рендеринга **многострочных блоков кода** Marked предоставляет отдельный метод `renderer.code`.

---

#### Кастомизация рендерера

Marked использует объект `Renderer`, позволяющий переопределять поведение отдельных элементов Markdown. Для блоков кода используется метод:

```javascript
const { marked } = require('marked');

const renderer = new marked.Renderer();

renderer.code = (code, language, isEscaped) => {
    const langClass = language ? ` class="language-${language}"` : '';
    return `<pre><code${langClass}>${isEscaped ? code : escapeHtml(code)}</code></pre>`;
};

marked.setOptions({ renderer });

Пояснения по параметрам:

  • code — текст блока кода.
  • language — язык программирования, если он указан после тройных апострофов.
  • isEscaped — флаг, указывающий, была ли строка уже экранирована.

Метод escapeHtml необходим для безопасного преобразования специальных HTML-символов (<, >, &) в соответствующие сущности.


Интеграция с подсветкой синтаксиса

Чтобы блоки кода выглядели более наглядно, часто используют подсветку синтаксиса. Популярной библиотекой для этого является highlight.js:

const hljs = require('highlight.js');

renderer.code = (code, language) => {
    const validLang = hljs.getLanguage(language) ? language : 'plaintext';
    const highlighted = hljs.highlight(code, { language: validLang }).value;
    return `<pre><code class="hljs language-${validLang}">${highlighted}</code></pre>`;
};

Особенности реализации:

  • Проверка hljs.getLanguage(language) предотвращает ошибки при неизвестных языках.
  • Метод hljs.highlight возвращает HTML с уже добавленными CSS-классами для подсветки.
  • Добавление класса hljs позволяет применить стандартные стили highlight.js.

Асинхронная обработка кода

В некоторых случаях блоки кода могут требовать асинхронной обработки, например, запрос подсветки с внешнего сервиса или вычисление содержимого. Marked поддерживает асинхронный рендеринг через функцию marked.parse с опцией async: true:

const markedAsync = async (markdownText) => {
    return await marked.parse(markdownText, { 
        renderer,
        async: true
    });
};

В сочетании с асинхронными библиотеками подсветки кода это позволяет выполнять вычисления и возвращать результат без блокировки основного потока.


Рендеринг с учетом дополнительных атрибутов

Для расширенного контроля можно добавлять data-атрибуты, id или title к блокам кода:

renderer.code = (code, language) => {
    const langClass = language ? `language-${language}` : '';
    return `<pre data-language="${language || 'none'}"><code class="${langClass}">${escapeHtml(code)}</code></pre>`;
};

Это открывает возможность интеграции с фронтенд-виджетами, например, редакторами кода или кнопками копирования.


Пример полной конфигурации

const { marked } = require('marked');
const hljs = require('highlight.js');

const renderer = new marked.Renderer();

renderer.code = (code, language) => {
    const lang = hljs.getLanguage(language) ? language : 'plaintext';
    const highlightedCode = hljs.highlight(code, { language: lang }).value;
    return `<pre><code class="hljs language-${lang}">${highlightedCode}</code></pre>`;
};

marked.setOptions({
    renderer,
    gfm: true,
    breaks: true,
    smartLists: true,
    smartypants: true
});

const markdownText = `
\`\`\`javascript
console.log('Marked + Highlight.js');
\`\`\`
`;

const html = marked(markdownText);
console.log(html);

Ключевые моменты при работе с блоками кода

  • Экранование HTML: всегда проверять, экранированы ли символы <, > и &.
  • Проверка языка: перед подсветкой синтаксиса необходимо убедиться, что библиотека поддерживает указанный язык.
  • Добавление классов: использование классов language-<lang> и hljs упрощает стилизацию через CSS.
  • Асинхронный рендеринг: полезен при работе с внешними сервисами или тяжёлыми вычислениями.

Использование renderer.code в Marked открывает полный контроль над тем, как блоки кода будут отображаться, сочетая безопасность, подсветку синтаксиса и гибкость кастомизации под любые требования.