From 7bdf7483ed78e91162e59e9f002852da8017a5e5 Mon Sep 17 00:00:00 2001 From: hnldlsjzt Date: Wed, 29 Jul 2026 09:43:52 +0100 Subject: [PATCH] fix(core): skip full-tree serialization when onNodesChange is not provided The Editor component unconditionally subscribes with a collector that calls query.serialize() (a JSON.stringify of the entire node tree) on every store change, solely to detect whether the onNodesChange callback should fire. For consumers that never pass onNodesChange - typically read-only viewers/renderers that receive frequent runtime prop updates - this serialization is pure overhead: on a page with ~500 nodes (~450KB of serialized JSON) every incoming data update paid a full-tree stringify, which profiling showed to be the single largest JS cost and a major source of GC pressure. Only set up the subscription when the consumer explicitly provides onNodesChange. Editors that pass the callback are unaffected. --- packages/core/src/editor/Editor.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/core/src/editor/Editor.tsx b/packages/core/src/editor/Editor.tsx index 2cbb22771..c4d2221f9 100644 --- a/packages/core/src/editor/Editor.tsx +++ b/packages/core/src/editor/Editor.tsx @@ -92,6 +92,16 @@ export const Editor = ({ children, ...options }: EditorProps) => { }, [context, options.enabled]); React.useEffect(() => { + // Only set up the serialize-and-compare subscription when the consumer + // actually provided an onNodesChange callback. The subscription serializes + // the entire node tree (JSON.stringify) on every store change just to + // detect whether the callback should fire, which is pure overhead for + // consumers that never passed onNodesChange (e.g. read-only viewers + // receiving frequent runtime prop updates). + if (!context || !optionsRef.current.onNodesChange) { + return; + } + context.subscribe( (_) => ({ json: context.query.serialize(),