From d3cb1482fe45a2b80b189856d8ab8a1f2c446bc6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 23 Aug 2025 16:29:10 -0400 Subject: [PATCH] perf: better effect pruning (#16625) * tweak * prune effects where possible * tweak * simplify * simplify * changeset * reset parent if necessary --- .changeset/tasty-lizards-care.md | 5 ++ .../src/internal/client/reactivity/effects.js | 57 +++++++++++-------- 2 files changed, 39 insertions(+), 23 deletions(-) create mode 100644 .changeset/tasty-lizards-care.md diff --git a/.changeset/tasty-lizards-care.md b/.changeset/tasty-lizards-care.md new file mode 100644 index 0000000000..b6aff07ffb --- /dev/null +++ b/.changeset/tasty-lizards-care.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +perf: prune effects without dependencies diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index df3dd75808..2c9e4db911 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -133,29 +133,40 @@ function create_effect(type, fn, sync, push = true) { schedule_effect(effect); } - // if an effect has no dependencies, no DOM and no teardown function, - // don't bother adding it to the effect tree - var inert = - sync && - effect.deps === null && - effect.first === null && - effect.nodes_start === null && - effect.teardown === null && - (effect.f & EFFECT_PRESERVED) === 0; - - if (!inert && push) { - if (parent !== null) { - push_effect(effect, parent); - } + if (push) { + /** @type {Effect | null} */ + var e = effect; - // if we're in a derived, add the effect there too + // if an effect has already ran and doesn't need to be kept in the tree + // (because it won't re-run, has no DOM, and has no teardown etc) + // then we skip it and go to its child (if any) if ( - active_reaction !== null && - (active_reaction.f & DERIVED) !== 0 && - (type & ROOT_EFFECT) === 0 + sync && + e.deps === null && + e.teardown === null && + e.nodes_start === null && + e.first === e.last && // either `null`, or a singular child + (e.f & EFFECT_PRESERVED) === 0 ) { - var derived = /** @type {Derived} */ (active_reaction); - (derived.effects ??= []).push(effect); + e = e.first; + } + + if (e !== null) { + e.parent = parent; + + if (parent !== null) { + push_effect(e, parent); + } + + // if we're in a derived, add the effect there too + if ( + active_reaction !== null && + (active_reaction.f & DERIVED) !== 0 && + (type & ROOT_EFFECT) === 0 + ) { + var derived = /** @type {Derived} */ (active_reaction); + (derived.effects ??= []).push(e); + } } } @@ -242,7 +253,7 @@ export function inspect_effect(fn) { */ export function effect_root(fn) { Batch.ensure(); - const effect = create_effect(ROOT_EFFECT, fn, true); + const effect = create_effect(ROOT_EFFECT | EFFECT_PRESERVED, fn, true); return () => { destroy_effect(effect); @@ -256,7 +267,7 @@ export function effect_root(fn) { */ export function component_root(fn) { Batch.ensure(); - const effect = create_effect(ROOT_EFFECT, fn, true); + const effect = create_effect(ROOT_EFFECT | EFFECT_PRESERVED, fn, true); return (options = {}) => { return new Promise((fulfil) => { @@ -375,7 +386,7 @@ export function block(fn, flags = 0) { * @param {boolean} [push] */ export function branch(fn, push = true) { - return create_effect(BRANCH_EFFECT, fn, true, push); + return create_effect(BRANCH_EFFECT | EFFECT_PRESERVED, fn, true, push); } /**