Skip to content

Commit 2b85d2a

Browse files
fix: run blocks eagerly during flush (#16631)
fixes #16548 --------- Co-authored-by: Simon H <[email protected]>
1 parent 7b2d774 commit 2b85d2a

File tree

7 files changed

+73
-18
lines changed

7 files changed

+73
-18
lines changed

.changeset/clever-months-clap.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+
perf: run blocks eagerly during flush instead of aborting

packages/svelte/src/internal/client/dom/operations.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { get_descriptor, is_extensible } from '../../shared/utils.js';
66
import { active_effect } from '../runtime.js';
77
import { async_mode_flag } from '../../flags/index.js';
88
import { TEXT_NODE, EFFECT_RAN } from '#client/constants';
9+
import { eager_block_effects } from '../reactivity/batch.js';
910

1011
// export these for reference in the compiled code, making global name deduplication unnecessary
1112
/** @type {Window} */
@@ -214,6 +215,7 @@ export function clear_text_content(node) {
214215
*/
215216
export function should_defer_append() {
216217
if (!async_mode_flag) return false;
218+
if (eager_block_effects !== null) return false;
217219

218220
var flags = /** @type {Effect} */ (active_effect).f;
219221
return (flags & EFFECT_RAN) !== 0;

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

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -292,12 +292,12 @@ export class Batch {
292292
if (!skip && effect.fn !== null) {
293293
if (is_branch) {
294294
effect.f ^= CLEAN;
295+
} else if ((flags & EFFECT) !== 0) {
296+
this.#effects.push(effect);
297+
} else if (async_mode_flag && (flags & RENDER_EFFECT) !== 0) {
298+
this.#render_effects.push(effect);
295299
} else if ((flags & CLEAN) === 0) {
296-
if ((flags & EFFECT) !== 0) {
297-
this.#effects.push(effect);
298-
} else if (async_mode_flag && (flags & RENDER_EFFECT) !== 0) {
299-
this.#render_effects.push(effect);
300-
} else if ((flags & ASYNC) !== 0) {
300+
if ((flags & ASYNC) !== 0) {
301301
var effects = effect.b?.pending ? this.#boundary_async_effects : this.#async_effects;
302302
effects.push(effect);
303303
} else if (is_dirty(effect)) {
@@ -584,6 +584,9 @@ function infinite_loop_guard() {
584584
}
585585
}
586586

587+
/** @type {Effect[] | null} */
588+
export let eager_block_effects = null;
589+
587590
/**
588591
* @param {Array<Effect>} effects
589592
* @returns {void}
@@ -598,7 +601,7 @@ function flush_queued_effects(effects) {
598601
var effect = effects[i++];
599602

600603
if ((effect.f & (DESTROYED | INERT)) === 0 && is_dirty(effect)) {
601-
var n = current_batch ? current_batch.current.size : 0;
604+
eager_block_effects = [];
602605

603606
update_effect(effect);
604607

@@ -619,21 +622,20 @@ function flush_queued_effects(effects) {
619622
}
620623
}
621624

622-
// if state is written in a user effect, abort and re-schedule, lest we run
623-
// effects that should be removed as a result of the state change
624-
if (
625-
current_batch !== null &&
626-
current_batch.current.size > n &&
627-
(effect.f & USER_EFFECT) !== 0
628-
) {
629-
break;
625+
if (eager_block_effects.length > 0) {
626+
// TODO this feels incorrect! it gets the tests passing
627+
old_values.clear();
628+
629+
for (const e of eager_block_effects) {
630+
update_effect(e);
631+
}
632+
633+
eager_block_effects = [];
630634
}
631635
}
632636
}
633637

634-
while (i < length) {
635-
schedule_effect(effects[i++]);
636-
}
638+
eager_block_effects = null;
637639
}
638640

639641
/**

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import * as e from '../errors.js';
3333
import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
3434
import { get_stack, tag_proxy } from '../dev/tracing.js';
3535
import { component_context, is_runes } from '../context.js';
36-
import { Batch, schedule_effect } from './batch.js';
36+
import { Batch, eager_block_effects, schedule_effect } from './batch.js';
3737
import { proxy } from '../proxy.js';
3838
import { execute_derived } from './deriveds.js';
3939

@@ -334,6 +334,12 @@ function mark_reactions(signal, status) {
334334
if ((flags & DERIVED) !== 0) {
335335
mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY);
336336
} else if (not_dirty) {
337+
if ((flags & BLOCK_EFFECT) !== 0) {
338+
if (eager_block_effects !== null) {
339+
eager_block_effects.push(/** @type {Effect} */ (reaction));
340+
}
341+
}
342+
337343
schedule_effect(/** @type {Effect} */ (reaction));
338344
}
339345
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
let { children } = $props();
3+
4+
let inited = $state(false);
5+
6+
$effect(() => {
7+
inited = true;
8+
});
9+
</script>
10+
11+
{#if inited}
12+
<span>{@render children()}</span>
13+
{/if}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target }) {
6+
const [button] = target.querySelectorAll('button');
7+
8+
assert.doesNotThrow(() => {
9+
flushSync(() => button.click());
10+
});
11+
}
12+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script>
2+
import Child from './Child.svelte';
3+
4+
let show = $state(false);
5+
</script>
6+
7+
<button onclick={() => show = !show}>
8+
toggle
9+
</button>
10+
11+
{#if show}
12+
{#each { length: 1234 } as i}
13+
<Child>{i}</Child>
14+
{/each}
15+
{/if}

0 commit comments

Comments
 (0)