Skip to content

Commit 3fc2007

Browse files
authored
fix: run effect roots in tree order (#15446)
* process effect roots in tree order * bring test over * add test * changeset * tidy
1 parent e591e87 commit 3fc2007

File tree

8 files changed

+104
-18
lines changed

8 files changed

+104
-18
lines changed

.changeset/shy-falcons-occur.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: run effect roots in tree order

packages/svelte/src/internal/client/reactivity/effects.js

+15-9
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,12 @@ function push_effect(effect, parent_effect) {
8282
* @returns {Effect}
8383
*/
8484
function create_effect(type, fn, sync, push = true) {
85-
var is_root = (type & ROOT_EFFECT) !== 0;
86-
var parent_effect = active_effect;
85+
var parent = active_effect;
8786

8887
if (DEV) {
8988
// Ensure the parent is never an inspect effect
90-
while (parent_effect !== null && (parent_effect.f & INSPECT_EFFECT) !== 0) {
91-
parent_effect = parent_effect.parent;
89+
while (parent !== null && (parent.f & INSPECT_EFFECT) !== 0) {
90+
parent = parent.parent;
9291
}
9392
}
9493

@@ -103,7 +102,7 @@ function create_effect(type, fn, sync, push = true) {
103102
fn,
104103
last: null,
105104
next: null,
106-
parent: is_root ? null : parent_effect,
105+
parent,
107106
prev: null,
108107
teardown: null,
109108
transitions: null,
@@ -136,9 +135,9 @@ function create_effect(type, fn, sync, push = true) {
136135
effect.teardown === null &&
137136
(effect.f & (EFFECT_HAS_DERIVED | BOUNDARY_EFFECT)) === 0;
138137

139-
if (!inert && !is_root && push) {
140-
if (parent_effect !== null) {
141-
push_effect(effect, parent_effect);
138+
if (!inert && push) {
139+
if (parent !== null) {
140+
push_effect(effect, parent);
142141
}
143142

144143
// if we're in a derived, add the effect there too
@@ -391,7 +390,14 @@ export function destroy_effect_children(signal, remove_dom = false) {
391390

392391
while (effect !== null) {
393392
var next = effect.next;
394-
destroy_effect(effect, remove_dom);
393+
394+
if ((effect.f & ROOT_EFFECT) !== 0) {
395+
// this is now an independent root
396+
effect.parent = null;
397+
} else {
398+
destroy_effect(effect, remove_dom);
399+
}
400+
395401
effect = next;
396402
}
397403
}

packages/svelte/src/internal/client/runtime.js

+5-9
Original file line numberDiff line numberDiff line change
@@ -661,13 +661,7 @@ function flush_queued_root_effects() {
661661
queued_root_effects = [];
662662

663663
for (var i = 0; i < length; i++) {
664-
var root = root_effects[i];
665-
666-
if ((root.f & CLEAN) === 0) {
667-
root.f ^= CLEAN;
668-
}
669-
670-
var collected_effects = process_effects(root);
664+
var collected_effects = process_effects(root_effects[i]);
671665
flush_queued_effects(collected_effects);
672666
}
673667
}
@@ -759,11 +753,12 @@ function process_effects(root) {
759753
/** @type {Effect[]} */
760754
var effects = [];
761755

762-
var effect = root.first;
756+
/** @type {Effect | null} */
757+
var effect = root;
763758

764759
while (effect !== null) {
765760
var flags = effect.f;
766-
var is_branch = (flags & BRANCH_EFFECT) !== 0;
761+
var is_branch = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) !== 0;
767762
var is_skippable_branch = is_branch && (flags & CLEAN) !== 0;
768763

769764
if (!is_skippable_branch && (flags & INERT) === 0) {
@@ -788,6 +783,7 @@ function process_effects(root) {
788783
}
789784
}
790785

786+
/** @type {Effect | null} */
791787
var child = effect.first;
792788

793789
if (child !== null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target, logs }) {
6+
const [b1, b2] = target.querySelectorAll('button');
7+
8+
flushSync(() => b1.click());
9+
assert.deepEqual(logs, [0, 1]);
10+
11+
flushSync(() => b1.click());
12+
assert.deepEqual(logs, [0, 1, 2]);
13+
14+
flushSync(() => b2.click());
15+
assert.deepEqual(logs, [0, 1, 2]);
16+
}
17+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script>
2+
let obj = $state({ count: 0 });
3+
4+
$effect.root(() => {
5+
let teardown;
6+
7+
$effect.pre(() => {
8+
if (obj) {
9+
teardown ??= $effect.root(() => {
10+
$effect.pre(() => {
11+
console.log(obj.count);
12+
});
13+
});
14+
} else {
15+
teardown?.();
16+
teardown = null;
17+
}
18+
});
19+
});
20+
</script>
21+
22+
<button onclick={() => ((obj ??= { count: 0 }).count += 1)}>+1</button>
23+
<button onclick={() => (obj = null)}>null</button>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target }) {
6+
let [, btn2] = target.querySelectorAll('button');
7+
8+
btn2.click();
9+
flushSync();
10+
11+
assert.htmlEqual(target.innerHTML, `<button>Set data</button><button>Clear data</button>`);
12+
}
13+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
import { toStore } from 'svelte/store'
3+
4+
let { data } = $props()
5+
const currentValue = toStore(() => data.value)
6+
</script>
7+
8+
<p>
9+
Current value:
10+
<span>{$currentValue}</span>
11+
</p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script>
2+
import Child from './child.svelte'
3+
4+
let data = $state({ value: 'hello' });
5+
6+
const setData = () => (data = { value: 'hello' })
7+
const clearData = () => (data = undefined)
8+
</script>
9+
10+
<button onclick={setData}>Set data</button>
11+
<button onclick={clearData}>Clear data</button>
12+
13+
{#if data}
14+
<Child {data} />
15+
{/if}

0 commit comments

Comments
 (0)