Условный рендеринг

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

Основы рендеринга

При обработке Markdown каждая конструкция (тег, блок, inline-элемент) разбивается на токены. Каждый токен содержит тип, контент, уровень вложенности и дополнительные атрибуты. Пример структуры токена:

{
  type: 'paragraph_open',
  tag: 'p',
  attrs: null,
  map: [0, 1],
  nesting: 1,
  level: 0,
  children: null,
  content: ''
}

Эти токены затем проходят через рендерер, который генерирует HTML или другой формат. Условный рендеринг достигается через переопределение функций рендеринга токенов.

Настройка рендерера для условного вывода

Markdown-it предоставляет объект md.renderer.rules, где ключами являются типы токенов, а значениями — функции рендеринга. Стандартная функция имеет вид:

function(tokens, idx, options, env, self) {
  return self.renderToken(tokens, idx, options);
}

Для условного рендеринга функция может проверять любые условия: содержимое токена, параметры окружения (env), глобальные настройки.

Пример: условный рендеринг заголовков в зависимости от уровня:

const MarkdownIt = require('markdown-it');
const md = new MarkdownIt();

md.renderer.rules.heading_open = (tokens, idx, options, env, self) => {
  const token = tokens[idx];
  if (token.tag === 'h1' && env.hideH1) {
    return ''; // пропускаем H1
  }
  return self.renderToken(tokens, idx, options);
};

const env = { hideH1: true };
const result = md.render('# Скрытый заголовок\n## Видимый заголовок', env);
console.log(result);
// Выведет только <h2>Видимый заголовок</h2>

Работа с пользовательскими токенами

Markdown-it позволяет создавать собственные синтаксические конструкции через плагины. Плагин может добавлять токены с уникальным типом, а затем их рендерить условно.

Пример добавления кастомного блока spoiler:

function spoilerPlugin(md) {
  md.block.ruler.before('paragraph', 'spoiler', (state, startLine, endLine, silent) => {
    const pos = state.bMarks[startLine] + state.tShift[startLine];
    const max = state.eMarks[startLine];
    const lineText = state.src.slice(pos, max);

    if (!lineText.startsWith('!!!spoiler')) return false;

    if (!silent) {
      const token = state.push('spoiler_open', 'div', 1);
      token.attrs = [['class', 'spoiler']];
      token.content = lineText.replace('!!!spoiler', '').trim();

      const closeToken = state.push('spoiler_close', 'div', -1);
    }
    state.line = startLine + 1;
    return true;
  });

  md.renderer.rules.spoiler_open = (tokens, idx, options, env, self) => {
    if (env.showSpoilers) {
      return `<div class="spoiler">${tokens[idx].content}`;
    }
    return '';
  };

  md.renderer.rules.spoiler_close = (tokens, idx) => {
    return '</div>';
  };
}

md.use(spoilerPlugin);

const env = { showSpoilers: true };
console.log(md.render('!!!spoiler Секретная информация', env));

В данном примере содержимое блока рендерится только при условии env.showSpoilers = true.

Контекстный рендеринг через env

Параметр env передается во все функции рендеринга и является удобным способом передавать контекст:

  • Флаги показа/скрытия элементов
  • Текущую тему или стиль
  • Пользовательские данные для замены переменных

Пример условного вывода параграфов:

md.renderer.rules.paragraph_open = (tokens, idx, options, env, self) => {
  if (env.skipParagraphs) return '';
  return self.renderToken(tokens, idx, options);
};

const env = { skipParagraphs: true };
console.log(md.render('Первый параграф\n\nВторой параграф', env));
// Вывод: пусто, параграфы пропущены

Комбинирование нескольких условий

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

md.renderer.rules.text = (tokens, idx, options, env, self) => {
  let content = tokens[idx].content;

  if (env.uppercaseText) {
    content = content.toUpperCase();
  }

  if (env.replaceVars) {
    content = content.replace(/\{\{username\}\}/g, env.username || 'Guest');
  }

  return content;
};

const env = {
  uppercaseText: true,
  replaceVars: true,
  username: 'Иван'
};

console.log(md.render('Привет, {{username}}!', env));
// Вывод: ПРИВЕТ, ИВАН!

Применение условного рендеринга

  • Динамическое скрытие/показывание блоков в зависимости от прав пользователя
  • Генерация документации с учетом версии продукта или режима публикации
  • Темизация контента: светлая/темная тема, локализация текста
  • Создание интерактивных элементов вроде спойлеров, заметок и предупреждений

Советы по производительности

  • Минимизировать сложные проверки внутри функций рендеринга, лучше подготовить данные в env заранее.
  • Для больших документов использовать предварительную фильтрацию токенов.
  • Комбинировать стандартные правила рендеринга с пользовательскими, не переопределяя полностью renderToken, чтобы сохранять совместимость с другими плагинами.

Условный рендеринг в Markdown-it открывает широкие возможности кастомизации и позволяет создавать динамические, контекстно-зависимые HTML-документы, оставаясь при этом в рамках легковесного и производительного парсера Markdown.