跳到正文
cccskills
免费在 GitHub 公开

web-editor-lexical

Extensible text editor framework by Meta. Use when building a rich text editor on Lexical — editor setup, custom nodes, commands, transforms, and serialization.

中文简介正在准备中。当前显示原文说明。

查看安装方法

包含的文件(6)

  • SKILL.md13.6 KB
  • examples/core.md13.3 KB
  • examples/custom-nodes.md13.8 KB
  • examples/serialization.md10.2 KB
  • metadata.yaml487 B
  • reference.md6.8 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Lexical Editor Patterns

Quick Guide: Lexical is an editor framework rather than an editor: the core gives you a node tree, a selection model, a reconciler, a command bus and an update lifecycle, and everything else is a plugin. EditorState is immutable, so every read and every mutation happens inside an editor.update() or editor.read() closure, and the $-prefixed functions are the ones that require that context. Extend the tree through ElementNode, TextNode or DecoratorNode, and react to content through transforms rather than listeners. Current: v0.42.x, pre-1.0 — APIs still move between minors.

Detailed Resources:

  • examples/core.md — editor setup, plugins with cleanup, toolbars, transforms, persistence
  • examples/custom-nodes.md — ElementNode, TextNode and DecoratorNode classes, the NodeState and $config APIs
  • examples/serialization.md — JSON and HTML round trips, exportDOM/importDOM, headless editors
  • reference.md — package map, command priorities, built-in commands, node hierarchy, plugin list, custom-node checklist

Which path applies

  • A React app — LexicalComposer owns the editor and every plugin is a child component reaching it through useLexicalComposerContext(). Start at examples/core.md.
  • A server or a build step, with no DOM — createHeadlessEditor from @lexical/headless runs the same node classes for search indexing, email rendering and content transforms. Register the same nodes as the client. See examples/serialization.md.
  • Adding a content type — the work is a node class plus its registration, and the branch that matters is which base node it extends. See the decision framework below and examples/custom-nodes.md.

<critical_requirements>

Before writing Lexical code

Call $-prefixed functions inside an editor.update() or editor.read() closure. $getRoot, $getSelection, $createTextNode and their siblings read the active editor state from a context that only exists inside those closures; outside one they throw at runtime, and nothing catches it at compile time.

Register every custom node in initialConfig.nodes. An unregistered node throws or silently drops content the moment the editor meets it, including on deserialization of previously saved documents.

Return the unsubscribe function from the useEffect that registers a command, transform or listener. Every register* method hands one back, and dropping it leaks a listener per render.

Open every transform with a precondition that the mutation makes false. A transform that mutates its target unconditionally marks the node dirty, which re-triggers the transform and freezes the editor.

</critical_requirements>


Auto-detection: Lexical, @lexical/react, @lexical/rich-text, @lexical/list, @lexical/code, @lexical/link, @lexical/html, @lexical/headless, LexicalComposer, EditorState, LexicalNode, ElementNode, TextNode, DecoratorNode, createCommand, dispatchCommand, registerCommand, COMMAND_PRIORITY, $getRoot, $getSelection, $createParagraphNode, $createTextNode, RichTextPlugin, OnChangePlugin, HistoryPlugin, useLexicalComposerContext, editor.update, editor.read, registerNodeTransform, exportJSON, importJSON, exportDOM, importDOM, NodeState, createState

Applies to:

  • Rich text editing with custom formatting and embedded content
  • Custom content types — mentions, embeds, callouts, polls — as node classes
  • Structured document output rather than an HTML string
  • Server-side or build-time processing of editor content
  • Collaborative editing, where Lexical supplies the binding point

Handled elsewhere:

  • Visual design of the editor — the theme maps class names onto nodes, and what those classes contain is settled by whatever owns styling
  • Where the serialized document goes — the editor hands back JSON, and the transport and store are not its concern
  • Real-time sync between clients — Lexical exposes the state to bind, and the sync layer itself is a separate concern
  • Sanitizing HTML entering or leaving the editor — $generateNodesFromDOM trusts what it is given

<philosophy>

Lexical ships a core and no editor. The tree, selection, reconciler, command bus and update lifecycle are the product; toolbars, lists, links, embeds and formatting are all plugins, including the ones Meta writes.

EditorState is immutable. The editor holds a frozen snapshot. editor.update() clones it, applies the closure's changes, and reconciles the difference to the DOM — which is why a stale read outside a closure has no state to read and throws.

