This work is ongoing and not ready yet.
There are lots of changes in this branch. But here's an overview of the big differences from an architectural point of view.
More coming, still in progress…
The data model is now comprised of simple JSON objects. Previously, it used Immutable.js data structures. This is a huge change, and one that unlocks many other things. Hopefully it will also increase the average performance when using Slate. It also makes it much easier to get started for newcomers. This will be a large change to migrate from, but it will be worth it.
Interfaces & Namespaces
The data model is interface-based. Previously each model was an instance of a class. Now, not only is the data plain objects, but Slate only expects that the objects implement an interface. So custom properties that used to live in
node.data can now live at the top-level of the nodes. Helpers are exposed as a collection of helper functions on a namespace. For example,
Node.get(root, path) or
Range.isCollapsed(range). This ends up making code much clearer because you can always quickly see what interface you're working with.
The codebase now uses TypeScript. Working with pure JSON as a data model, and using an interface-based API are two things that have been made easier by migrating to TypeScript. You don't need to use it yourself, but if you do you'll get a lot more security when using the APIs. (And if you use VS Code you'll get nice autocompletion regardless!)
The number of interfaces and commands has been reduced. Previously
Decoration used to all be separate classes. Now they are simply objects that implement the
Range interface. Previously
Inline were separate, now they are objects that implement the
Element interface. Previously there was a
Value, but now the top-level
Editor contains the children nodes of the document itself.
The number of commands has been reduced too. Previously we had commands for every type of input, like
insertTextAtPath. These have been merged into a smaller set of more customizable commands, eg.
insertText which can take
at: Path | Range | Point.
In attempt to decrease the maintenance burden, and because the new abstraction and APIs in Slate's core packages make things much easier, the total number of packages has been reduced. Things like
slate-base64-serializer, etc. have been removed and can be implemented in userland easily if needed. Even the
slate-html-deserializer can now be implemented in userland (in ~10 LOC leveraging
slate-hyperscript). And internal packages like
slate-dev-test-utils, etc. are no longer exposed because they are implementation details.
Plugins are now plain functions that augment the
Editor object they receive and return it again. For example can augment the command execution by composing the
editor.exec function. Or listen to operations by composing
editor.apply. Previously they relied on a custom middleware stack, and they were just bags of handlers that got merged onto an editor. Now we're using plain old function composition (aka wrapping) instead.
Block-ness and inline-ness is now a runtime choice. Previously it was baked into the data model with the
object: 'block' or
object: 'inline' attributes. Now, it checks whether an "element" is inline or not at runtime. For example, you might check to see that
element.type === 'link' and treat it as inline.
We now use the
beforeinput event almost exclusively. Instead of having relying on a series of shims and the quirks of React synthetic events, we're now using the standardized
beforeinput event as our baseline. It is fully supported in Safari and Chrome, will soon be supported in the new Chromium-based Edge, and is currently being worked on in Firefox. In the meantime there are a few patches to make Firefox work. Internet Explorer is no longer supported in core out of the box.
Rendering and event-handling is no longer a plugin's concern. Previously plugins had full control over the rendering logic, and event-handling logic in the editor. This creates a bad incentive to start putting all rendering logic in plugins, which puts Slate in a position of being a wrapper around all of React, which is very hard to do well. Instead, the new architecture has plugins focused purely on the rich-text aspects, and leaves the rendering and event handling aspects to React.
<Editor> component was doing double duty as a sort of "controller" object and also the
contenteditable DOM element. This led to a lot of awkwardness in how other components worked with Slate. In the new version, there is a new
<Slate> context provider and a simpler
contenteditable-like component. By putting the
<Slate> provider higher up in your component tree, you can share the editor directly with toolbars, buttons, etc. using the
In addition to the
useSlate hook, there are a handful of other hooks. For example the
useFocused hooks help with knowing when to render selected states (often for void nodes). And since the use React's Content API they will automatically re-render when their state changes.
One of the goals was to dramatically simplify a lot of the logic in Slate to make it easier to maintain and iterate on. This was done by refactoring to better base abstractions that can be built on, by leveraging modern DOM APIs, and by migrating to simpler React patterns.
To give you a sense for the change in total lines of code:
slate 8,436 -> 4,038 (48%)
slate-react 3,905 -> 715 (18%)
slate-base64-serializer 38 -> 0
slate-dev-benchmark 340 -> 0
slate-dev-environment 102 -> 0
slate-dev-test-utils 44 -> 0
slate-history 0 -> 201
slate-hotkeys 62 -> 0
slate-html-serializer 253 -> 0
slate-hyperscript 447 -> 410
slate-plain-serializer 56 -> 0
slate-prop-types 62 -> 0
slate-react-placeholder 62 -> 0
slate-schema 0 -> 504
total 13,807 -> 5,868 (43%)
It's quite a big difference, although it's not done so the final sizes will likely grow a bit before it's ready. But that doesn't even include the dependencies that were shed in the process too.
This is an estimate of which issues are fixed by this pull request. There are a lot of them, because it changes a lot of things. There might be a few incorrectly "fixed" ones here, so if one of them is your issue and you don't think it's fixed feel free to reopen a new issue.
In addition to fixing a lot of things, the significant changes to the architecture mean that a lot of open issues are no longer relevant because they are talking about concepts that no longer exist.
improvement debt ✶ breaking