Skip to content

Commit

Permalink
refactor: avoid calling traverseAndSetElement on every rerender (#3998)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmsjtu authored Feb 15, 2024
1 parent 090aab8 commit cd7ed65
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 18 deletions.
4 changes: 2 additions & 2 deletions packages/@lwc/engine-core/src/framework/hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import {

import { patchProps } from './modules/props';
import { applyEventListeners } from './modules/events';
import { applyStaticParts } from './modules/static-parts';
import { mountStaticParts } from './modules/static-parts';
import { getScopeTokenClass, getStylesheetTokenHost } from './stylesheet';
import { renderComponent } from './component';
import { applyRefs } from './modules/refs';
Expand Down Expand Up @@ -228,7 +228,7 @@ function hydrateStaticElement(elm: Node, vnode: VStatic, renderer: RendererAPI):

vnode.elm = elm;

applyStaticParts(elm, vnode, renderer, true);
mountStaticParts(elm, vnode, renderer);

return elm;
}
Expand Down
52 changes: 40 additions & 12 deletions packages/@lwc/engine-core/src/framework/modules/static-parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,7 @@ function traverseAndSetElements(root: Element, parts: VStaticPart[], renderer: R
* @param renderer - the renderer to use
* @param mount - true this is a first (mount) render as opposed to a subsequent (patch) render
*/
export function applyStaticParts(
root: Element,
vnode: VStatic,
renderer: RendererAPI,
mount: boolean
): void {
export function mountStaticParts(root: Element, vnode: VStatic, renderer: RendererAPI): void {
// On the server, we don't support ref (because it relies on renderedCallback), nor do we
// support event listeners (no interactivity), so traversing parts makes no sense
if (!process.env.IS_BROWSER) {
Expand All @@ -90,18 +85,51 @@ export function applyStaticParts(
return;
}

// This adds `part.elm` to each `part`. We have to do this on every mount/patch because the `parts`
// This adds `part.elm` to each `part`. We have to do this on every mount because the `parts`
// array is recreated from scratch every time, so each `part.elm` is now undefined.
// TODO [#3800]: avoid calling traverseAndSetElements on every re-render
traverseAndSetElements(root, parts, renderer);

// Currently only event listeners and refs are supported for static vnodes
for (const part of parts) {
if (mount) {
// Event listeners only need to be applied once when mounting
applyEventListeners(part, renderer);
}
// Event listeners only need to be applied once when mounting
applyEventListeners(part, renderer);
// Refs must be updated after every render due to refVNodes getting reset before every render
applyRefs(part, owner);
}
}

/**
* Mounts elements to the newly generated VStatic node
*
* @param n1 - the previous VStatic vnode
* @param n2 - the current VStatic vnode
*/
export function patchStaticParts(n1: VStatic, n2: VStatic) {
// On the server, we don't support ref (because it relies on renderedCallback), nor do we
// support event listeners (no interactivity), so traversing parts makes no sense
if (!process.env.IS_BROWSER) {
return;
}

const { parts: currParts, owner: currPartsOwner } = n2;
if (isUndefined(currParts)) {
return;
}

const { parts: prevParts } = n1;
if (process.env.NODE_ENV !== 'production') {
assert.isTrue(
currParts.length === prevParts?.length,
'Expected static parts to be the same for the same element. This is an error with the LWC framework itself.'
);
}

for (let i = 0; i < currParts.length; i++) {
const part = currParts[i];
// Patch only occurs if the vnode is newly generated, which means the part.elm is always undefined
// Since the vnode and elements are the same we can safely assume that prevParts[i].elm is defined.
part.elm = prevParts![i].elm;
// Refs must be updated after every render due to refVNodes getting reset before every render
applyRefs(part, currPartsOwner);
}
}
8 changes: 4 additions & 4 deletions packages/@lwc/engine-core/src/framework/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ import { applyEventListeners } from './modules/events';
import { applyStaticClassAttribute } from './modules/static-class-attr';
import { applyStaticStyleAttribute } from './modules/static-style-attr';
import { applyRefs } from './modules/refs';
import { applyStaticParts } from './modules/static-parts';
import { mountStaticParts, patchStaticParts } from './modules/static-parts';
import { LightningElementConstructor } from './base-lightning-element';

export function patchChildren(
Expand Down Expand Up @@ -265,12 +265,12 @@ function mountElement(
}

function patchStatic(n1: VStatic, n2: VStatic, renderer: RendererAPI) {
const elm = (n2.elm = n1.elm!);
n2.elm = n1.elm!;

// slotAssignments can only apply to the top level element, never to a static part.
patchSlotAssignment(n1, n2, renderer);
// The `refs` object is blown away in every re-render, so we always need to re-apply them
applyStaticParts(elm, n2, renderer, false);
patchStaticParts(n1, n2);
}

function patchElement(n1: VElement, n2: VElement, renderer: RendererAPI) {
Expand Down Expand Up @@ -305,7 +305,7 @@ function mountStatic(
// slotAssignments can only apply to the top level element, never to a static part.
patchSlotAssignment(null, vnode, renderer);
insertNode(elm, parent, anchor, renderer);
applyStaticParts(elm, vnode, renderer, true);
mountStaticParts(elm, vnode, renderer);
}

function mountCustomElement(
Expand Down

0 comments on commit cd7ed65

Please sign in to comment.