Skip to content

Commit f225c63

Browse files
committed
Merge branch 'main' into run-batch-until-complete
2 parents 925dbeb + f549478 commit f225c63

File tree

10 files changed

+137
-7
lines changed

10 files changed

+137
-7
lines changed

.changeset/witty-seas-learn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: ensure guards (eg. if, each, key) run before their contents

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

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ function infinite_loop_guard() {
604604
}
605605
}
606606

607-
/** @type {Effect[] | null} */
607+
/** @type {Set<Effect> | null} */
608608
export let eager_block_effects = null;
609609

610610
/**
@@ -621,7 +621,7 @@ function flush_queued_effects(effects) {
621621
var effect = effects[i++];
622622

623623
if ((effect.f & (DESTROYED | INERT)) === 0 && is_dirty(effect)) {
624-
eager_block_effects = [];
624+
eager_block_effects = new Set();
625625

626626
update_effect(effect);
627627

@@ -644,15 +644,34 @@ function flush_queued_effects(effects) {
644644

645645
// If update_effect() has a flushSync() in it, we may have flushed another flush_queued_effects(),
646646
// which already handled this logic and did set eager_block_effects to null.
647-
if (eager_block_effects?.length > 0) {
648-
// TODO this feels incorrect! it gets the tests passing
647+
if (eager_block_effects?.size > 0) {
649648
old_values.clear();
650649

651650
for (const e of eager_block_effects) {
652-
update_effect(e);
651+
// Skip eager effects that have already been unmounted
652+
if ((e.f & (DESTROYED | INERT)) !== 0) continue;
653+
654+
// Run effects in order from ancestor to descendant, else we could run into nullpointers
655+
/** @type {Effect[]} */
656+
const ordered_effects = [e];
657+
let ancestor = e.parent;
658+
while (ancestor !== null) {
659+
if (eager_block_effects.has(ancestor)) {
660+
eager_block_effects.delete(ancestor);
661+
ordered_effects.push(ancestor);
662+
}
663+
ancestor = ancestor.parent;
664+
}
665+
666+
for (let j = ordered_effects.length - 1; j >= 0; j--) {
667+
const e = ordered_effects[j];
668+
// Skip eager effects that have already been unmounted
669+
if ((e.f & (DESTROYED | INERT)) !== 0) continue;
670+
update_effect(e);
671+
}
653672
}
654673

655-
eager_block_effects = [];
674+
eager_block_effects.clear();
656675
}
657676
}
658677
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ function mark_reactions(signal, status) {
336336
} else if (not_dirty) {
337337
if ((flags & BLOCK_EFFECT) !== 0) {
338338
if (eager_block_effects !== null) {
339-
eager_block_effects.push(/** @type {Effect} */ (reaction));
339+
eager_block_effects.add(/** @type {Effect} */ (reaction));
340340
}
341341
}
342342

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { test } from '../../test';
2+
import { flushSync } from 'svelte';
3+
4+
export default test({
5+
mode: ['client'],
6+
async test({ target, assert, logs }) {
7+
const button = target.querySelector('button');
8+
9+
button?.click();
10+
flushSync();
11+
button?.click();
12+
flushSync();
13+
button?.click();
14+
flushSync();
15+
button?.click();
16+
flushSync();
17+
18+
assert.deepEqual(logs, ['two', 'one', 'two', 'one', 'two']);
19+
}
20+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script lang="ts">
2+
let b = $state(false);
3+
let v = $state("two");
4+
5+
$effect(() => {
6+
v = b ? "one" : "two";
7+
})
8+
</script>
9+
10+
<button onclick={() => b = !b}>Trigger</button>
11+
12+
{#if v === "one"}
13+
<div>if1 matched! {console.log('one')}</div>
14+
{:else if v === "two"}
15+
<div>if2 matched! {console.log('two')}</div>
16+
{:else}
17+
<div>nothing matched {console.log('else')}</div>
18+
{/if}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { test } from '../../test';
2+
import { flushSync } from 'svelte';
3+
4+
export default test({
5+
mode: ['client'],
6+
async test({ target, assert }) {
7+
const button = target.querySelector('button');
8+
9+
flushSync(() => button?.click());
10+
11+
assert.equal(target.textContent?.trim(), 'Trigger');
12+
}
13+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script>
2+
let centerRow = $state({ nested: { optional: 2, required: 3 } });
3+
4+
let someChange = $state(false);
5+
$effect(() => {
6+
if (someChange) centerRow = undefined;
7+
});
8+
</script>
9+
10+
{#if centerRow?.nested}
11+
{#if centerRow?.nested?.optional != undefined && centerRow.nested.optional > 0}
12+
op: {centerRow.nested.optional}<br />
13+
{:else}
14+
req: {centerRow.nested.required}<br />
15+
{/if}
16+
{/if}
17+
18+
<button onclick={() => (someChange = true)}>Trigger</button>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
let { p } = $props();
3+
$effect.pre(() => {
4+
console.log('running ' + p)
5+
})
6+
</script>
Lines changed: 13 additions & 0 deletions
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+
mode: ['client'],
6+
async test({ assert, target, logs }) {
7+
const button = target.querySelector('button');
8+
9+
button?.click();
10+
flushSync();
11+
assert.deepEqual(logs, ['pre', 'running b', 'pre', 'pre']);
12+
}
13+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script>
2+
import Component from './Component.svelte';
3+
4+
let p = $state('b');
5+
6+
$effect.pre(() => {
7+
console.log('pre')
8+
if (p === 'a') p = null;
9+
})
10+
</script>
11+
12+
{#if p || !p}
13+
{#if p}
14+
<Component {p} />
15+
{/if}
16+
{/if}
17+
18+
<button onclick={() => p = 'a'}>a</button>

0 commit comments

Comments
 (0)