Building Your Own WYSIWYG with LexicalJs

Articles about libraries for building your own WYSIWYG editor periodically appear on Habr. Such a need arose in my team – “Beeline House”, for creating news. In this article, we will look at them from a more general perspective and additionally analyze the library LexicalJs.

This article is based on my talk at our internal frontend meetup, so there will be slides here from time to time.

WYSIWYG — stands for What You See Is What You Get. In such an editor, the content is displayed during the editing process and looks as close as possible to the final layout that the end user will see. This is very convenient if the editor does not have much knowledge of HTML and CSS. In most cases, those who use such editors are accustomed to MS Word or Google Docs.

WYSIWYG editors have been around for quite some time. Different editors were popular at different times. Approaches to organizing their work also evolved. Let's try to form types of approaches.

  1. Formatting according to the html-in-html principle. This approach was very popular before. Its principle is that html tags are inserted into the code that can be edited and the user edits them. In this case, some errors are possible, and if you want to make some custom component, it becomes quite difficult. I noted Quill, CkeEditor (old version), TinyMCE editor on the slide

  2. The following principle was once very popular on forums. If you remember those times, or maybe still use some forums, you know about bbcode. This is when there are special characters with opening and closing square brackets. Here is a noticeable attempt to simplify the html format and provide a more user-friendly syntax. Although, judging by the fact that this approach is not particularly used now, it has not really caught on. In this class of editors, bbcode is formatted in html.

  3. Speaking about WYSIWYG editors, I can't help but mention the approach based on markdown markup. A lot of tech-savvy people prefer to keep notes in markdown. All programmers write readme in markdowm. This is actually quite convenient, and I am very glad that, for example, Notion supports markdown, and I often use it, but I am not a management company operator or a content manager. Therefore, this approach is not really suitable for solving my problem.

  4. And finally, the most popular approach today is when the original formatting after the chains of events is transformed into a data tree, and then into html. In this case, there is no direct access to html. This approach is similar to flux and what we, as front-end developers, see in the interaction of react and redux. At the same time, due to the fact that we have a tree that can be easily serialized into JSON, we can more conveniently add our custom objects. It also simplifies the ability to navigate through the history of changes. In general, now most of the available common editors for the web use exactly this approach. I also settled on this approach, so let's take a closer look at its representatives.

Let's start with DraftJs

I think for quite a long time for React developers it was a de facto standard for creating WYSIWYG. It is heavily integrated with React and was developed by Facebook. For Beeline b2c, deep integration with React does not matter much, because all our projects are on React. But if you use angular, vue or another framework, it will be a big stopper.

DraftJs provides a fairly flexible API for interaction, allowing you to implement all the necessary features for WYSIWYG. But the downside is that you need to write a fairly large harness of hooks.

Among the positive aspects, we can also highlight the large community and established practices for using this library.

The downside is the use of immutable, which already looks outdated in 2024. And the biggest downside is that the last publication was four years ago and the library is no longer maintained or developed.

The next library is EditorJs

Personally, I like the approach used in this library the most. It is a block formatting structure. You could have come across it if you wrote an article on VKontakte or Zen. Fashionable, youthful – yes, familiar to content managers and management company operators – no.

There is a problem with this approach, including in EditorJs. Text selection is limited to one block. That is, if you left-click in the middle of one paragraph and move the mouse pointer to the middle of the next paragraph to select several sentences, the text selection will stop at the end of the first paragraph. This can be avoided by turning on a special mode, but someone still needs to be shown and taught this.

Another advantage is that it is an independent framework and is being developed by a community, the core team of which includes guys from Russia.

ProseMirror

Overall, it's a good choice. It has a pretty well-designed API by the author of the book “Expressive JavaScript” Marijn Haverbeck. The library is framework-agnostic and can be used for more than just react.

The downsides, in my opinion, are that the documentation is not very good and there are few out-of-the-box features.

Now let's move on to what I chose.

Lexical

Lexical — is not only a framework, but also a platform-independent library. It is developed by Facebook and allows you to create your own WYSIWYG editor.

It is important to note that Lexical is not built for any specific platform. Rather, it is designed to be completely cross-platform and framework-agnostic, meaning that the underlying API can be used for mobile and desktop devices. There is already an officially implemented version for IOS, for example.

Let's move on to the possibilitieswhich are provided by Lexical.

All these features are provided as separate packages, so if you don't need any of them, you can skip them, saving the bundle size.

It is also important to note that Lexical does not limit the look or style of the editor's user interface. You can customize the look and feel however you want, and Lexical provides you with an API for easy operation.

As we can see, there are many possibilities. You can see for yourself by going to playground.

Let's now take a look at the library architecture.

We will start with the architecture of subsystems.

Lexical works by attaching to a contentEditable element, and from there you can work with declarative APIs.

The main api is stored in the lexical package, which is responsible for updating the appearance using its comparison mechanism (a simplified version of the react reconciliation), which allows for better performance, nodes, command execution, editor state, and some utilities. The state is stored as a tree, which you could see in the example of the editor. Accordingly, the state itself is updated using commands.

Lexical is also extended with plugins. Plugins can be either official, provided by the lexical team, or you can write your own plugin.

Now let's talk about the state.

Editor state is the underlying data model that represents what you want to display in the DOM. Editor states consist of two parts:

The editor state is immutable. To change it, you need to dispatch a command that will be processed either by the core package or by the plugin. To process commands by the plugin, these commands, of course, need to be registered. After that, the states will be compared and updated if necessary. Very similar to redux, as I already said.

Editor states are also fully converted to JSON and can be easily parsed back into an editor using editor.parseEditorState().

Library capabilities can be extended with your own plugins.

Plugins can be used to create any constructions that extend the built-in capabilities of the editor. The main idea of ​​plugins is that they process certain commands from the user or program.

Plugins can also contain their own styles, functions, icons, etc. The plugin has access to all the API provided by the core package. It is important not to forget to register your package.

Interaction can be organized by expanding existing nodes. For example, as in the picture. And you can also create your own nodes, as you saw in the example with exalidrau.

To sum up

The WYSIWYG approach allows you to edit text in such a way that the editor and the user see it the same.

There are different approaches to organizing the work of a wysiwyg editor. The most common approach for web applications is to build a state stored as a serializable javascript object, modified by events, and then convert it all to html.

Of the editors considered (DraftJs, Editorjs, ProseMirror, lexical), I chose Lexicalbecause:

  • The library is evolving, unlike DraftJs

  • It has no shortage of block editors and is familiar to content managers and management company operators

  • It has a large community, many implemented modules and support from a large company, unlike ProseMirror

  • These libraries differ slightly in size, but Lexical also wins in this regard.

P.S. LexicalJs can be considered in more detail, for example, creating your own plugins. If it is interesting, I will write another more detailed article.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *