From 93443f93160792d49a85b6b8ac7571abd3fccede Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 20 May 2025 11:13:55 -0400 Subject: [PATCH] fix: only re-run directly applied attachment if it changed (#15962) * fix: only re-run directly applied attachment if it changed * add note * one down * fix * Revert "one down" This reverts commit 9f6c4cf84838de8d8cb93e7217de37871839ac8b. --- .changeset/slimy-drinks-divide.md | 5 ++++ .../client/dom/elements/attachments.js | 30 +++++++++++++++---- .../attachment-in-mutated-state/_config.js | 16 ++++++++++ .../attachment-in-mutated-state/main.svelte | 15 ++++++++++ 4 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 .changeset/slimy-drinks-divide.md create mode 100644 packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/main.svelte diff --git a/.changeset/slimy-drinks-divide.md b/.changeset/slimy-drinks-divide.md new file mode 100644 index 0000000000..420d0bed56 --- /dev/null +++ b/.changeset/slimy-drinks-divide.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: only re-run directly applied attachment if it changed diff --git a/packages/svelte/src/internal/client/dom/elements/attachments.js b/packages/svelte/src/internal/client/dom/elements/attachments.js index 6e3089a384..4fc1280138 100644 --- a/packages/svelte/src/internal/client/dom/elements/attachments.js +++ b/packages/svelte/src/internal/client/dom/elements/attachments.js @@ -1,15 +1,33 @@ -import { effect } from '../../reactivity/effects.js'; +/** @import { Effect } from '#client' */ +import { block, branch, effect, destroy_effect } from '../../reactivity/effects.js'; + +// TODO in 6.0 or 7.0, when we remove legacy mode, we can simplify this by +// getting rid of the block/branch stuff and just letting the effect rip. +// see https://github.com/sveltejs/svelte/pull/15962 /** * @param {Element} node * @param {() => (node: Element) => void} get_fn */ export function attach(node, get_fn) { - effect(() => { - const fn = get_fn(); + /** @type {false | undefined | ((node: Element) => void)} */ + var fn = undefined; + + /** @type {Effect | null} */ + var e; + + block(() => { + if (fn !== (fn = get_fn())) { + if (e) { + destroy_effect(e); + e = null; + } - // we use `&&` rather than `?.` so that things like - // `{@attach DEV && something_dev_only()}` work - return fn && fn(node); + if (fn) { + e = branch(() => { + effect(() => /** @type {(node: Element) => void} */ (fn)(node)); + }); + } + } }); } diff --git a/packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/_config.js b/packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/_config.js new file mode 100644 index 0000000000..5d37252358 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target, logs }) { + assert.deepEqual(logs, ['up']); + + const button = target.querySelector('button'); + + flushSync(() => button?.click()); + assert.deepEqual(logs, ['up']); + + flushSync(() => button?.click()); + assert.deepEqual(logs, ['up', 'down']); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/main.svelte b/packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/main.svelte new file mode 100644 index 0000000000..fbec108c7a --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/main.svelte @@ -0,0 +1,15 @@ + + + + +{#if state.count < 2} +
+{/if}