mirror of https://github.com/sveltejs/svelte
Merge f1fcdafd9e into 020242d6be
commit
426fd4f634
@ -0,0 +1,9 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: `flushSync` at the top of a component script no longer crashes or breaks reactivity
|
||||
|
||||
Calling `flushSync()` while the effect tree is still being constructed (e.g. at the top of a component's `<script>`) used to flush the in-flight batch mid-construction. The boundary then resolved against a nulled `current_batch` and crashed with `Cannot read properties of null (reading 'transfer_effects')`, and the half-built tree left the enclosing branch's CLEAN flag unbalanced — which made every later `schedule` call bail, silently killing reactivity.
|
||||
|
||||
`flushSync` now detects that a branch/root effect's update is in flight and skips the flush (there is nothing user-visible to flush yet; the batch flushes normally once construction settles). The boundary's resolution also tolerates a null batch as defense in depth, transferring deferred effects into a fresh batch instead of crashing.
|
||||
@ -0,0 +1,15 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target }) {
|
||||
assert.htmlEqual(target.innerHTML, '<button>0</button>');
|
||||
|
||||
// No explicit flushSync after the click — the update must still propagate
|
||||
// through the normal microtask flush.
|
||||
/** @type {HTMLButtonElement} */ (target.querySelector('button')).click();
|
||||
await tick();
|
||||
|
||||
assert.htmlEqual(target.innerHTML, '<button>1</button>');
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
<script>
|
||||
import { flushSync } from 'svelte';
|
||||
|
||||
let count = $state(0);
|
||||
|
||||
flushSync();
|
||||
</script>
|
||||
|
||||
<button onclick={() => count++}>{count}</button>
|
||||
@ -0,0 +1,17 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
test({ assert, target, logs }) {
|
||||
// $effect.pre must have run during init, despite the top-level flushSync
|
||||
assert.deepEqual(logs, ['pre ran']);
|
||||
assert.htmlEqual(target.innerHTML, '<button>0</button>');
|
||||
|
||||
/** @type {HTMLButtonElement} */ (target.querySelector('button')).click();
|
||||
flushSync();
|
||||
|
||||
// reactivity must still work after the top-level flushSync
|
||||
assert.htmlEqual(target.innerHTML, '<button>1</button>');
|
||||
assert.deepEqual(logs, ['pre ran']);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,13 @@
|
||||
<script>
|
||||
import { flushSync } from 'svelte';
|
||||
|
||||
let count = $state(0);
|
||||
|
||||
$effect.pre(() => {
|
||||
console.log('pre ran');
|
||||
});
|
||||
|
||||
flushSync();
|
||||
</script>
|
||||
|
||||
<button onclick={() => count++}>{count}</button>
|
||||
@ -0,0 +1,13 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
test({ assert, target }) {
|
||||
assert.htmlEqual(target.innerHTML, '<button>0</button>');
|
||||
|
||||
/** @type {HTMLButtonElement} */ (target.querySelector('button')).click();
|
||||
flushSync();
|
||||
|
||||
assert.htmlEqual(target.innerHTML, '<button>1</button>');
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
<script>
|
||||
import { flushSync } from 'svelte';
|
||||
|
||||
let count = $state(0);
|
||||
|
||||
flushSync();
|
||||
</script>
|
||||
|
||||
<button onclick={() => count++}>{count}</button>
|
||||
Loading…
Reference in new issue