[WIP] Feature: $config protocol + NodeState registration/flattening #7258
+1,180
−386
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Description
Based on the future needs of NodeState (#7117) - specifically allowing nodes to statically declare their required state - this RFC refactors LexicalNode and the associated internals to remove the need for any static methods, vastly reducing the boilerplate in order to subclass nodes. This is a squashed/rebased version of the original #7189 PR that explored this feature.
The short story is that this sort of thing will let us get better type-level information about nodes. We can't have
node.getType()
ever give a more specific type thanstring
but we could write a$getType(node)
that does infer to the exact type if we want. Same thing fornode.exportJSON()
and similar methods.It also facilitates scrapping all of the node subclass boilerplate, except for this one method, so long as the class has a compatible signature (trivial constructor)
Example of extension and inference capability from the tests
Closes #7260
How it works
The proposed protocol for declaring nodes would scale down to something like this:
The two method interface is useful for TypeScript reasons, basically. The outer
getStaticNodeConfig
(could come up with a shorter and/or more memorable name) is the API and thethis.configureNode(…)
is a helper method that has the right generics to make it easy to return something with the right shape and type. The types are very delicate, if you don't have all of the annotations just right then the type will simplify into something without information that can be extracted. Basically it is just returning an object like this, but in a way where TypeScript doesn't collapse the type into something simpler (kind of like usingas const
orsatisfies
in the right place):On the LexicalEditor side, during createEditor,
CustomTextNode.prototype.$config()
is called and can grab the type and any other useful metadata (e.g. a defined transform, registered state, anything else we want to put in there). For compatibility reasons it will also monkeypatchgetType
,importJSON
, andclone
static methods onto the node class if they are not already there. It adds a module global variable to inject cloned node keys so it doesn't need to break any compatibility with multi-argument constructors, it only requires that they have a 0-argument form.I've also put a
$create
method in there which reduces the boilerplate and increases the efficiency there, assuming that we'd be willing to deprecatewith
in the node overrides and go directly towithKlass
so that we don't have to construct any intermediate garbage when that's configured.I think this covers most of the breaking-ish changes I would want to make to the lowest level stuff, in exchange for better DX and no loss of performance (probably better, even if just code size improves).
Future direction
I think this is where we fix the rest of the #6998 things here with some combination of middleware-style functions or automatic super calls (like
$afterCloneFrom
) instead of methods so that we can control things better without variance violations.