Skip to content
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

fix: use Fragment VNodes for if-elseif-else children #3042

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions packages/@lwc/engine-core/src/framework/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
VNodeType,
VStatic,
Key,
VFragment,
} from './vnodes';

const SymbolIterator: typeof Symbol.iterator = Symbol.iterator;
Expand All @@ -63,6 +64,18 @@ function st(fragment: Element, key: Key): VStatic {
};
}

// [fr]agment node
function fr(key: Key, children: VNodes): VFragment {
return {
type: VNodeType.Fragment,
sel: undefined,
key,
elm: undefined,
children: [t('', key + 's'), ...children, t('', key + 'e')],
owner: getVMBeingRendered()!,
};
}

// [h]tml node
function h(sel: string, data: VElementData, children: VNodes = EmptyArray): VElement {
const vmBeingRendered = getVMBeingRendered()!;
Expand Down Expand Up @@ -346,8 +359,8 @@ function f(items: Readonly<Array<Readonly<Array<VNodes>> | VNodes>>): VNodes {
}

// [t]ext node
function t(text: string): VText {
let sel, key, elm;
function t(text: string, key?: Key): VText {
let sel, elm;
return {
type: VNodeType.Text,
sel,
Expand Down Expand Up @@ -541,6 +554,7 @@ const api = ObjectFreeze({
k,
co,
dc,
fr,
ti,
st,
gid,
Expand Down
12 changes: 12 additions & 0 deletions packages/@lwc/engine-core/src/framework/hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
VElement,
VCustomElement,
VStatic,
VFragment,
} from './vnodes';

import { patchProps } from './modules/props';
Expand Down Expand Up @@ -88,6 +89,11 @@ function hydrateNode(node: Node, vnode: VNode, renderer: RendererAPI): Node | nu
hydratedNode = hydrateStaticElement(node, vnode, renderer);
break;

case VNodeType.Fragment:
// @todo: what's vnode.data.renderer? do we need to update the fragment vnode?
hydratedNode = hydrateFragment(node, vnode, renderer);
break;

case VNodeType.Element:
hydratedNode = hydrateElement(node, vnode, vnode.data.renderer ?? renderer);
break;
Expand Down Expand Up @@ -154,6 +160,12 @@ function hydrateStaticElement(elm: Node, vnode: VStatic, renderer: RendererAPI):
return elm;
}

function hydrateFragment(elm: Node, vnode: VFragment, renderer: RendererAPI): Node | null {
hydrateChildren(elm, vnode.children, renderer.getProperty(elm, 'parentNode'), vnode.owner);

return vnode.children[vnode.children.length - 1]!.elm as Node;
}

function hydrateElement(elm: Node, vnode: VElement, renderer: RendererAPI): Node | null {
if (
!hasCorrectNodeType<Element>(vnode, elm, EnvNodeTypes.ELEMENT, renderer) ||
Expand Down
33 changes: 30 additions & 3 deletions packages/@lwc/engine-core/src/framework/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
isSameVnode,
VNodeType,
VStatic,
VFragment,
} from './vnodes';

import { patchAttributes } from './modules/attrs';
Expand Down Expand Up @@ -106,6 +107,10 @@ function patch(n1: VNode, n2: VNode, parent: ParentNode, renderer: RendererAPI)
n2.elm = n1.elm;
break;

case VNodeType.Fragment:
patchFragment(n1 as VFragment, n2, parent, renderer);
break;

case VNodeType.Element:
patchElement(n1 as VElement, n2, n2.data.renderer ?? renderer);
break;
Expand Down Expand Up @@ -133,6 +138,10 @@ export function mount(node: VNode, parent: ParentNode, renderer: RendererAPI, an
mountStatic(node, parent, anchor, renderer);
break;

case VNodeType.Fragment:
mountFragment(node, parent, anchor, renderer);
break;

case VNodeType.Element:
// If the vnode data has a renderer override use it, else fallback to owner's renderer
mountElement(node, parent, anchor, node.data.renderer ?? renderer);
Expand Down Expand Up @@ -188,6 +197,20 @@ function mountComment(
insertNode(commentNode, parent, anchor, renderer);
}

function mountFragment(
vnode: VFragment,
parent: ParentNode,
anchor: Node | null,
renderer: RendererAPI
) {
mountVNodes(vnode.children, parent, renderer, anchor);
}

function patchFragment(n1: VFragment, n2: VFragment, parent: ParentNode, renderer: RendererAPI) {
// @todo: atm, all fragments are dynamic, so the diffing must be the slow one.
updateDynamicChildren(n1.children, n2.children, parent, renderer);
}

function mountElement(
vnode: VElement,
parent: ParentNode,
Expand Down Expand Up @@ -370,9 +393,13 @@ function unmount(
// When unmounting a VNode subtree not all the elements have to removed from the DOM. The
// subtree root, is the only element worth unmounting from the subtree.
if (doRemove) {
// The vnode might or might not have a data.renderer associated to it
// but the removal used here is from the owner instead.
removeNode(elm!, parent, renderer);
if (type === VNodeType.Fragment) {
unmountVNodes(vnode.children, parent, renderer, doRemove);
} else {
// The vnode might or might not have a data.renderer associated to it
// but the removal used here is from the owner instead.
removeNode(elm!, parent, renderer);
}
}

switch (type) {
Expand Down
19 changes: 14 additions & 5 deletions packages/@lwc/engine-core/src/framework/vnodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ export const enum VNodeType {
Element,
CustomElement,
Static,
Fragment,
}

export type VNode = VText | VComment | VElement | VCustomElement | VStatic;
export type VParentElement = VElement | VCustomElement;
export type VNode = VText | VComment | VElement | VCustomElement | VStatic | VFragment;
export type VParentElement = VElement | VCustomElement | VFragment;
export type VNodes = Readonly<Array<VNode | null>>;

export interface BaseVParent {
children: VNodes;
}

export interface BaseVNode {
type: VNodeType;
elm: Node | undefined;
Expand All @@ -37,11 +42,16 @@ export interface VStatic extends BaseVNode {
fragment: Element;
}

export interface VFragment extends BaseVNode, BaseVParent {
sel: undefined;
type: VNodeType.Fragment;
}

export interface VText extends BaseVNode {
type: VNodeType.Text;
sel: undefined;
text: string;
key: undefined;
key: Key | undefined;
}

export interface VComment extends BaseVNode {
Expand All @@ -51,10 +61,9 @@ export interface VComment extends BaseVNode {
key: undefined;
}

export interface VBaseElement extends BaseVNode {
export interface VBaseElement extends BaseVNode, BaseVParent {
sel: string;
data: VElementData;
children: VNodes;
elm: Element | undefined;
key: Key;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<x-comments-foreach>
<template shadowroot="open">
<ul>
<!-- color -->
<li>
red
Expand All @@ -13,6 +14,7 @@
<li>
yellow
</li>
</ul>
</template>
</x-comments-foreach>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<x-for-each-block>
<template shadowroot="open">
<ul>
<li>
0 - paris
</li>
Expand All @@ -10,6 +11,7 @@
<li>
2 - tokyo
</li>
</ul>
</template>
</x-for-each-block>
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<x-for-each-child-nested>
<template shadowroot="open">
<ol>
<li>
europe
<x-child>
<template shadowroot="open">
<li>
0 - paris
</li>
Expand All @@ -14,13 +16,15 @@
<li>
2 - rome
</li>
</template>
</x-child>
</li>
<li>
asia
<x-child>
<template shadowroot="open">
<li>
0 - tokyo
</li>
Expand All @@ -30,9 +34,11 @@
<li>
2 - singapore
</li>
</template>
</x-child>
</li>
</ol>
</template>
</x-for-each-child-nested>
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<x-for-each-nested>
<template shadowroot="open">
<ol>
<li>
europe
<ul>
<li>
paris
</li>
Expand All @@ -13,11 +15,13 @@
<li>
rome
</li>
</ul>
</li>
<li>
asia
<ul>
<li>
tokyo
</li>
Expand All @@ -27,8 +31,10 @@
<li>
singapore
</li>
</ul>
</li>
</ol>
</template>
</x-for-each-nested>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<x-getter-is-connected>
<template shadowroot="open">
<ul>
<li>
constructor: false
</li>
Expand All @@ -10,6 +11,7 @@
<li>
render: true
</li>
</ul>
</template>
</x-getter-is-connected>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<x-iterator-block>
<template shadowroot="open">
<ul>
<li>
<div class="list-first">
</div>
Expand All @@ -14,6 +15,7 @@
<div class="list-last">
</div>
</li>
</ul>
</template>
</x-iterator-block>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<x-lifecycle-hooks>
<template shadowroot="open">
<ul>
<li>
constructor
</li>
Expand All @@ -10,6 +11,7 @@
<li>
render
</li>
</ul>
</template>
</x-lifecycle-hooks>
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ export default {

expect(hydratedSnapshot.text).not.toBe(snapshots.text);

expect(consoleCalls.error).toHaveSize(2);
expect(consoleCalls.error[0][0].message).toContain(
expect(consoleCalls.error).toHaveSize(3);
// extra li does not matches with the fragmentEnd text node delimiter
expect(consoleCalls.error[0][0].message).toContain('incorrect node type received');
expect(consoleCalls.error[1][0].message).toContain(
'Server rendered more nodes than the client.'
);
expect(consoleCalls.error[1][0].message).toContain('Hydration completed with errors.');
expect(consoleCalls.error[2][0].message).toContain('Hydration completed with errors.');
},
};
Loading