diff --git a/.changeset/shiny-mayflies-clean.md b/.changeset/shiny-mayflies-clean.md new file mode 100644 index 000000000000..3adaa3dec85a --- /dev/null +++ b/.changeset/shiny-mayflies-clean.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: only destroy snippets when they have changed diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index c432943c1a6a..80ed8c09f4f6 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -20,15 +20,17 @@ export function if_block( elseif = false ) { /** @type {import('#client').Effect | null} */ - let consequent_effect = null; + var consequent_effect = null; /** @type {import('#client').Effect | null} */ - let alternate_effect = null; + var alternate_effect = null; /** @type {boolean | null} */ - let condition = null; + var condition = null; - const effect = block(() => { + var flags = elseif ? EFFECT_TRANSPARENT : 0; + + block(() => { if (condition === (condition = !!get_condition())) return; /** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */ @@ -76,9 +78,5 @@ export function if_block( // continue in hydration mode set_hydrating(true); } - }); - - if (elseif) { - effect.f |= EFFECT_TRANSPARENT; - } + }, flags); } diff --git a/packages/svelte/src/internal/client/dom/blocks/snippet.js b/packages/svelte/src/internal/client/dom/blocks/snippet.js index e88544091082..c5097ee2c3c6 100644 --- a/packages/svelte/src/internal/client/dom/blocks/snippet.js +++ b/packages/svelte/src/internal/client/dom/blocks/snippet.js @@ -1,5 +1,5 @@ import { EFFECT_TRANSPARENT } from '../../constants.js'; -import { branch, render_effect } from '../../reactivity/effects.js'; +import { branch, block, destroy_effect } from '../../reactivity/effects.js'; /** * @template {(node: import('#client').TemplateNode, ...args: any[]) => import('#client').Dom} SnippetFn @@ -12,13 +12,19 @@ export function snippet(get_snippet, node, ...args) { /** @type {SnippetFn | null | undefined} */ var snippet; - var effect = render_effect(() => { + /** @type {import('#client').Effect | null} */ + var snippet_effect; + + block(() => { if (snippet === (snippet = get_snippet())) return; - if (snippet) { - branch(() => /** @type {SnippetFn} */ (snippet)(node, ...args)); + if (snippet_effect) { + destroy_effect(snippet_effect); + snippet_effect = null; } - }); - effect.f |= EFFECT_TRANSPARENT; + if (snippet) { + snippet_effect = branch(() => /** @type {SnippetFn} */ (snippet)(node, ...args)); + } + }, EFFECT_TRANSPARENT); } diff --git a/packages/svelte/src/internal/client/dom/elements/transitions.js b/packages/svelte/src/internal/client/dom/elements/transitions.js index a9f55d940fcf..3551a093da1d 100644 --- a/packages/svelte/src/internal/client/dom/elements/transitions.js +++ b/packages/svelte/src/internal/client/dom/elements/transitions.js @@ -7,7 +7,7 @@ import { should_intro } from '../../render.js'; import { is_function } from '../../utils.js'; import { current_each_item } from '../blocks/each.js'; import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js'; -import { BLOCK_EFFECT, EFFECT_RAN } from '../../constants.js'; +import { BLOCK_EFFECT, EFFECT_RAN, EFFECT_TRANSPARENT } from '../../constants.js'; /** * @template T @@ -241,18 +241,26 @@ export function transition(flags, element, get_fn, get_params) { (e.transitions ??= []).push(transition); - // if this is a local transition, we only want to run it if the parent (block) effect's - // parent (branch) effect is where the state change happened. we can determine that by - // looking at whether the branch effect is currently initializing + // if this is a local transition, we only want to run it if the parent (branch) effect's + // parent (block) effect is where the state change happened. we can determine that by + // looking at whether the block effect is currently initializing if (is_intro && should_intro) { - var parent = /** @type {import('#client').Effect} */ (e.parent); + let run = is_global; - // e.g snippets are implemented as render effects — keep going until we find the parent block - while ((parent.f & BLOCK_EFFECT) === 0 && parent.parent) { - parent = parent.parent; + if (!run) { + var block = /** @type {import('#client').Effect | null} */ (e.parent); + + // skip over transparent blocks (e.g. snippets, else-if blocks) + while (block && (block.f & EFFECT_TRANSPARENT) !== 0) { + while ((block = block.parent)) { + if ((block.f & BLOCK_EFFECT) !== 0) break; + } + } + + run = !block || (block.f & EFFECT_RAN) !== 0; } - if (is_global || (parent.f & EFFECT_RAN) !== 0) { + if (run) { effect(() => { untrack(() => transition.in()); }); diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 3b0b97ade50e..515d13136eb1 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -230,9 +230,12 @@ export function render_effect(fn) { return create_effect(RENDER_EFFECT, fn, true); } -/** @param {(() => void)} fn */ -export function block(fn) { - return create_effect(RENDER_EFFECT | BLOCK_EFFECT, fn, true); +/** + * @param {(() => void)} fn + * @param {number} flags + */ +export function block(fn, flags = 0) { + return create_effect(RENDER_EFFECT | BLOCK_EFFECT | flags, fn, true); } /** @param {(() => void)} fn */