From cf2ff5e8dc908395853e3a7cc2022fe3552f7062 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 7 Jul 2025 11:46:57 -0400 Subject: [PATCH 1/2] fix: abort and reschedule effect processing after state change in user effect (#16280) * fix: abort and reschedule effect processing after state change in user effect * failing test * skip for now --- .changeset/cuddly-walls-tan.md | 5 ++++ .../svelte/src/internal/client/constants.js | 1 + .../svelte/src/internal/client/context.js | 4 ++-- .../src/internal/client/reactivity/effects.js | 15 ++++++++---- .../svelte/src/internal/client/runtime.js | 15 +++++++++++- .../samples/effect-order-6/A.svelte | 11 +++++++++ .../samples/effect-order-6/B.svelte | 9 ++++++++ .../samples/effect-order-6/Child.svelte | 20 ++++++++++++++++ .../samples/effect-order-6/_config.js | 13 +++++++++++ .../samples/effect-order-6/main.svelte | 23 +++++++++++++++++++ .../samples/effect-order-7/A.svelte | 9 ++++++++ .../samples/effect-order-7/B.svelte | 9 ++++++++ .../samples/effect-order-7/Child.svelte | 20 ++++++++++++++++ .../samples/effect-order-7/_config.js | 15 ++++++++++++ .../samples/effect-order-7/main.svelte | 22 ++++++++++++++++++ 15 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 .changeset/cuddly-walls-tan.md create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-order-6/A.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-order-6/B.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-order-6/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-order-6/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-order-6/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-order-7/A.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-order-7/B.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-order-7/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-order-7/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-order-7/main.svelte diff --git a/.changeset/cuddly-walls-tan.md b/.changeset/cuddly-walls-tan.md new file mode 100644 index 000000000000..feececc052d9 --- /dev/null +++ b/.changeset/cuddly-walls-tan.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: abort and reschedule effect processing after state change in user effect diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 1cffa4394081..a5a8b42972dc 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -19,6 +19,7 @@ export const INSPECT_EFFECT = 1 << 17; export const HEAD_EFFECT = 1 << 18; export const EFFECT_PRESERVED = 1 << 19; export const EFFECT_IS_UPDATING = 1 << 20; +export const USER_EFFECT = 1 << 21; export const STATE_SYMBOL = Symbol('$state'); export const LEGACY_PROPS = Symbol('legacy props'); diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index e4220149ab2c..736b81c1726a 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -9,7 +9,7 @@ import { set_active_effect, set_active_reaction } from './runtime.js'; -import { effect, teardown } from './reactivity/effects.js'; +import { create_user_effect, teardown } from './reactivity/effects.js'; import { legacy_mode_flag } from '../flags/index.js'; import { FILENAME } from '../../constants.js'; @@ -191,7 +191,7 @@ export function pop(component) { var component_effect = component_effects[i]; set_active_effect(component_effect.effect); set_active_reaction(component_effect.reaction); - effect(component_effect.fn); + create_user_effect(component_effect.fn); } } finally { set_active_effect(previous_effect); diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 7570064c37c7..df2afd3e386e 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -33,7 +33,8 @@ import { MAYBE_DIRTY, EFFECT_PRESERVED, BOUNDARY_EFFECT, - STALE_REACTION + STALE_REACTION, + USER_EFFECT } from '#client/constants'; import { set } from './sources.js'; import * as e from '../errors.js'; @@ -200,11 +201,17 @@ export function user_effect(fn) { reaction: active_reaction }); } else { - var signal = effect(fn); - return signal; + return create_user_effect(fn); } } +/** + * @param {() => void | (() => void)} fn + */ +export function create_user_effect(fn) { + return create_effect(EFFECT | USER_EFFECT, fn, false); +} + /** * Internal representation of `$effect.pre(...)` * @param {() => void | (() => void)} fn @@ -217,7 +224,7 @@ export function user_pre_effect(fn) { value: '$effect.pre' }); } - return render_effect(fn); + return create_effect(RENDER_EFFECT | USER_EFFECT, fn, true); } /** @param {() => void | (() => void)} fn */ diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index fce6c78b56e4..c00ade558770 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -22,7 +22,8 @@ import { ROOT_EFFECT, DISCONNECTED, EFFECT_IS_UPDATING, - STALE_REACTION + STALE_REACTION, + USER_EFFECT } from './constants.js'; import { flush_tasks } from './dom/task.js'; import { internal_set, old_values } from './reactivity/sources.js'; @@ -581,6 +582,8 @@ function flush_queued_effects(effects) { if ((effect.f & (DESTROYED | INERT)) === 0) { if (check_dirtiness(effect)) { + var wv = write_version; + update_effect(effect); // Effects with no dependencies or teardown do not get added to the effect tree. @@ -597,9 +600,19 @@ function flush_queued_effects(effects) { effect.fn = null; } } + + // if state is written in a user effect, abort and re-schedule, lest we run + // effects that should be removed as a result of the state change + if (write_version > wv && (effect.f & USER_EFFECT) !== 0) { + break; + } } } } + + for (; i < length; i += 1) { + schedule_effect(effects[i]); + } } /** diff --git a/packages/svelte/tests/runtime-runes/samples/effect-order-6/A.svelte b/packages/svelte/tests/runtime-runes/samples/effect-order-6/A.svelte new file mode 100644 index 000000000000..2e789a046007 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-order-6/A.svelte @@ -0,0 +1,11 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/effect-order-6/B.svelte b/packages/svelte/tests/runtime-runes/samples/effect-order-6/B.svelte new file mode 100644 index 000000000000..1fad19bc1568 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-order-6/B.svelte @@ -0,0 +1,9 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/effect-order-6/Child.svelte b/packages/svelte/tests/runtime-runes/samples/effect-order-6/Child.svelte new file mode 100644 index 000000000000..b905b4b4d7e3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-order-6/Child.svelte @@ -0,0 +1,20 @@ + + + + +{#if object?.boolean} + + {@render children(object.boolean)} +{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/effect-order-6/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-order-6/_config.js new file mode 100644 index 000000000000..8f9077e9544b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-order-6/_config.js @@ -0,0 +1,13 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const [open, close] = target.querySelectorAll('button'); + + flushSync(() => open.click()); + flushSync(() => close.click()); + + assert.deepEqual(logs, [true]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/effect-order-6/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-order-6/main.svelte new file mode 100644 index 000000000000..eee487fa132f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-order-6/main.svelte @@ -0,0 +1,23 @@ + + + + + + +
+ + + {#snippet children(boolean)} + + {/snippet} + + diff --git a/packages/svelte/tests/runtime-runes/samples/effect-order-7/A.svelte b/packages/svelte/tests/runtime-runes/samples/effect-order-7/A.svelte new file mode 100644 index 000000000000..54f4869d6252 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-order-7/A.svelte @@ -0,0 +1,9 @@ + + +{boolean} + + diff --git a/packages/svelte/tests/runtime-runes/samples/effect-order-7/B.svelte b/packages/svelte/tests/runtime-runes/samples/effect-order-7/B.svelte new file mode 100644 index 000000000000..2a2e634db156 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-order-7/B.svelte @@ -0,0 +1,9 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/effect-order-7/Child.svelte b/packages/svelte/tests/runtime-runes/samples/effect-order-7/Child.svelte new file mode 100644 index 000000000000..9606fd8602c7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-order-7/Child.svelte @@ -0,0 +1,20 @@ + + + + +{#if object?.nested} + + {@render children(object.nested)} +{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/effect-order-7/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-order-7/_config.js new file mode 100644 index 000000000000..29c33c7b1886 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-order-7/_config.js @@ -0,0 +1,15 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + skip: true, + + async test({ assert, target, logs }) { + const [open, close] = target.querySelectorAll('button'); + + flushSync(() => open.click()); + flushSync(() => close.click()); + + assert.deepEqual(logs, [true]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/effect-order-7/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-order-7/main.svelte new file mode 100644 index 000000000000..c9c45c50cf4d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-order-7/main.svelte @@ -0,0 +1,22 @@ + + + + + + +
+ + + {#snippet children(nested)} +
+ {/snippet} + From eb094ba059d262e5583108a85b0e549b95dbd344 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 12:37:51 -0400 Subject: [PATCH 2/2] Version Packages (#16313) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/cuddly-walls-tan.md | 5 ----- packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/cuddly-walls-tan.md diff --git a/.changeset/cuddly-walls-tan.md b/.changeset/cuddly-walls-tan.md deleted file mode 100644 index feececc052d9..000000000000 --- a/.changeset/cuddly-walls-tan.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: abort and reschedule effect processing after state change in user effect diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 4f1be5e299fd..89ae38084029 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.35.4 + +### Patch Changes + +- fix: abort and reschedule effect processing after state change in user effect ([#16280](https://github.com/sveltejs/svelte/pull/16280)) + ## 5.35.3 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 30a8f8833af1..aaf8f26fec2a 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.35.3", + "version": "5.35.4", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index d98b62290805..c4111b9e8d41 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.35.3'; +export const VERSION = '5.35.4'; export const PUBLIC_VERSION = '5'; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy