Потоковая обработка

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


Потоковый API

Для потоковой обработки используется объект Marked.Renderer в сочетании с Marked.Parser. Однако в последних версиях библиотеки потоковая обработка реализуется через transform streams, что позволяет подключать Marked напрямую к потокам данных Node.js. Это особенно полезно для больших файлов или для рендеринга Markdown по мере его поступления, например, из сети или базы данных.

Простейший пример потокового рендеринга:

import fs from 'fs';
import { marked } from 'marked';
import { Transform } from 'stream';

const markdownStream = fs.createReadStream('large.md', 'utf8');

const rendererStream = new Transform({
  transform(chunk, encoding, callback) {
    try {
      const html = marked.parse(chunk.toString());
      this.push(html);
      callback();
    } catch (err) {
      callback(err);
    }
  }
});

markdownStream.pipe(rendererStream).pipe(process.stdout);

Пояснение к коду:

  • fs.createReadStream читает файл частями, уменьшая нагрузку на память.
  • Transform обрабатывает каждый полученный фрагмент Markdown с помощью marked.parse.
  • Результат передается в стандартный вывод или может быть направлен в любой другой поток.

Настройка потокового рендеринга

Marked позволяет настраивать поведение парсера и рендерера через объект опций. Это особенно важно при потоковой обработке, так как фрагменты Markdown могут быть неполными. Ключевые параметры:

  • gfm: включает поддержку расширенного синтаксиса GitHub-flavored Markdown.
  • breaks: управляет обработкой переносов строк.
  • smartLists и smartypants: улучшают обработку списков и типографики.
  • headerIds и mangle: настройка генерации идентификаторов для заголовков и защиты email-адресов.

Пример с настройкой потокового рендерера:

import { marked } from 'marked';

marked.setOptions({
  gfm: true,
  breaks: true,
  smartLists: true,
  headerIds: true,
  mangle: false
});

Эти настройки автоматически применяются при каждом вызове marked.parse в потоке.


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

Для больших документов часто требуется рендерить Markdown по блокам, чтобы корректно обрабатывать таблицы, списки и вложенные элементы. Потоковая обработка по блокам достигается с помощью Lexer и Parser:

import { marked } from 'marked';

const lexer = new marked.Lexer();
const tokens = lexer.lex('# Заголовок\n\nТекст абзаца\n\n- Пункт 1\n- Пункт 2');

const parser = new marked.Parser();
const html = parser.parse(tokens);

console.log(html);
  • Lexer разбивает Markdown на токены.
  • Parser конвертирует токены в HTML.
  • При потоковой обработке можно токенизировать части документа по мере их поступления и парсить их последовательно.

Потоковый рендеринг с асинхронными функциями

Marked поддерживает асинхронный рендеринг, что позволяет подключать асинхронные расширения или плагины. Это удобно, если блоки Markdown требуют внешних данных (например, вставка кода с подсветкой):

import { marked } from 'marked';

const renderer = {
  async code(code, lang) {
    const highlighted = await highlightCodeAsync(code, lang);
    return `<pre><code class="lang-${lang}">${highlighted}</code></pre>`;
  }
};

marked.use({ renderer });

const html = await marked.parseAsync('# Пример кода\n\n```js\nconsole.log("Hello");\n```');
console.log(html);

Особенности:

  • parseAsync обрабатывает Markdown асинхронно.
  • Потоковый ввод может быть разбит на фрагменты, которые рендерятся асинхронно, не блокируя основной поток.

Преимущества потоковой обработки

  1. Масштабируемость: можно обрабатывать гигантские Markdown-файлы без перегрузки памяти.
  2. Интеграция с потоками Node.js: легко подключается к HTTP-запросам, файловым потокам и другим источникам данных.
  3. Гибкость рендеринга: поддержка асинхронных расширений, токенизации по блокам, настройки поведения парсера.
  4. Производительность: Marked оптимизирован для быстрого рендеринга, а потоковая обработка позволяет минимизировать задержки при обработке больших объемов данных.

Практические рекомендации

  • Для потоковой обработки больших файлов использовать чтение и парсинг по блокам, а не целиком.
  • При асинхронной обработке избегать использования marked.parse для всех данных сразу; лучше parseAsync для отдельных фрагментов.
  • Если Markdown приходит из сети, применяйте pipeline потоков Node.js для последовательной обработки и рендеринга.
  • Использовать Lexer и Parser отдельно, когда требуется более точный контроль за структурой документа.

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