From 345c68d7ab3ace4e7bc161bb05540c7216e4d926 Mon Sep 17 00:00:00 2001 From: James Tu Date: Tue, 11 Jun 2024 11:09:34 -0700 Subject: [PATCH] fix: skip hydration validation when static parts have static classes (#4278) --------- Co-authored-by: Nolan Lawson --- .../engine-core/src/framework/hydration.ts | 12 +++++++++-- .../index.spec.js | 20 +++++++++++++++++++ .../x/main/main.html | 4 ++++ .../x/main/main.js | 5 +++++ .../class-attr/static-same/index.spec.js | 15 ++++++++++++++ .../class-attr/static-same/x/main/main.html | 3 +++ .../class-attr/static-same/x/main/main.js | 3 +++ .../same-with-static-parts/index.spec.js | 20 +++++++++++++++++++ .../same-with-static-parts/x/main/main.html | 4 ++++ .../same-with-static-parts/x/main/main.js | 5 +++++ 10 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same-with-static-parts/index.spec.js create mode 100644 packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same-with-static-parts/x/main/main.html create mode 100644 packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same-with-static-parts/x/main/main.js create mode 100644 packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same/index.spec.js create mode 100644 packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same/x/main/main.html create mode 100644 packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same/x/main/main.js create mode 100644 packages/@lwc/integration-karma/test-hydration/mismatches/style-attr/static/same-with-static-parts/index.spec.js create mode 100644 packages/@lwc/integration-karma/test-hydration/mismatches/style-attr/static/same-with-static-parts/x/main/main.html create mode 100644 packages/@lwc/integration-karma/test-hydration/mismatches/style-attr/static/same-with-static-parts/x/main/main.js diff --git a/packages/@lwc/engine-core/src/framework/hydration.ts b/packages/@lwc/engine-core/src/framework/hydration.ts index f0cdc45954..1cc7b0317c 100644 --- a/packages/@lwc/engine-core/src/framework/hydration.ts +++ b/packages/@lwc/engine-core/src/framework/hydration.ts @@ -815,6 +815,7 @@ function haveCompatibleStaticParts(vnode: VStatic, renderer: RendererAPI) { return true; } + const shouldValidateAttr = (data: VStaticPartData, attrName: string) => attrName in data; // The validation here relies on 2 key invariants: // 1. It's never the case that `parts` is undefined on the server but defined on the client (or vice-versa) // 2. It's never the case that `parts` has one length on the server but another on the client @@ -826,8 +827,15 @@ function haveCompatibleStaticParts(vnode: VStatic, renderer: RendererAPI) { } const { data } = part; const hasMatchingAttrs = validateAttrs(vnode, elm, data, renderer, () => true); - const hasMatchingStyleAttr = validateStyleAttr(vnode, elm, data, renderer); - const hasMatchingClass = validateClassAttr(vnode, elm, data, renderer); + // Explicitly skip hydration validation when static parts don't contain `style` or `className`. + // This means the style/class attributes are either static or don't exist on the element and + // cannot be affected by hydration. + const hasMatchingStyleAttr = shouldValidateAttr(data, 'style') + ? validateStyleAttr(vnode, elm, data, renderer) + : true; + const hasMatchingClass = shouldValidateAttr(data, 'className') + ? validateClassAttr(vnode, elm, data, renderer) + : true; if (isFalse(hasMatchingAttrs && hasMatchingStyleAttr && hasMatchingClass)) { return false; } diff --git a/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same-with-static-parts/index.spec.js b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same-with-static-parts/index.spec.js new file mode 100644 index 0000000000..4ce3cf9fa4 --- /dev/null +++ b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same-with-static-parts/index.spec.js @@ -0,0 +1,20 @@ +export default { + props: { + s1: 's1', + }, + snapshot(target) { + const p = target.shadowRoot.querySelector('p'); + return { + p, + classes: p.className, + }; + }, + test(target, snapshots, consoleCalls) { + const p = target.shadowRoot.querySelector('p'); + + expect(p).toBe(snapshots.p); + expect(p.className).toBe(snapshots.classes); + // static classes are skipped by hydration validation + expect(consoleCalls.error).toHaveSize(0); + }, +}; diff --git a/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same-with-static-parts/x/main/main.html b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same-with-static-parts/x/main/main.html new file mode 100644 index 0000000000..c721460f6d --- /dev/null +++ b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same-with-static-parts/x/main/main.html @@ -0,0 +1,4 @@ + diff --git a/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same-with-static-parts/x/main/main.js b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same-with-static-parts/x/main/main.js new file mode 100644 index 0000000000..c72a9e82b5 --- /dev/null +++ b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same-with-static-parts/x/main/main.js @@ -0,0 +1,5 @@ +import { LightningElement, api } from 'lwc'; + +export default class Main extends LightningElement { + @api s1; +} diff --git a/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same/index.spec.js b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same/index.spec.js new file mode 100644 index 0000000000..a69d00963d --- /dev/null +++ b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same/index.spec.js @@ -0,0 +1,15 @@ +export default { + snapshot(target) { + const p = target.shadowRoot.querySelector('p'); + return { + p, + classes: p.className, + }; + }, + test(target, snapshots) { + const p = target.shadowRoot.querySelector('p'); + + expect(p).toBe(snapshots.p); + expect(p.className).toBe(snapshots.classes); + }, +}; diff --git a/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same/x/main/main.html b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same/x/main/main.html new file mode 100644 index 0000000000..137d2be8e4 --- /dev/null +++ b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same/x/main/main.html @@ -0,0 +1,3 @@ + diff --git a/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same/x/main/main.js b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same/x/main/main.js new file mode 100644 index 0000000000..80164ac007 --- /dev/null +++ b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/static-same/x/main/main.js @@ -0,0 +1,3 @@ +import { LightningElement } from 'lwc'; + +export default class Main extends LightningElement {} diff --git a/packages/@lwc/integration-karma/test-hydration/mismatches/style-attr/static/same-with-static-parts/index.spec.js b/packages/@lwc/integration-karma/test-hydration/mismatches/style-attr/static/same-with-static-parts/index.spec.js new file mode 100644 index 0000000000..fc0d9aa2dd --- /dev/null +++ b/packages/@lwc/integration-karma/test-hydration/mismatches/style-attr/static/same-with-static-parts/index.spec.js @@ -0,0 +1,20 @@ +export default { + props: { + c1: 'c1', + }, + snapshot(target) { + const p = target.shadowRoot.querySelector('p'); + return { + p, + style: p.getAttribute('style'), + }; + }, + test(target, snapshots, consoleCalls) { + const p = target.shadowRoot.querySelector('p'); + + expect(p).toBe(snapshots.p); + expect(p.getAttribute('style')).toBe(snapshots.style); + // static classes are skipped by hydration validation + expect(consoleCalls.error).toHaveSize(0); + }, +}; diff --git a/packages/@lwc/integration-karma/test-hydration/mismatches/style-attr/static/same-with-static-parts/x/main/main.html b/packages/@lwc/integration-karma/test-hydration/mismatches/style-attr/static/same-with-static-parts/x/main/main.html new file mode 100644 index 0000000000..13ba37298d --- /dev/null +++ b/packages/@lwc/integration-karma/test-hydration/mismatches/style-attr/static/same-with-static-parts/x/main/main.html @@ -0,0 +1,4 @@ + diff --git a/packages/@lwc/integration-karma/test-hydration/mismatches/style-attr/static/same-with-static-parts/x/main/main.js b/packages/@lwc/integration-karma/test-hydration/mismatches/style-attr/static/same-with-static-parts/x/main/main.js new file mode 100644 index 0000000000..8f11610826 --- /dev/null +++ b/packages/@lwc/integration-karma/test-hydration/mismatches/style-attr/static/same-with-static-parts/x/main/main.js @@ -0,0 +1,5 @@ +import { LightningElement, api } from 'lwc'; + +export default class Main extends LightningElement { + @api c1; +}