Skip to content

Conversation

jodarove
Copy link
Contributor

@jodarove jodarove commented Jun 23, 2020

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

@@ -63,7 +63,7 @@ export interface VText extends VNode {
children: undefined;
elm: Node | undefined;
text: string;
key: undefined;
key: Key | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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;
Copy link
Contributor

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.

Suggested change
t(text: string, data: ElementCompilerData): VText;
t(text: string, data: NodeCompilerData): VText;

Copy link
Contributor Author

@jodarove jodarove Jun 23, 2020

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;
Copy link
Contributor

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?

Copy link
Contributor Author

@jodarove jodarove Jun 23, 2020

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?

Copy link
Member

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export function d(value: any, data: CustomElementCompilerData): VNode | null {
export function d(value: any, data: ElementCompilerData): VNode | null {

Copy link
Member

@ekashida ekashida left a 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?

Copy link
Member

@pmdartus pmdartus left a 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;
Copy link
Member

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.

Suggested change
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;
Copy link
Member

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.

Suggested change
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;
Copy link
Member

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();
Copy link
Member

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.

Suggested change
return Promise.resolve();

return Promise.resolve()
.then(() => {
const text = elm.shadowRoot.textContent;
expect(text).toContain('false');
Copy link
Member

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');
Copy link
Member

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);

Copy link
Member

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.

@pmdartus
Copy link
Member

@jodarove Also before moving forward, Best caught a ~8% regression on 2 benchmarks. We should investigate this moving forward with this PR.

@jodarove jodarove force-pushed the jodarove/fix-diffing-algo-with-text branch 2 times, most recently from e6c6aaa to 623b42f Compare June 24, 2020 18:59
@jodarove
Copy link
Contributor Author

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

@jodarove
Copy link
Contributor Author

closed in favor of #1946

@jodarove jodarove closed this Jun 25, 2020
@jodarove jodarove deleted the jodarove/fix-diffing-algo-with-text branch June 25, 2020 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Optimize createKeyToOldIdx
4 participants