diff --git a/.changeset/cuddly-walls-tan.md b/.changeset/cuddly-walls-tan.md new file mode 100644 index 0000000000..feececc052 --- /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/.changeset/curvy-carrots-prove.md b/.changeset/curvy-carrots-prove.md deleted file mode 100644 index 7723990179..0000000000 --- a/.changeset/curvy-carrots-prove.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: account for mounting when `select_option` in `attribute_effect` diff --git a/.changeset/new-wolves-punch.md b/.changeset/new-wolves-punch.md deleted file mode 100644 index c856038cbc..0000000000 --- a/.changeset/new-wolves-punch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: do not proxify the value assigned to a derived diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index ad72bfabb0..4f1be5e299 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.35.3 + +### Patch Changes + +- fix: account for mounting when `select_option` in `attribute_effect` ([#16309](https://github.com/sveltejs/svelte/pull/16309)) + +- fix: do not proxify the value assigned to a derived ([#16302](https://github.com/sveltejs/svelte/pull/16302)) + ## 5.35.2 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index dfec90e96e..2d4b8992dd 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.2", + "version": "5.35.3", "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 b4798728bb..44ee7a45c8 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -19,12 +19,13 @@ 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; // Flags used for async -export const REACTION_IS_UPDATING = 1 << 21; -export const EFFECT_ASYNC = 1 << 22; +export const REACTION_IS_UPDATING = 1 << 22; +export const EFFECT_ASYNC = 1 << 23; -export const ERROR_VALUE = 1 << 23; +export const ERROR_VALUE = 1 << 24; 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 895e75bfc2..93c14e6c05 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 { async_mode_flag, legacy_mode_flag } from '../flags/index.js'; import { FILENAME } from '../../constants.js'; @@ -189,7 +189,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/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 5febc61fbf..9db6c6a276 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -9,7 +9,8 @@ import { EFFECT_ASYNC, INERT, RENDER_EFFECT, - ROOT_EFFECT + ROOT_EFFECT, + USER_EFFECT } from '#client/constants'; import { async_mode_flag } from '../../flags/index.js'; import { deferred, define_property } from '../../shared/utils.js'; @@ -23,7 +24,8 @@ import { set_is_updating_effect, set_queued_root_effects, set_signal_status, - update_effect + update_effect, + write_version } from '../runtime.js'; import * as e from '../errors.js'; import { flush_tasks } from '../dom/task.js'; @@ -513,7 +515,7 @@ function infinite_loop_guard() { * @param {Array} effects * @returns {void} */ -export function flush_queued_effects(effects) { +function flush_queued_effects(effects) { var length = effects.length; if (length === 0) return; @@ -522,6 +524,8 @@ export 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. @@ -538,9 +542,19 @@ export 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/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index a6b3c8f91a..cedfed1ea9 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -32,7 +32,8 @@ import { MAYBE_DIRTY, EFFECT_PRESERVED, BOUNDARY_EFFECT, - STALE_REACTION + STALE_REACTION, + USER_EFFECT } from '#client/constants'; import * as e from '../errors.js'; import { DEV } from 'esm-env'; @@ -203,11 +204,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 @@ -220,7 +227,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 94e329cb72..a9da628d0a 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -133,7 +133,7 @@ export function set_untracked_writes(value) { * @type {number} Used by sources and deriveds for handling updates. * Version starts from 1 so that unowned deriveds differentiate between a created effect and a run one for tracing **/ -let write_version = 1; +export let write_version = 1; /** @type {number} Used to version each read of a source of derived to avoid duplicating depedencies inside a reaction */ let read_version = 0; diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 47c354c7a2..d98b622908 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.2'; +export const VERSION = '5.35.3'; 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 0000000000..2e789a0460 --- /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 0000000000..1fad19bc15 --- /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 0000000000..b905b4b4d7 --- /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 0000000000..8f9077e954 --- /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 0000000000..eee487fa13 --- /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 0000000000..54f4869d62 --- /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 0000000000..2a2e634db1 --- /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 0000000000..9606fd8602 --- /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 0000000000..29c33c7b18 --- /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 0000000000..c9c45c50cf --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-order-7/main.svelte @@ -0,0 +1,22 @@ + + + + + + +
+ + + {#snippet children(nested)} +
+ {/snippet} +