-
Notifications
You must be signed in to change notification settings - Fork 418
fix(engine): add keys to text vnodes #1943
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
Conversation
@@ -63,7 +63,7 @@ export interface VText extends VNode { | |||
children: undefined; | |||
elm: Node | undefined; | |||
text: string; | |||
key: undefined; | |||
key: Key | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
key: Key | undefined; | |
key: Key; |
Also, can we now now update VNode interface to key: Key;
and not override it in VElement and VText
@@ -77,8 +77,8 @@ export interface RenderAPI { | |||
): VNode; | |||
i(items: any[], factory: () => VNode | VNode): VNodes; | |||
f(items: any[]): any[]; | |||
t(text: string): VText; | |||
d(value: any): VNode | null; | |||
t(text: string, data: ElementCompilerData): VText; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be called NodeCompilerData? It would be non-intuitive to generate ElementCompilerData for a text node.
Although, ElementCompilerData and its parent VNodeData seems bloated for a text node.
t(text: string, data: ElementCompilerData): VText; | |
t(text: string, data: NodeCompilerData): VText; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in theory it could be just "key", i made it an object so it's compatible with the existing API (and if extended in the future the api does not need to change).
imo, adding key
to VNodeData
makes sense. what do you think?
const data = EmptyObject; | ||
let sel, children, key, elm; | ||
export function t(text: string, data: CustomElementCompilerData): VText { | ||
const { key } = data; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the data
a newly created param from the compiler OR is it pre-existing param that was not used? Will this break any existing pre-compiled component definitions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mm, good point, data
is a new param from the compiler. i left the VText to be Key | undefined, trying to support the existing compilations, but i missed this case.
Though, we should probably dismiss existing pre-compiled components. thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, all the precompiled components have to be dismissed.
export function t(text: string): VText { | ||
const data = EmptyObject; | ||
let sel, children, key, elm; | ||
export function t(text: string, data: CustomElementCompilerData): VText { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export function t(text: string, data: CustomElementCompilerData): VText { | |
export function t(text: string, data: ElementCompilerData): VText { |
@@ -519,11 +519,11 @@ export function t(text: string): VText { | |||
} | |||
|
|||
// [d]ynamic value to produce a text vnode | |||
export function d(value: any): VNode | null { | |||
export function d(value: any, data: CustomElementCompilerData): VNode | null { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export function d(value: any, data: CustomElementCompilerData): VNode | null { | |
export function d(value: any, data: ElementCompilerData): VNode | null { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious whether there would be a performance benefit to use the actual string of the text node as the key?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should avoid creating short life objects when possible especially on the critical path.
@@ -77,8 +77,8 @@ export interface RenderAPI { | |||
): VNode; | |||
i(items: any[], factory: () => VNode | VNode): VNodes; | |||
f(items: any[]): any[]; | |||
t(text: string): VText; | |||
d(value: any): VNode | null; | |||
t(text: string, data: ElementCompilerData): VText; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of creating a new object here, I would recommend passing directly the key
a parameter to avoid creating a short life object.
t(text: string, data: ElementCompilerData): VText; | |
t(text: string, key: number): VText; |
t(text: string): VText; | ||
d(value: any): VNode | null; | ||
t(text: string, data: ElementCompilerData): VText; | ||
d(value: any, data: ElementCompilerData): VNode | null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto. Also in this case, the returned value is a VText
not a Vnode
.
d(value: any, data: ElementCompilerData): VNode | null; | |
d(value: any, key: number): VText | null; |
const data = EmptyObject; | ||
let sel, children, key, elm; | ||
export function t(text: string, data: CustomElementCompilerData): VText { | ||
const { key } = data; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, all the precompiled components have to be dismissed.
expect(text).toContain('false'); | ||
|
||
elm.toggleVisibility(); | ||
return Promise.resolve(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed, the .then
is invoked in the microtask after the render.
return Promise.resolve(); |
return Promise.resolve() | ||
.then(() => { | ||
const text = elm.shadowRoot.textContent; | ||
expect(text).toContain('false'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should match against the entire text content here to make sure that all the text is in the right place, not only the partial content.
}) | ||
.then(() => { | ||
const text = elm.shadowRoot.textContent; | ||
expect(text).toContain('second text'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
it('should not confuse text vnodes when moving elements', function () { | ||
const elm = createElement('x-container', { is: Container }); | ||
document.body.appendChild(elm); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also assert the text content here before the first toogle.
@jodarove Also before moving forward, Best caught a ~8% regression on 2 benchmarks. We should investigate this moving forward with this PR. |
e6c6aaa
to
623b42f
Compare
623b42f
to
43611c1
Compare
mmm, true about the performance, ill need to take a look. Alternatively, i opened #1946 which does not have the performance, and also fixes the issue. cc/ @pmdartus @ravijayaramappa @ekashida |
closed in favor of #1946 |
Details
When vnodes are allocated in slots, the process rewrites the vnode key, in the case of text vnodes (they don't have key), it will make 2 different text vnodes in the same slot, to have the same key.
Later on in this section diffing algo, createKeyToOldIdx will map incorrectly those 2 nodes, causing the algo to patch the incorrect vnode, which eventually triggers an error.
This PR fixes the issue by generating a key for text nodes. It also fixes #1637, improving
createKeyToOldIdx
since now all vnodes have key.Note: For the review, the functionality is in the first commit (de8d086). The rest of the pr is updating the test snapshots.
Does this PR introduce breaking changes?
No, it does not introduce breaking changes.
GUS work item
W-7292159