A plugin is a React component. It renders as a child of <LexicalComposer>, reaches the editor through useLexicalComposerContext(), and registers its commands, transforms and listeners in a useEffect that returns their unsubscribes. Many plugins render null.

Commands are the bus. Typed commands with priority-ordered listeners let one plugin intercept or augment another's behaviour without either knowing about the other.

Content is typed nodes. A new kind of content is a new node class, not a new attribute.

Lexical is pre-1.0, so a project that needs a frozen API surface should weigh that before adopting it. Weigh the node model too: a DecoratorNode is a real node in the tree, so it serializes and moves with the content around it. An editor whose requirement is a purely visual overlay — highlights or annotations that must never enter the saved document — is asking for something Lexical's decorators do not do.

</philosophy>

<decision_framework>

Which node type to extend

Does the content contain child nodes?
├─ YES → ElementNode (paragraphs, blockquotes, callouts)
└─ NO → Is it text carrying extra formatting or behaviour?
    ├─ YES → TextNode (coloured text, mentions)
    └─ NO → Is it an embedded component (image, video, widget)?
        └─ YES → DecoratorNode, whose decorate() returns the component

Plugin, transform or listener

Does the reaction modify nodes?
├─ YES → Transform — runs before reconciliation, so one DOM update covers it
└─ NO → Is it observing state?
    ├─ YES → registerUpdateListener, which runs after reconciliation
    └─ NO → A command, for user actions and toolbar clicks

Which command priority

Base editor behaviour?          → COMMAND_PRIORITY_EDITOR (0)
An ordinary plugin?             → COMMAND_PRIORITY_LOW (1) or _NORMAL (2)
Must override other plugins?    → COMMAND_PRIORITY_HIGH (3), as table navigation does
Nothing else may win?           → COMMAND_PRIORITY_CRITICAL (4)

Higher runs first, and returning true stops propagation to everything below. Full table in reference.md.

</decision_framework>


<patterns>

Core patterns

Pattern 1: React editor setup

LexicalComposer takes one initialConfig and wraps the plugins as children. Define the config outside the component so it is not rebuilt every render.

const initialConfig = {
  namespace: "MyEditor",
  theme, // class names per node type
  onError, // rethrow, or report — see red flags
  nodes: [HeadingNode, QuoteNode, ListNode, ListItemNode], // every node the plugins need
};

<LexicalComposer initialConfig={initialConfig}>
  <RichTextPlugin contentEditable={<ContentEditable />} ErrorBoundary={LexicalErrorBoundary} />
  <HistoryPlugin />
</LexicalComposer>;

Full code: examples/core.md


Pattern 2: The update lifecycle

editor.update() mutates, editor.read() observes, and the $ prefix marks the functions that need one of them open.

editor.update(() => {
  const paragraph = $createParagraphNode();
  paragraph.append($createTextNode("Hello world"));
  $getRoot().append(paragraph);
});

editor.read(() => $getRoot().getTextContent());

Updates batch synchronously and reconcile asynchronously; pass { discrete: true } when the DOM has to be committed before the next statement reads it.

Full code: examples/serialization.md


Pattern 3: The command system

Commands carry a typed payload and are dispatched from anywhere. Listeners register at a priority and return true to consume the command.

export const INSERT_IMAGE_COMMAND: LexicalCommand<{
  src: string;
  alt: string;
}> = createCommand("INSERT_IMAGE_COMMAND");

editor.dispatchCommand(INSERT_IMAGE_COMMAND, {
  src: "/image.png",
  alt: "Photo",
});

Full code: examples/core.md


Pattern 4: A plugin as a React component

The plugin reads the editor from context and owns its registrations for the life of the component.

export function ToolbarPlugin() {
  const [editor] = useLexicalComposerContext();
  return (
    <button type="button" onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold")}>
      Bold
    </button>
  );
}

Full code: examples/core.md


Pattern 5: Node transforms

A transform runs on every dirty node of its type before reconciliation, which makes it the cheapest place to react to content. The precondition is what stops it re-triggering itself.

editor.registerNodeTransform(TextNode, (textNode) => {
  const text = textNode.getTextContent();
  if (text.length > 0 && text[0] !== text[0].toUpperCase()) {
    textNode.setTextContent(text[0].toUpperCase() + text.slice(1));
  }
});

Leaf nodes transform first, then elements, then the root, and the whole cascade produces a single DOM reconciliation.

Full code: examples/core.md


Pattern 6: Custom nodes

Every custom node needs static getType(), static clone(), createDOM(), updateDOM(), exportJSON() / static importJSON(), and an entry in initialConfig.nodes. Private properties take a double-underscore prefix so minifiers leave them alone, and every one of them has to be JSON-serializable.

