-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Breaking Change][lexical] Feature: Add updateFromJSON and move more textFormat/textStyle to ElementNode #6970
[Breaking Change][lexical] Feature: Add updateFromJSON and move more textFormat/textStyle to ElementNode #6970
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
size-limit report 📦
|
Thanks Bob, the I like the solution you've presented and how it works with types! I'm not so sure about the moving On a separate note, and beyond this PR, I'm somewhat concerned about the growing number of methods in Nodes. While it's easy to extend and for the most part they're self-explanatory it takes a fair amount of time to configure a new Node and understand what the relevant methods are. I'd be keen take this offline and discuss potential alternatives, including higher-level abstractions and codegen. |
The textFormat and textStyle is only relevant to an element because we don't have another reliable way to preserve it when there isn't a TextNode present. Previously we were doing this only for ParagraphNode, but it probably makes sense for any element that can contain a TextNode (such as ListItemNode in this particular use case). It's not used directly on the element for any purpose, it's really just storage for the selection. Possibly a future direction might be to phase it out and insert some kind of format preservation node (a special empty TextNode that doesn't get normalized away or maybe even rendered, for example). With regard to methods, the only real reason we need to add methods here is backwards compatibility. If we didn't have that constraint we could get rid of I think the next step here is to allow classes to drop // before
const constructor = latestNode.constructor;
const mutableNode = constructor.clone(latestNode) as T;
mutableNode.afterCloneFrom(latestNode); // after
const constructor = latestNode.constructor;
let mutableNode: T;
if (Object.hasOwn(constructor, 'clone')) {
// backwards compatibility
mutableNode = constructor.clone() as T;
} else {
$setNextNodeKey(latestNode.getKey());
// this does require all constructors to have a 0-arg form and a correct afterCloneFrom
mutableNode = new constructor() as T;
// if __DEV__ make sure the key was consumed by $setNodeKey(this, key);
}
mutableNode.afterCloneFrom(latestNode); Instead of checking that these static methods exist we can instead have a lint that looks for extra properties on a node and if they exist then expect an |
The exportJSON refactor is implemented in #6983 |
…t-node-text-format-style
Description
One of the biggest pain points and error-prone parts of extending lexical is the amount of boilerplate involved in subclassing nodes. In particular the worst of it is the
static importJSON
method that must be implemented. We can eventually make the boilerplate go away with some targeted refactoring.The majority of this PR implements
updateFromJSON
which is theimportJSON
analog to theafterCloneFrom
forclone
from #6505. Basically the key concept here is to move all of the logic, other than the constructor itself (with as many defaults as possible), into an instance method, so that super can be called to inherit all of the base class logic.More documentation has been added related to serialization best practices, including a recommendation not to use the
version
property as it does not compose well and is generally just ignored and/or redundant.Before:
After:
The motivation for this was to move textFormat and textStyle to ElementNode, which to do properly would require also updating serialization everywhere, so it made sense to eliminate the boilerplate at the same time.
Note that the types here are not quite sound and are probably not possible to type in Flow, for the same class of reason that
updateDOM
andafterCloneFrom
are also not sound. When a subclass implements this method with a more specific type, it's no longer sound to upcast it to the base class and call the method with the schema intended for the base class. I'm sure it would be possible to come up with a scheme that is sound, but I think it would require a large DX trade-off one way or another (such as using a new method name for each class likeupdateFromTextNodeJSON
, moving it to a static method or exported function that must be called fully qualified, or forcing theupdateFromJSON
implementations to parse the JSON from something likeRecord<unknown, unknown>
- which would be much safer but also very tedious with a runtime penalty). See #6998 for more information about this and some ideas for runtime checks.Closes #6949
Closes #6476
Closes #5708 (although it does not implement a perfect solution to persist styles at arbitrary positions, it's better than status quo)
Might get closer to fixing #6583
Related: #3931
Upgrade Notes
This change adds optional
textFormat
andtextStyle
properties toSerializedElementNode
. If you have existing classes with those properties it could create a namespace clash that you will have to resolve one way or another.TextNode and ElementNode subclasses should be updated to call the
updateFromJSON(serializedNode)
method from theirstatic importJSON
methods. If they don't, they won't support this new functionality, and will have to continue copy and pasting the super implementation of importJSON for correct behavior if the base class ever changes in the future.You should consider dropping usage of the
version
field.Test plan
Before
Styles were not preserved when creating a new ListItem
After
Styles are preserved when creating a new ListItem (including e2e test)