lexical icon indicating copy to clipboard operation
lexical copied to clipboard

Bug: Extremely slow performance in big documents

Open hyperknot opened this issue 1 year ago • 9 comments

Lexical version: 0.29.0

Steps To Reproduce

  1. Create an extremely basic editor. No plugins, no anything, just
    const config = {
      namespace: 'test',
      onError: console.error,
    }

    editor = createEditor(config)
    editor.setRootElement(ref)
  1. Copy a large piece of text to the clipboard in a browser. I just selected all on a big Hacker News comments thread (about 500 kB characters) and copied it.
  2. Paste it into lexical using Cmd + V.
  3. Paste it into lexical using Cmd + Shift + V.

The current behavior

  • Very slow pasting with Cmd + V.
  • Extremely slow pasting - taking minutes(!!) on a M1 Macbook Pro
  • Super slow text editing once the text has been pasted. Almost 1 sec latency for inserting/deleting a character.

This is all in plain text mode. No plugins, no nothing, just a core editor.

The expected behavior

Prosemirror does all of these nearly instantly. Pasting is max 1-2 seconds, latency is fluid even with a big document.

I'm considering migrating to Prosemirror, just because of this performance problem on large documents.

Do you know if this is inherent on the design of lexical, or there is a chance of solving this?

hyperknot avatar Apr 03 '25 22:04 hyperknot

Lexical's architecture works fine until you have a lot of nodes (probably on the order of hundreds of thousands) and then it doesn't work particularly well because of the way the NodeMap is stored and how naive its garbage collection for nodes is (scanning all nodes). There are ways to fix this but it isn't a priority for anyone at the moment. If you do intend to work with really large documents I would recommend that you use something designed for that, or plan to invest in improving lexical one way or another (by writing PRs to improve it or paying someone else to).

Here's a link to a discussion on discord about ways that NodeMap could be improved to suit large documents https://discord.com/channels/953974421008293909/964928369793843230/1303854088596881510

Once that happens it would be straightforward to improve the reconciler's garbage collector to scan fewer nodes.

I haven't done any profiling on it but importDOM could probably be optimized as well.

etrepum avatar Apr 04 '25 00:04 etrepum

If it is a rich text editor written by canvas, or a virtual dom implementation, you can achieve a lot of dom rendering, similar to Google's online documents , Or Tencent Documentation

rocky-yao-guan-shou avatar Apr 04 '25 06:04 rocky-yao-guan-shou

Thank you for the replies. It's not a rich text editor, I simply want to make it behave exactly like a textarea for a start. Plain text only, no plugins, except history.

I guess I can overwrite the paste handler and implement it myself. But then, the normal, editing real-time performance still has a very high latency. I don't know how are tokens counted (is <p> one token?) but ProseMirror React puts the 1.1 million character Moby Dick book on their official demo, and it's perfectly smooth.

hyperknot avatar Apr 04 '25 10:04 hyperknot

The history support that it ships with is not memory efficient when you have documents with a lot of nodes. This is for the same reason that the editor isn’t, history just holds snapshots of the EditorState. Its performance would be improved by the same sort of optimizations that would help the editor in general.

Lexical does not do any rendering virtualization at all. All lexical nodes have at least one DOM element. It does not support alternate renderers such as canvas.

etrepum avatar Apr 04 '25 13:04 etrepum

Per your question about tokens, <p> is a ParagraphNode, the <span> inside it is a TextNode, and each <br> is a LineBreakNode so each paragraph is roughly 1 + 2 * lineCount nodes (assuming the lines are non-empty).

etrepum avatar Apr 04 '25 15:04 etrepum

One quick-fix hack could be to disable the <HistoryPlugin/> if the editorState exceeds some threshold, tho of course then you'd loose undo/redo, maybe you could then store snapshots in IndexedDB, localStorage or your database.

Another might be to add your own virtualization (using TanStack Virtual for example), tho not sure if Lexical could get confused 🤔

CanRau avatar May 17 '25 19:05 CanRau

The only rendering you can virtualize in a lexical document is the contents of a decorator node, all other rendering is managed

etrepum avatar May 17 '25 20:05 etrepum

@hyperknot

Thank you for the replies. It's not a rich text editor, I simply want to make it behave exactly like a textarea for a start. Plain text only, no plugins, except history.

I guess I can overwrite the paste handler and implement it myself. But then, the normal, editing real-time performance still has a very high latency. I don't know how are tokens counted (is <p> one token?) but ProseMirror React puts the 1.1 million character Moby Dick book on their official demo, and it's perfectly smooth.

I copy-pasted content from https://handlewithcarecollective.github.io/react-prosemirror/ to lexical-playground https://playground.lexical.dev/, it works fast...

We are experience slow performance with 500kb documents. But on initial load. if you'll find the solution, please, let me know.

erisnuts avatar Sep 04 '25 10:09 erisnuts

I think they must have fixed some of the performance issues, now pasting Moby Dick seems to be almost as fast as with ProseMirror.

hyperknot avatar Sep 04 '25 11:09 hyperknot