export class ImageNode extends DecoratorNode<JSX.Element> {
  __src: string;

  static getType(): string {
    return "image";
  }
  static clone(node: ImageNode): ImageNode {
    return new ImageNode(node.__src, node.__key);
  }
  updateDOM(): boolean {
    return false; // the existing element can be reused
  }
  decorate(): JSX.Element {
    return <img src={this.__src} alt="" />;
  }
}

Reach a node through its $createXxxNode() factory rather than new, so $applyNodeReplacement can run.

Full code: examples/custom-nodes.md


Pattern 7: Serialization

JSON is the persistence format — it round-trips the whole tree including custom node properties. HTML is for display and interop, and it is lossy.

const jsonString = JSON.stringify(editor.getEditorState().toJSON());

editor.setEditorState(editor.parseEditorState(jsonString).clone(null));

editor.read(() => $generateHtmlFromNodes(editor, null));

Full code: examples/serialization.md

</patterns>

<red_flags>

Red flags

Breaks at runtime:

  • A $-function called outside editor.update() / editor.read() — throws, with no compile-time warning — move the call inside the closure
  • A custom node missing from initialConfig.nodes — the editor throws or drops the content when it meets the node — register it in the same place the plugin is added
  • A transform with no precondition — the mutation marks the node dirty, re-triggering the transform until the editor freezes — guard on the condition the mutation removes
  • A register* call whose unsubscribe is dropped — one leaked listener per render and stale references after unmount — return it from useEffect
  • A node property holding a function, Map or Set — serialization breaks quietly — keep every property JSON-serializable
  • Two nodes sharing a getType() string — deserialization resolves the wrong class — namespace the type
  • new MyNode() instead of $createMyNode() — bypasses $applyNodeReplacement, so any registered replacement never runs
  • A single-underscore node property — minifiers mangle it — use __
  • Direct DOM mutation of editor content — bypasses the reconciler and desyncs state from DOM — go through nodes and commands
  • editor.update() called from inside an update listener — breaks undo/redo history and forces an extra render — use a transform
  • console.log in onError with no rethrow — swallows every editor error — rethrow or report it

Surprising behaviour:

  • Updates batch synchronously but reconcile asynchronously, so a DOM measurement taken straight after an update reads the old layout unless the update passed { discrete: true }
  • setEditorState steals focus unless the state is passed through editorState.clone(null)
  • DecoratorNode.decorate() renders its component outside the normal React tree, so state and context in it need care
  • onError that does not rethrow lets Lexical attempt its own recovery, which can mask a broken node class
  • TextNode modes change deletion: "token" makes the text an immutable chip, "segmented" deletes it word by word
  • CSS transition never fires on node removal, because the reconciler removes the element rather than hiding it
  • The NodeState API (v0.26+) is experimental and can change without an extended deprecation

</red_flags>

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

Hugging Face Inference SDK patterns for TypeScript/Node.js — InferenceClient setup, chat completion, text generation, streaming, embeddings, image generation, audio transcription, translation, summarization, and Inference Endpoints

中文简介正在准备中。当前显示原文说明。

agents-inc/skills252026年10月17日 更新

LiteLLM proxy server setup, TypeScript client patterns via OpenAI SDK, model routing, fallbacks, load balancing, spend tracking, virtual keys, and production deployment

中文简介正在准备中。当前显示原文说明。

agents-inc/skills252026年10月17日 更新

Serverless GPU compute platform for AI model deployment — web endpoints, GPU functions, model serving, and TypeScript client patterns

中文简介正在准备中。当前显示原文说明。

agents-inc/skills252026年10月17日 更新

Local LLM inference with the Ollama JavaScript client -- chat, streaming, tool calling, vision, embeddings, structured output, model management, and OpenAI-compatible endpoint

中文简介正在准备中。当前显示原文说明。

agents-inc/skills252026年10月17日 更新

Replicate SDK patterns for TypeScript/Node.js -- client setup, predictions, streaming, webhooks, file handling, model versioning, deployments, and training

中文简介正在准备中。当前显示原文说明。

agents-inc/skills252026年10月17日 更新

Together AI SDK patterns for TypeScript — client setup, chat completions, streaming, structured output, function calling, embeddings, image generation, fine-tuning, and OpenAI-compatible endpoints

中文简介正在准备中。当前显示原文说明。

agents-inc/skills252026年10月17日 更新

查看 agents-inc 的全部技能

报告此技能的问题