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/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/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'; 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 @@ + + + + + + +
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: