From 1e002145678da99e4ae31494a32091451505883f Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Tue, 22 Oct 2024 18:34:30 -0300 Subject: [PATCH 1/3] fix: dynamic class with empty string results in hydration mismatch --- .../@lwc/engine-core/src/framework/hydration.ts | 2 +- .../class-attr/empty-string/index.spec.js | 17 +++++++++++++++++ .../class-attr/empty-string/x/main/main.html | 3 +++ .../class-attr/empty-string/x/main/main.js | 4 ++++ 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/empty-string/index.spec.js create mode 100644 packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/empty-string/x/main/main.html create mode 100644 packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/empty-string/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 868c9b80d8..65666e0dbe 100644 --- a/packages/@lwc/engine-core/src/framework/hydration.ts +++ b/packages/@lwc/engine-core/src/framework/hydration.ts @@ -664,7 +664,7 @@ function validateClassAttr( const elmClassName = getAttribute(elm, 'class'); - if (!isUndefined(className) && String(className) !== elmClassName) { + if (!isUndefined(className) && String(className) !== elmClassName && className !== '') { // className is used when class is bound to an expr. nodesAreCompatible = false; // stringify for pretty-printing diff --git a/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/empty-string/index.spec.js b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/empty-string/index.spec.js new file mode 100644 index 0000000000..db4aec58ec --- /dev/null +++ b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/empty-string/index.spec.js @@ -0,0 +1,17 @@ +export default { + props: { + classes: '', + }, + 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/empty-string/x/main/main.html b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/empty-string/x/main/main.html new file mode 100644 index 0000000000..213b575922 --- /dev/null +++ b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/empty-string/x/main/main.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/empty-string/x/main/main.js b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/empty-string/x/main/main.js new file mode 100644 index 0000000000..837fcfe574 --- /dev/null +++ b/packages/@lwc/integration-karma/test-hydration/mismatches/class-attr/empty-string/x/main/main.js @@ -0,0 +1,4 @@ +import { LightningElement, api } from 'lwc'; +export default class Main extends LightningElement { + @api classes; +} From 6a7b378f8bb599c030ecf8e7d1b353e4cebd307c Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Fri, 25 Oct 2024 14:45:05 -0300 Subject: [PATCH 2/3] fix: also check if client-side className is null --- packages/@lwc/engine-core/src/framework/hydration.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/@lwc/engine-core/src/framework/hydration.ts b/packages/@lwc/engine-core/src/framework/hydration.ts index 65666e0dbe..d9721e27d9 100644 --- a/packages/@lwc/engine-core/src/framework/hydration.ts +++ b/packages/@lwc/engine-core/src/framework/hydration.ts @@ -664,7 +664,12 @@ function validateClassAttr( const elmClassName = getAttribute(elm, 'class'); - if (!isUndefined(className) && String(className) !== elmClassName && className !== '') { + if ( + !isUndefined(className) && + String(className) !== elmClassName && + // No mismatch if SSR className is empty and the client-side class is null + !(className === '' && isNull(elmClassName)) + ) { // className is used when class is bound to an expr. nodesAreCompatible = false; // stringify for pretty-printing From ed491c9600d41dc90f8c9ca8afdd9d7459b0c594 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Fri, 25 Oct 2024 13:00:29 -0700 Subject: [PATCH 3/3] Update packages/@lwc/engine-core/src/framework/hydration.ts --- packages/@lwc/engine-core/src/framework/hydration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@lwc/engine-core/src/framework/hydration.ts b/packages/@lwc/engine-core/src/framework/hydration.ts index d9721e27d9..f55ae10f3d 100644 --- a/packages/@lwc/engine-core/src/framework/hydration.ts +++ b/packages/@lwc/engine-core/src/framework/hydration.ts @@ -667,7 +667,7 @@ function validateClassAttr( if ( !isUndefined(className) && String(className) !== elmClassName && - // No mismatch if SSR className is empty and the client-side class is null + // No mismatch if SSR `class` attribute is missing and CSR `class` is the empty string !(className === '' && isNull(elmClassName)) ) { // className is used when class is bound to an expr.