Bidirectional transformation library between markdown AST and ProseMirror document structures for the Milkdown editor
npx @tessl/cli install tessl/npm-milkdown--transformer@7.15.0@milkdown/transformer is a TypeScript library that provides bidirectional transformation between markdown AST and ProseMirror document structures. It serves as a critical bridge component in the Milkdown ecosystem, enabling seamless conversion between markdown source and the editor's internal document representation while maintaining semantic consistency and supporting extensible parser/serializer specifications.
npm install @milkdown/transformerimport {
ParserState,
SerializerState,
Parser,
Serializer,
NodeParserSpec,
MarkParserSpec,
NodeSerializerSpec,
MarkSerializerSpec
} from "@milkdown/transformer";For CommonJS:
const {
ParserState,
SerializerState
} = require("@milkdown/transformer");External Dependencies:
This package integrates with ProseMirror and remark. Key external types include:
// From @milkdown/prose/model (ProseMirror types)
import type {
Schema, // ProseMirror schema definition
Node, // ProseMirror document node
NodeType, // ProseMirror node type
MarkType, // ProseMirror mark type
NodeSpec, // ProseMirror node specification
MarkSpec, // ProseMirror mark specification
Attrs, // Node/mark attributes object
Fragment, // Collection of ProseMirror nodes
Mark // ProseMirror mark instance
} from "@milkdown/prose/model";
// From remark ecosystem
import type { remark } from "remark";
import type { Plugin, Transformer } from "unified";import { ParserState, SerializerState } from "@milkdown/transformer";
import { remark } from "remark";
import { schema } from "@milkdown/prose/model";
// Create parser to convert markdown to ProseMirror
const parser = ParserState.create(schema, remark());
const prosemirrorDoc = parser("# Hello World\n\nThis is **bold** text.");
// Create serializer to convert ProseMirror to markdown
const serializer = SerializerState.create(schema, remark());
const markdown = serializer(prosemirrorDoc);
console.log(markdown); // "# Hello World\n\nThis is **bold** text."@milkdown/transformer is built around several key components:
ParserState class providing a state machine for transforming markdown AST to ProseMirror nodesSerializerState class providing a state machine for transforming ProseMirror nodes to markdown ASTNodeParserSpec, MarkParserSpec, etc.)State machine and specification system for converting markdown AST into ProseMirror document structures with extensible node and mark processing.
class ParserState extends Stack<Node, ParserStackElement> {
static create(schema: Schema, remark: RemarkParser): Parser;
injectRoot(node: MarkdownNode, nodeType: NodeType, attrs?: Attrs): ParserState;
openNode(nodeType: NodeType, attrs?: Attrs): ParserState;
closeNode(): ParserState;
addNode(nodeType: NodeType, attrs?: Attrs, content?: Node[]): ParserState;
openMark(markType: MarkType, attrs?: Attrs): ParserState;
closeMark(markType: MarkType): ParserState;
addText(text: string): ParserState;
next(nodes: MarkdownNode | MarkdownNode[]): ParserState;
toDoc(): Node;
run(remark: RemarkParser, markdown: string): ParserState;
}
type Parser = (text: string) => Node;State machine and specification system for converting ProseMirror document structures into markdown AST with extensible node and mark processing.
class SerializerState extends Stack<MarkdownNode, SerializerStackElement> {
static create(schema: Schema, remark: RemarkParser): Serializer;
openNode(type: string, value?: string, props?: JSONRecord): SerializerState;
closeNode(): SerializerState;
addNode(type: string, children?: MarkdownNode[], value?: string, props?: JSONRecord): SerializerState;
withMark(mark: Mark, type: string, value?: string, props?: JSONRecord): SerializerState;
closeMark(mark: Mark): SerializerState;
next(nodes: Node | Fragment): SerializerState;
toString(remark: RemarkParser): string;
run(tree: Node): SerializerState;
}
type Serializer = (content: Node) => string;Interfaces and types for defining custom transformation behavior between markdown and ProseMirror elements.
interface NodeParserSpec {
match: (node: MarkdownNode) => boolean;
runner: (state: ParserState, node: MarkdownNode, proseType: NodeType) => void;
}
interface MarkParserSpec {
match: (node: MarkdownNode) => boolean;
runner: (state: ParserState, node: MarkdownNode, proseType: MarkType) => void;
}
interface NodeSerializerSpec {
match: (node: Node) => boolean;
runner: (state: SerializerState, node: Node) => void;
}
interface MarkSerializerSpec {
match: (mark: Mark) => boolean;
runner: (state: SerializerState, mark: Mark, node: Node) => void | boolean;
}Base classes, type definitions, and utility functions for stack management and type safety.
class Stack<Node, Element extends StackElement<Node>> {
size(): number;
top(): Element | undefined;
push(node: Node): void;
open(node: Element): void;
close(): Element;
}
abstract class StackElement<Node> {
abstract push(node: Node, ...rest: Node[]): void;
}
type RemarkParser = ReturnType<typeof remark>;
type MarkdownNode = Node & { children?: MarkdownNode[] };interface NodeSchema extends NodeSpec {
readonly toMarkdown: NodeSerializerSpec;
readonly parseMarkdown: NodeParserSpec;
readonly priority?: number;
}
interface MarkSchema extends MarkSpec {
readonly toMarkdown: MarkSerializerSpec;
readonly parseMarkdown: MarkParserSpec;
}
interface RemarkPlugin<T = Record<string, unknown>> {
plugin: Plugin<[T], Root>;
options: T;
}
type JSONRecord = Record<string, JSONValue>;
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };