Differences between Prosemirror and Lexical

There has been a lot of talk this week about the open source release of Lexical. The new Facebook text editor that comes to replace DraftJS. In less than a week it already collects +6k stars on github.

They affirm that their value proposition differentiates them from RTE as Prosemirror in the following points:

  1. Mutable state approach: I don’t really understand how this can be an advantage or disadvantage in reality, so I would appreciate if someone can elaborate on this point.
  2. React +18 support: The main point here and the one that is the biggest new thing in React 18 is concurrency. This feature allows you to choose which nodes to render instead of the whole tree/page, so it should be a noticeable performance improvement. Another item that one of the developers described about performance was the following:

This EditorState ↔ DOM model above allows to optimize the slowest part of most JS apps, the rendering. We introduced the concept of batching through queueMicrotask, this allows Lexical to bundle multiple updates (from plugins or external events), do the computation on a separate/new EditorState (aka pendingEditorState) and later do a single DOM reconciliation. Note that queueMicrotask happens fairly quickly, allows us to bundle synchronous operations while not causing any noticeable slowdown in the time we trigger the reconciliation process.

  1. Prevent the DOM from being manipulated externally (i.e. extensions) to guarantee that the DOM always matches the EditorState. I know that Prosemirror can have interference issues with extensions like Grammarly for this very reason.

Lexical is probably not such a mature option at the moment, however they say that Meta has assigned a full-time team to this project, something that DraftJS never had.

Do you think these features are worthy of starting a new editor? Or is the wheel being reinvented with features that would be relatively easy to incorporate into Prosemirror or Slate?

References:

4 Likes

I will start by saying that Prosemirror has been absolutely spectacular for me - both performance wise and highly functional plugins and adapting / implementing decorations that go past what a “text editor” usually feels like - the old “CK Editor” type implementation (and many others but that one comes to mind).

After reviewing Lexical, there does not seem to be a way it would be able to support my current use cases, but of course its an early beta and they make that clear. Yet, the “documentation” pages have too many blank placeholder pages; I am surprised they dont have better public facing knowledge management over at Meta? Maybe it could, but one wouldnt know by reading the sparse documentation (only the source code)

Reading through what is documented though, it appears some of the language and explanations are inspired by how Prosemirror library is written / named. Not a big deal, the language for modern editors may just be more generic but definite overlap in naming wrt Prosemirror (which highly suggests something about the architecture.) Wondering if the authors have taken inspiration from the best editor out there.

Note: I did not evaluate any source code in this evaluation - just reading the docs and playing around with the live example (which seems to at least have some bugs with toggling markdown mode but not sure)

I know I didnt directly answer your questions, perhaps someone else will take up each;

Thoughts on mutability / immutability. It likely isnt that simple. Right in their documentation:

The most common way to update the editor is to use editor.update(). Calling this function requires a function to be passed in that will provide access to mutate the underlying editor state. When starting a fresh update, the current editor state is cloned and used as the starting point. From a technical perspective, this means that Lexical leverages a technique called double-buffering during updates. There’s an editor state to represent what is current on the screen, and another work-in-progress editor state that represents future changes.

Creating an update is typically an async process that allows Lexical to batch multiple updates together in a single update – improving performance. When Lexical is ready to commit the update to the DOM, the underlying mutations and changes in the update will form a new immutable editor state. Calling editor.getEditorState() will then return the latest editor state based on the changes from the update.

Some data structures are mutable and some are immutable according to this - as I would expect. Would have to read more about their architecture and how it differs from Prosemirror though. Highly curious of @marijn’s thoughts

1 Like

I’m still waiting for the docs to get to a point where I can understand what they are doing (in regards to the document model and such) before I can properly evaluate the ideas involved.

8 Likes

Took a look at Lexical today, some brief thoughts

Lexical does not handle collaboration itself. There’s an example of a Yjs collab plugin to do that. Not sure how well yjs handles editor collaboration, but I imagine their generic approach would be inferior to Prosemirror’s which specializes in this use-case.

Lexical’s direct support for React 18 is promising, for more control over what’s rendered when. Plus the project may have more resources to offer React Native support which would be a big win.

1 Like

With Lexical, it doesn’t handle very much by itself. This is intentional, as you don’t want collaboration being in the core, costing valuable code size, if people don’t actually need it. There is a package with Yjs bindings for Lexical @lexical/yjs. I worked closely with Kevin (author of Yjs) to implement optimized bindings for Lexical – along with both unit tests and e2e tests – which can all be found in core. It’s a different approach to ProseMirror, but I actually used the Yjs ProseMirror bindings as a starting point (as well as Quill’s)! :smiley:

We actually took a lot of inspiration from ProseMirror when creating parts of Lexical. However, we also had different requirements for Lexical – and some of the requirements we needed for Facebook, Instagram and WhatsApp came from tight requirements around code size, accessibility and compatibility with React 18 (using @lexical/react).

I’d be happy to talk more about this in the future. As many of you have pointed out, Lexical is still early in its open source development phase.

5 Likes

I’m glad someone involved in the project is joining the discussion. I am following the documentation and saw some things added. Thank you for the work you are doing and sharing it.

Could you develop a little how lexical compares with prosemirror in the 3 differences that I put in the initial post? (4 if you wanted to also develop those differences you mention between Yjs-prosemirror and Yjs-lexical). :stuck_out_tongue:

I don’t have any insight about Lexical to add to the conversation, but just to mention I haven’t seen anything about document schemas with Lexical.

A lot of people use ProseMirror as “just” a very good rich text editor, but in reality it’s much more than that. ProseMirror is a full-blown schema-based structured or semantic editor, that feels like a familiar text editor. This is a remarkable, and unique (well, as far as I know) accomplishment. Structured editors have tended to be research toys or niche products because they didn’t solve this problem and instead are “a bit odd”. Maybe it’s a bad thing that there’s not more willingness in the world for people to give funky different kinds of editors a chance, but ProseMirror avoids that problem.

For the curious, here’s a thread form 2015 where Marijn goes into detail on how simple things like enter and backspace need very careful thought, and references a bunch of academic research.

See also: the blinking horizontal caret from the gap-cursor plugin : D

I’m sure loads of projects that use ProseMirror don’t need this power, but for those that do (mine does) I would guess Lexical is not an option, but I could be wrong.

5 Likes

I think the biggest mistake people make about Lexical, is assuming it’s not going to be as good as X, because X does it perfect already. Like, that might be true, but is also a general flaw in the frontend software circles. I was on the React Core team for 3+ years, and we spent a lot of time looking into APIs and how we could eliminate whole classes of issue by simply making the edge-cases implementation details of the framework itself. The same thing applies to Lexical.

We look at Lexical as being the React of editors – in terms of its declarative approach to promoting imperative APIs and how you go about reasoning intent. An example might be schemas from ProseMirror. Lexical doesn’t have schemas as a hot keyword, but it does offer the same functionality via Lexical’s node APIs. Many ElementNode and TextNode methods define the intent of an interaction or a relationship with a parent, sibling, or child. For example, see: lexical/LexicalElementNode.js at main · facebook/lexical · GitHub. You can extend one of the Lexical core nodes and just over-ride those methods and specific your heuristics for your node as needed. Which is far less boilerplate, and fully type-able in Flow/TypeScript – which is priority at Meta.

The reason we have to build these things, is because we need to have them at scale for Facebook, WhatsApp, Messenger, Instagram, Oculus etc. That means, we’re also investing in building native versions of Lexical for mobile (in native mobile languages) and also investing into building React Native bindings for those who use those surfaces. So we need something that can scale now, not something that can scale later. Lexical is already used by billions of people everyday, and we plan on expanding that to 3+ billion in the future. So Lexical most definitely isn’t a toy.

Lexical is in a fortunate position, where we can take from what has existed previously – including ProseMirror, React, Yjs, and leverage it with latest browser APIs. Lexical doesn’t support IE, or legacy Edge, and we work closely with engineers from Mozilla and Google to patch bugs in their evergreen browsers to ensure all web editors work better (that’s why we leverage beforeinput so much).

We are also trying to do best in-class for accessibility, given we have some of the best accessibility developers and advocates working at Meta with us. So we’ve taken time to make sure Lexical works properly for users of all capabilities. VoiceOver, Dragon Naturally Speaking and JAWS being key tools that fail with most web editors today. Plus, we’re aware of existing issues and we’re transparently tackling them. A good example is the horizontal caret that is awesome in ProseMirror. Unfortunately, this doesn’t pass WCAG guidelines and doesn’t work well on mobile, so whilst we supported this in the past, we’re now going back to look at how we can insert whitespace upon moving selection to a place where the user might struggle with it (like a paragraph, for example).

For collaboration, I worked closely with Kevin (author of Yjs) to ensure we could “sync” the Lexical editor state between many clients. We use the tree structure of Lexical to sync to Y.XmlElement, and Y.Text accordingly. We flatten text within a given block, and use inline Y.Map nodes between characters to infer the beginning and end of Lexical TextNodes. The Y.Map also holds all the properties of the TextNode, or user extended TextNode and scales well without having to use attribute ranges, which isn’t performant in Lexical’s case with CRDT. We are internally shipping Lexical with Yjs today and we hope to expand this in the future to more surfaces, including mobile.

If anything, the reason I’m here on the ProseMirror discussion board is to praise ProseMirror’s efforts and ideas. Without them, we wouldn’t have the Lexical we have today. We just wanted to take our spin on how we could do it, and given my experience building React and Inferno, I thought my twist on it might be appealing to this community. Innovation doesn’t end, it continues and that’s what open source is all about.

14 Likes