MelonyParser

Low-level parser class for extracting and parsing Melony components from content strings.

Import

import { MelonyParser } from "melony";

Overview

MelonyParser is the underlying parser used by MelonyMarkdown andMelonyWidget. You typically don't need to use it directly, but it's available for advanced use cases like custom renderers, custom component registration, or building your own parsing logic.

Basic Usage

import { MelonyParser } from "melony";

const parser = new MelonyParser();

const content = `
<card title="Weather">
  <text value="Sunny" />
</card>
`;

const blocks = parser.parseContentAsBlocks(content);
console.log(blocks);

Methods

parseContentAsBlocks

(content: string) => Block[]

Parses content string into an array of blocks (text or component blocks).

const content = `
Some text here.

<card title="Hello">
  <text value="World" />
</card>

More text.
`;

const blocks = parser.parseContentAsBlocks(content);

// Returns:
// [
//   { type: "text", content: "Some text here." },
//   { type: "component", component: {...} },
//   { type: "text", content: "More text." }
// ]

registerComponent

(tag: string, componentName: string) => void

Register a custom component tag mapping.

const parser = new MelonyParser();

// Register custom component
parser.registerComponent("mywidget", "MyWidget");

// Now the parser recognizes <mywidget> tags
const content = `<mywidget prop="value" />`;
const blocks = parser.parseContentAsBlocks(content);

registerComponents

(mappings: Record<string, string>) => void

Register multiple component tag mappings at once.

const parser = new MelonyParser();

parser.registerComponents({
  customcard: "CustomCard",
  specialbutton: "SpecialButton",
  mywidget: "MyWidget",
});

Block Types

The parser returns an array of blocks with these types:

type Block = TextBlock | ComponentBlock;

interface TextBlock {
  type: "text";
  content: string;
}

interface ComponentBlock {
  type: "component";
  component: ComponentDef;
}

interface ComponentDef {
  component: string;
  props?: Record<string, any>;
  children?: (ComponentDef | string)[];
}

Custom Component Registration

Register custom component tags for specialized use cases:

import { MelonyParser } from "melony";

// Create parser with custom components
const parser = new MelonyParser();

parser.registerComponents({
  "weather-card": "WeatherCard",
  "stock-ticker": "StockTicker",
  "custom-chart": "CustomChart",
});

const content = `
<weather-card location="SF" temp="72" />
<stock-ticker symbol="AAPL" />
`;

const blocks = parser.parseContentAsBlocks(content);
// Blocks will reference "WeatherCard" and "StockTicker" components

Building Custom Renderers

Use the parser to build your own custom rendering logic:

import { MelonyParser, renderComponent } from "melony";

function customRenderer(content: string) {
  const parser = new MelonyParser();
  const blocks = parser.parseContentAsBlocks(content);

  return blocks.map((block, index) => {
    if (block.type === "text") {
      return <div key={index}>{block.content}</div>;
    } else if (block.type === "component") {
      // Custom component rendering logic
      return (
        <div key={index} className="component-wrapper">
          {renderComponent(block.component)}
        </div>
      );
    }
  });
}

Progressive Parsing

The parser is designed for progressive/streaming content:

import { MelonyParser } from "melony";
import { useState, useEffect } from "react";

function StreamingRenderer({ stream }: { stream: ReadableStream }) {
  const [content, setContent] = useState("");
  const parser = new MelonyParser();

  useEffect(() => {
    const reader = stream.getReader();
    
    async function read() {
      const { done, value } = await reader.read();
      if (done) return;
      
      const chunk = new TextDecoder().decode(value);
      setContent((prev) => prev + chunk);
      
      read();
    }
    
    read();
  }, [stream]);

  const blocks = parser.parseContentAsBlocks(content);

  return (
    <div>
      {blocks.map((block, i) => (
        <div key={i}>
          {block.type === "text" ? block.content : renderComponent(block.component)}
        </div>
      ))}
    </div>
  );
}

Complete Example

Advanced usage with custom parsing and rendering:

import { MelonyParser, renderComponent, ComponentDef } from "melony";
import { useState } from "react";

// Custom parser with additional components
const createCustomParser = () => {
  const parser = new MelonyParser();
  
  // Register custom components
  parser.registerComponents({
    "analytics-card": "AnalyticsCard",
    "chart-widget": "ChartWidget",
    "data-table": "DataTable",
  });
  
  return parser;
};

function CustomMelonyRenderer({ content }: { content: string }) {
  const parser = createCustomParser();
  const blocks = parser.parseContentAsBlocks(content);

  return (
    <div className="custom-renderer">
      {blocks.map((block, index) => {
        if (block.type === "text") {
          // Custom text rendering
          return (
            <div key={index} className="text-block">
              {block.content}
            </div>
          );
        } else {
          // Custom component rendering with wrapper
          return (
            <div key={index} className="component-block">
              <div className="component-header">
                Component: {block.component.component}
              </div>
              <div className="component-content">
                {renderComponent(block.component)}
              </div>
            </div>
          );
        }
      })}
    </div>
  );
}

export default CustomMelonyRenderer;

When to Use MelonyParser

Use MelonyParser directly when:

  • Building custom rendering logic
  • Need to register custom component tags
  • Building integrations with other libraries
  • Need low-level access to parsed blocks
  • Implementing custom progressive rendering

Use MelonyMarkdown or MelonyWidget instead when:

  • Building standard chat interfaces
  • Rendering AI responses with default components
  • No need for custom parsing logic
  • Standard use cases with built-in components

Notes

  • MelonyParser is used internally by MelonyMarkdown and MelonyWidget
  • Component registration is per-parser instance
  • The parser handles incomplete/streaming content gracefully
  • Parsed blocks can be cached for performance
  • Use renderComponent helper to render ComponentDef objects

TypeScript Types

// Import types
import type { 
  MelonyParser,
  Block,
  TextBlock,
  ComponentBlock,
  ComponentDef 
} from "melony";

// Block type
type Block = TextBlock | ComponentBlock;

interface TextBlock {
  type: "text";
  content: string;
}

interface ComponentBlock {
  type: "component";
  component: ComponentDef;
}

// Component definition
interface ComponentDef {
  component: string;
  props?: Record<string, any>;
  children?: (ComponentDef | string)[];
}

See Also

MelonyMarkdown - High-level markdown rendering component

MelonyWidget - High-level widget rendering component

MelonyProvider - Root provider component