diff --git a/.github/workflows/karma.yml b/.github/workflows/karma.yml index 841704d518..51fc27b5e8 100644 --- a/.github/workflows/karma.yml +++ b/.github/workflows/karma.yml @@ -58,7 +58,9 @@ jobs: - run: LEGACY_BROWSERS=1 yarn sauce:ci - run: FORCE_NATIVE_SHADOW_MODE_FOR_TEST=1 yarn sauce:ci - run: DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE=1 yarn sauce:ci + - run: DISABLE_DETACHED_REHYDRATION=1 yarn sauce:ci - run: DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE=1 DISABLE_SYNTHETIC=1 yarn sauce:ci + - run: DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE=1 DISABLE_DETACHED_REHYDRATION=1 yarn sauce:ci - name: Upload coverage results uses: actions/upload-artifact@v4 @@ -187,6 +189,8 @@ jobs: - run: NODE_ENV_FOR_TEST=production yarn hydration:sauce:ci:engine-server - run: DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE=1 yarn hydration:sauce:ci:engine-server - run: DISABLE_STATIC_CONTENT_OPTIMIZATION=1 yarn hydration:sauce:ci:engine-server + - run: DISABLE_DETACHED_REHYDRATION=1 yarn hydration:sauce:ci:engine-server + - run: DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE=1 DISABLE_DETACHED_REHYDRATION=1 yarn hydration:sauce:ci:engine-server - run: yarn hydration:sauce:ci - run: ENABLE_SYNTHETIC_SHADOW_IN_HYDRATION=1 yarn hydration:sauce:ci - run: NODE_ENV_FOR_TEST=production yarn hydration:sauce:ci diff --git a/packages/@lwc/engine-core/src/framework/vm.ts b/packages/@lwc/engine-core/src/framework/vm.ts index 54db244bc1..e5c977d9b6 100644 --- a/packages/@lwc/engine-core/src/framework/vm.ts +++ b/packages/@lwc/engine-core/src/framework/vm.ts @@ -673,7 +673,14 @@ function flushRehydrationQueue() { for (let i = 0, len = vms.length; i < len; i += 1) { const vm = vms[i]; try { - rehydrate(vm); + // We want to prevent rehydration from occurring when nodes are detached from the DOM as this can trigger + // unintended side effects, like lifecycle methods being called multiple times. + // For backwards compatibility, we use a flag to control the check. + // 1. When flag is off, always rehydrate (legacy behavior) + // 2. When flag is on, only rehydrate when the VM state is connected (fixed behavior) + if (!lwcRuntimeFlags.DISABLE_DETACHED_REHYDRATION || vm.state === VMState.connected) { + rehydrate(vm); + } } catch (error) { if (i + 1 < len) { // pieces of the queue are still pending to be rehydrated, those should have priority diff --git a/packages/@lwc/features/src/index.ts b/packages/@lwc/features/src/index.ts index 2c4608d7b0..59dc9624f2 100644 --- a/packages/@lwc/features/src/index.ts +++ b/packages/@lwc/features/src/index.ts @@ -23,6 +23,7 @@ const features: FeatureFlagMap = { DISABLE_SCOPE_TOKEN_VALIDATION: null, LEGACY_LOCKER_ENABLED: null, DISABLE_LEGACY_VALIDATION: null, + DISABLE_DETACHED_REHYDRATION: null, }; if (!(globalThis as any).lwcRuntimeFlags) { diff --git a/packages/@lwc/features/src/types.ts b/packages/@lwc/features/src/types.ts index ab7984b7a8..f66032f010 100644 --- a/packages/@lwc/features/src/types.ts +++ b/packages/@lwc/features/src/types.ts @@ -93,6 +93,12 @@ export interface FeatureFlagMap { * If false or unset, then the value of the `LEGACY_LOCKER_ENABLED` flag is used. */ DISABLE_LEGACY_VALIDATION: FeatureFlagValue; + + /** + * If true, skips rehydration of DOM elements that are not connected. + * Applies to rehydration performed while flushing the rehydration queue. + */ + DISABLE_DETACHED_REHYDRATION: FeatureFlagValue; } export type FeatureFlagName = keyof FeatureFlagMap; diff --git a/packages/@lwc/integration-not-karma/configs/base.js b/packages/@lwc/integration-not-karma/configs/base.js index ea4e4459ff..af0380fba7 100644 --- a/packages/@lwc/integration-not-karma/configs/base.js +++ b/packages/@lwc/integration-not-karma/configs/base.js @@ -17,6 +17,7 @@ const env = { 'ENGINE_SERVER', 'FORCE_NATIVE_SHADOW_MODE_FOR_TEST', 'NATIVE_SHADOW', + 'DISABLE_DETACHED_REHYDRATION', ]), LWC_VERSION, NODE_ENV: options.NODE_ENV_FOR_TEST, @@ -60,7 +61,10 @@ export default {