Работа с потоками данных

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

Потоковый парсинг Markdown

Marked поддерживает потоковую обработку с использованием интерфейса потоков Node.js (Readable и Writable). Потоковый парсинг позволяет обрабатывать данные по мере их поступления, не загружая весь документ в память. Это критично при работе с большими файлами или динамически поступающими данными, например, из сетевых источников.

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

import { Readable, Writable } from 'stream';
import { marked } from 'marked';

const readable = Readable.from([
  '# Заголовок\n',
  'Текст абзаца с **жирным выделением**.\n'
]);

const writable = new Writable({
  write(chunk, encoding, callback) {
    console.log(chunk.toString());
    callback();
  }
});

readable.on('data', chunk => {
  const html = marked.parse(chunk.toString());
  writable.write(html);
});

В этом примере Markdown строки поступают из Readable потока и обрабатываются функцией marked.parse. Результат сразу передаётся в Writable поток.

Настройка рендерера для потоков

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

import { marked } from 'marked';

const renderer = {
  heading(text, level) {
    return `<h${level} class="custom-heading">${text}</h${level}>`;
  },
  paragraph(text) {
    return `<p class="custom-paragraph">${text}</p>`;
  }
};

marked.use({ renderer });

const html = marked.parse('# Заголовок\nТекст абзаца');
console.log(html);

Использование собственного рендерера совместимо с потоковой обработкой, так как marked.parse может вызываться на фрагментах данных по мере их поступления.

Парсинг больших файлов

При работе с большими Markdown файлами важно разделять данные на логические блоки, чтобы избежать перегрузки памяти. Для этого применяют чтение файла частями с помощью fs.createReadStream и преобразование каждой части через Marked.

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

const stream = fs.createReadStream('large-file.md', { encoding: 'utf-8' });

stream.on('data', chunk => {
  const html = marked.parse(chunk);
  process.stdout.write(html);
});

stream.on('end', () => {
  console.log('\nПарсинг завершён.');
});

Фрагментированный парсинг обеспечивает эффективную обработку и позволяет интегрировать Marked в системы генерации HTML на серверной стороне.

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

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

import { marked } from 'marked';

const asyncRenderer = {
  async code(code, language) {
    const highlighted = await someAsyncHighlighter(code, language);
    return `<pre><code>${highlighted}</code></pre>`;
  }
};

marked.use({ renderer: asyncRenderer });

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

Асинхронный рендерер позволяет безопасно обрабатывать потоки больших документов с вычислительно затратными операциями.

Потоковое использование с Transform Stream

Для более комплексной обработки данных удобно использовать Transform поток, который принимает Markdown и возвращает HTML.

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

class MarkdownTransform extends Transform {
  _transform(chunk, encoding, callback) {
    const html = marked.parse(chunk.toString());
    this.push(html);
    callback();
  }
}

const mdStream = new MarkdownTransform();

process.stdin.pipe(mdStream).pipe(process.stdout);

Такой подход позволяет подключать Marked к цепочкам потоков Node.js, включая файлы, сетевые источники и другие потоки данных.

Оптимизация потокового парсинга

  • Буферизация фрагментов: Для корректного парсинга блоков Markdown (например, списков и таблиц) рекомендуется накапливать строки до завершения логического блока.
  • Минимизация вызовов parse: Обработка слишком мелких фрагментов увеличивает накладные расходы; разумно использовать порции данных размером несколько килобайт.
  • Асинхронные операции: Для сложных рендеров рекомендуется использовать асинхронный парсер, чтобы не блокировать основной поток Node.js.

Применение в веб-приложениях

Потоковая обработка позволяет рендерить Markdown на лету в веб-приложениях, например при загрузке больших документов через WebSocket или HTTP Streaming. Потоки позволяют отображать части документа сразу, улучшая отзывчивость интерфейса.

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