CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-milkdown--transformer

Bidirectional transformation library between markdown AST and ProseMirror document structures for the Milkdown editor

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

parser.mddocs/

Parser System

The parser system provides state machine functionality for converting markdown AST into ProseMirror document structures. It offers a fluent API for building complex document transformations with extensible node and mark processing.

Capabilities

ParserState Class

The main state machine class for parsing markdown AST to ProseMirror nodes.

/**
 * A state machine for parser. Transform remark AST into prosemirror state.
 */
class ParserState extends Stack<Node, ParserStackElement> {
  readonly schema: Schema;
  
  /**
   * Create a parser from schema and remark instance.
   * @param schema - ProseMirror schema defining available nodes and marks
   * @param remark - Remark parser instance for processing markdown
   * @returns Parser function that converts markdown text to ProseMirror nodes
   */
  static create(schema: Schema, remark: RemarkParser): Parser;
  
  /**
   * Inject root node for prosemirror state.
   * @param node - Markdown AST node to process
   * @param nodeType - ProseMirror node type for the root
   * @param attrs - Optional attributes for the root node
   * @returns ParserState instance for chaining
   */
  injectRoot(node: MarkdownNode, nodeType: NodeType, attrs?: Attrs): ParserState;
  
  /**
   * Open a new node, the next operations will add nodes into that new node until closeNode is called.
   * @param nodeType - ProseMirror node type to open
   * @param attrs - Optional attributes for the node
   * @returns ParserState instance for chaining
   */
  openNode(nodeType: NodeType, attrs?: Attrs): ParserState;
  
  /**
   * Close the current node and push it into the parent node.
   * @returns ParserState instance for chaining
   */
  closeNode(): ParserState;
  
  /**
   * Add a node into current node.
   * @param nodeType - ProseMirror node type to add
   * @param attrs - Optional attributes for the node
   * @param content - Optional child nodes
   * @returns ParserState instance for chaining
   */
  addNode(nodeType: NodeType, attrs?: Attrs, content?: Node[]): ParserState;
  
  /**
   * Open a new mark, the next nodes added will have that mark.
   * @param markType - ProseMirror mark type to open
   * @param attrs - Optional attributes for the mark
   * @returns ParserState instance for chaining
   */
  openMark(markType: MarkType, attrs?: Attrs): ParserState;
  
  /**
   * Close a opened mark.
   * @param markType - ProseMirror mark type to close
   * @returns ParserState instance for chaining
   */
  closeMark(markType: MarkType): ParserState;
  
  /**
   * Add a text node into current node.
   * @param text - Text content to add
   * @returns ParserState instance for chaining
   */
  addText(text: string): ParserState;
  
  /**
   * Give the node or node list back to the state and the state will find a proper runner to handle it.
   * @param nodes - Markdown nodes to process (single node or array)
   * @returns ParserState instance for chaining
   */
  next(nodes?: MarkdownNode | MarkdownNode[]): ParserState;
  
  /**
   * Build the current state into a ProseMirror document.
   * @returns The final ProseMirror document node
   */
  toDoc(): Node;
  
  /**
   * Transform a markdown string into prosemirror state.
   * @param remark - Remark parser instance
   * @param markdown - Markdown text to parse
   * @returns ParserState instance for chaining
   */
  run(remark: RemarkParser, markdown: string): ParserState;
}

Usage Examples:

import { ParserState } from "@milkdown/transformer";
import { remark } from "remark";
import { schema } from "@milkdown/prose/model";

// Create a parser
const parser = ParserState.create(schema, remark());

// Parse markdown to ProseMirror
const doc = parser("# Hello World\n\nThis is **bold** text.");

// Manual state manipulation
const state = new ParserState(schema);
state.openNode(schema.nodes.doc)
  .openNode(schema.nodes.heading, { level: 1 })
  .addText("Hello World")
  .closeNode()
  .openNode(schema.nodes.paragraph)
  .openMark(schema.marks.strong)
  .addText("bold")
  .closeMark(schema.marks.strong)
  .addText(" text")
  .closeNode()
  .closeNode();

const doc = state.toDoc();

ParserStackElement Class

Stack element implementation for parser state management.

/**
 * Stack element for parser state, holds ProseMirror nodes during transformation.
 */
class ParserStackElement extends StackElement<Node> {
  type: NodeType;
  content: Node[];
  attrs?: Attrs;
  
  /**
   * Create a new parser stack element.
   * @param type - ProseMirror node type
   * @param content - Array of child nodes
   * @param attrs - Optional node attributes
   * @returns New ParserStackElement instance
   */
  static create(type: NodeType, content: Node[], attrs?: Attrs): ParserStackElement;
  
  /**
   * Add nodes to the element's content.
   * @param node - Node to add
   * @param rest - Additional nodes to add
   */
  push(node: Node, ...rest: Node[]): void;
  
  /**
   * Remove and return the last node from content.
   * @returns Last node or undefined if empty
   */
  pop(): Node | undefined;
}

Parser Specification Types

Types for defining custom parsing behavior.

/**
 * The parser type which is used to transform markdown text into prosemirror node.
 */
type Parser = (text: string) => Node;

/**
 * The spec for node parser in schema.
 */
interface NodeParserSpec {
  /**
   * The match function to check if the node is the target node.
   * @param node - Markdown AST node to check
   * @returns true if this spec should handle the node
   */
  match: (node: MarkdownNode) => boolean;
  
  /**
   * The runner function to transform the node into prosemirror node.
   * Generally, you should call methods in state to add node to state.
   * @param state - Parser state for adding nodes
   * @param node - Markdown AST node to transform
   * @param proseType - Target ProseMirror node type
   */
  runner: (state: ParserState, node: MarkdownNode, proseType: NodeType) => void;
}

/**
 * The spec for mark parser in schema.
 */
interface MarkParserSpec {
  /**
   * The match function to check if the node is the target mark.
   * @param node - Markdown AST node to check
   * @returns true if this spec should handle the node
   */
  match: (node: MarkdownNode) => boolean;
  
  /**
   * The runner function to transform the node into prosemirror mark.
   * Generally, you should call methods in state to add mark to state.
   * @param state - Parser state for adding marks
   * @param node - Markdown AST node to transform
   * @param proseType - Target ProseMirror mark type
   */
  runner: (state: ParserState, node: MarkdownNode, proseType: MarkType) => void;
}

Specification Examples:

// Example node parser spec for paragraphs
const paragraphParserSpec: NodeParserSpec = {
  match: (node) => node.type === 'paragraph',
  runner: (state, node, type) => {
    state
      .openNode(type)
      .next(node.children)
      .closeNode();
  }
};

// Example mark parser spec for emphasis
const emphasisParserSpec: MarkParserSpec = {
  match: (node) => node.type === 'emphasis',
  runner: (state, node, type) => {
    state
      .openMark(type)
      .next(node.children)
      .closeMark(type);
  }
};

docs

index.md

parser.md

serializer.md

specifications.md

utilities.md

tile.json