Merge branch 'main' into entangle-batches-2

entangle-batches-2
Simon Holthausen 7 days ago
commit 26dc5e62ec
No known key found for this signature in database

@ -1,5 +0,0 @@
---
'svelte': patch
---
chore: drop dead code that make TSGO fail

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: don't (re)connect deriveds when read inside branch/root effects

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: skip unnecessary derived effect in earlier batch

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: avoid declaration tag warning in event handlers

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: correctly transform declaration tags during SSR

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: transform computed keys in keyed `{#each}` destructuring patterns

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: chain preprocessor sourcemaps with an empty `sources[0]` instead of dropping them

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: don't treat declaration tags as parts inside each blocks

@ -1,5 +1,39 @@
# svelte
## 5.56.6
### Patch Changes
- perf: skip unnecessary blocker analysis when compiling components without top-level await ([#18548](https://github.com/sveltejs/svelte/pull/18548))
- fix: rerun derived that had an abort controller on reconnection ([#18551](https://github.com/sveltejs/svelte/pull/18551))
## 5.56.5
### Patch Changes
- chore: drop dead code that make TSGO fail ([#18496](https://github.com/sveltejs/svelte/pull/18496))
- fix: don't (re)connect deriveds when read inside branch/root effects ([#18527](https://github.com/sveltejs/svelte/pull/18527))
- fix: skip unnecessary derived effect in earlier batch ([#18525](https://github.com/sveltejs/svelte/pull/18525))
- fix: avoid declaration tag warning in event handlers ([#18500](https://github.com/sveltejs/svelte/pull/18500))
- fix: abort deriveds own AbortSignal when it disconnects ([#18400](https://github.com/sveltejs/svelte/pull/18400))
- fix: ensure `$state.eager()` is correctly transormed for SSR output ([#18530](https://github.com/sveltejs/svelte/pull/18530))
- fix: correctly transform declaration tags during SSR ([#18492](https://github.com/sveltejs/svelte/pull/18492))
- fix: transform computed keys in keyed `{#each}` destructuring patterns ([#18521](https://github.com/sveltejs/svelte/pull/18521))
- fix: chain preprocessor sourcemaps with an empty `sources[0]` instead of dropping them ([#18518](https://github.com/sveltejs/svelte/pull/18518))
- fix: clear previous_task reference after abort in Tween to prevent memory leak on interrupted tweens ([#18541](https://github.com/sveltejs/svelte/pull/18541))
- fix: don't treat declaration tags as parts inside each blocks ([#18507](https://github.com/sveltejs/svelte/pull/18507))
## 5.56.4
### Patch Changes

@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
"version": "5.56.4",
"version": "5.56.6",
"type": "module",
"types": "./types/index.d.ts",
"engines": {

@ -1221,6 +1221,10 @@ function calculate_blockers(instance, analysis) {
}
}
// With no top-level await, no binding can have a blocker and function tracing
// cannot affect the output.
if (!awaited) return;
flush_sync_group();
for (const fn of functions) {

@ -46,7 +46,7 @@ export function CallExpression(node, context) {
}
if (rune === '$state.eager') {
return node.arguments[0];
return context.visit(node.arguments[0]);
}
if (rune === '$state.snapshot') {

@ -27,6 +27,7 @@ import {
skipped_deps,
new_deps
} from '../runtime.js';
import { without_reactive_context } from '../dom/elements/bindings/shared.js';
import { equals, safe_equals } from './equality.js';
import * as e from '../errors.js';
import * as w from '../warnings.js';
@ -454,14 +455,18 @@ export function freeze_derived_effects(derived) {
// if the effect has a teardown function or abort signal, call it
if (e.teardown || e.ac) {
e.teardown?.();
e.ac?.abort(STALE_REACTION);
if (e.ac !== null) {
without_reactive_context(() => {
/** @type {AbortController} */ (e.ac).abort(STALE_REACTION);
e.ac = null;
});
}
// make it a noop so it doesn't get called again if the derived
// is unfrozen. we don't set it to `null`, because the existence
// of a teardown function is what determines whether the
// effect runs again during unfreezing (but not for teardown-only effects)
if (e.fn !== null) e.teardown = noop;
e.ac = null;
remove_reactions(e, 0);
destroy_effect_children(e);

@ -424,6 +424,16 @@ function remove_reaction(signal, dependency) {
update_derived_status(derived);
}
// Call abort controller, noone's listening to this derived anymore
if (derived.ac !== null) {
without_reactive_context(() => {
/** @type {AbortController} */ (derived.ac).abort(STALE_REACTION);
derived.ac = null;
// ensure it reruns right away next time instead of potentially returning a rejected promise as its value
set_signal_status(derived, DIRTY);
});
}
// freeze any effects inside this derived
freeze_derived_effects(derived);

@ -275,6 +275,7 @@ export class Tween {
}
previous_task?.abort();
previous_task = null;
}
const elapsed = now - start;

@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
export const VERSION = '5.56.4';
export const VERSION = '5.56.6';
export const PUBLIC_VERSION = '5';

@ -0,0 +1,20 @@
<script lang="ts">
import { getAbortSignal } from "svelte";
let { count, aborted = $bindable() } = $props()
let der = $derived.by(()=>{
const signal = getAbortSignal();
signal.addEventListener("abort", () => {
try {
aborted++;
} catch(e) {
console.error(e);
}
});
return count;
})
</script>
{der}

@ -0,0 +1,14 @@
import { ok, test } from '../../test';
import { flushSync } from 'svelte';
export default test({
async test({ assert, target, errors }) {
const btn = target.querySelector('button');
flushSync(() => {
btn?.click();
});
assert.htmlEqual(target.innerHTML, '1 <button>increment</button>');
assert.deepEqual(errors, []);
}
});

@ -0,0 +1,14 @@
<script lang="ts">
import Child from './Child.svelte'
let count = $state(0);
let aborted = $state(0)
</script>
{aborted}
<button onclick={() => count++}>increment</button>
{#if count % 2 === 0}
<Child {count} bind:aborted={aborted} />
{/if}

@ -0,0 +1,32 @@
import { test } from '../../test';
import { tick } from 'svelte';
export default test({
async test({ assert, target }) {
const [increment, toggle, resolve] = target.querySelectorAll('button');
const [div] = target.querySelectorAll('div');
assert.htmlEqual(div.innerHTML, 'loading');
resolve.click();
await tick();
assert.htmlEqual(div.innerHTML, '0');
increment.click();
await tick();
assert.htmlEqual(div.innerHTML, 'loading');
toggle.click();
await tick();
assert.htmlEqual(div.innerHTML, '');
toggle.click();
await tick();
assert.htmlEqual(div.innerHTML, 'loading');
resolve.click(); // this one's for clearing the obsolete/aborted one from the queue
await tick();
resolve.click();
await tick();
assert.htmlEqual(div.innerHTML, '2');
}
});

@ -0,0 +1,32 @@
<script>
import { getAbortSignal } from 'svelte'
let show = $state(true)
let count = $state(0);
let queued = [];
export function sleep(value, signal) {
return new Promise((resolve, reject) => {
signal.addEventListener('abort', reject, { once: true });
queued.push(() => resolve(value));
});
}
const double = $derived(sleep(count * 2, getAbortSignal()));
</script>
<button onclick={() => count += 1}>clicks: {count}</button>
<button onclick={() => show = !show}>toggle</button>
<button onclick={() => queued.shift()?.()}>resolve</button>
<div>
{#if show}
{#await double}
loading
{:then value}
{value}
{:catch}
error
{/await}
{/if}
</div>

@ -4,9 +4,11 @@ import { flushSync } from 'svelte';
export default test({
async test({ assert, target, errors }) {
const btn = target.querySelector('button');
flushSync(() => {
btn?.click();
});
assert.htmlEqual(target.innerHTML, '1:1 <button></button>');
assert.deepEqual(errors, []);
}
});

@ -9,9 +9,9 @@
const signal = getAbortSignal();
signal.addEventListener("abort", () => {
try{
try {
aborted++;
}catch(e){
} catch(e) {
console.error(e);
}
});
@ -19,6 +19,6 @@
})
</script>
{der}
{der}:{aborted}
<button onclick={() => count++}></button>
<button onclick={() => count++}></button>

@ -0,0 +1,42 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
skip: true, // TODO fix
async test({ assert, target, logs }) {
await tick();
const [a, b, log, resolve] = target.querySelectorAll('button');
const [p] = target.querySelectorAll('p');
a.click();
await tick();
assert.htmlEqual(p.innerHTML, '0 0 0');
b.click();
await tick();
assert.htmlEqual(p.innerHTML, '0 0 0');
log.click();
await tick();
assert.deepEqual(logs, [0, 2]);
resolve.click();
await tick();
assert.htmlEqual(p.innerHTML, '1 0 1');
assert.deepEqual(logs, [0, 2, 1]);
log.click();
await tick();
assert.deepEqual(logs, [0, 2, 1, 2]);
resolve.click();
await tick();
assert.htmlEqual(p.innerHTML, '1 1 2');
assert.deepEqual(logs, [0, 2, 1, 2, 2]);
log.click();
await tick();
assert.deepEqual(logs, [0, 2, 1, 2, 2, 2]);
}
});

@ -0,0 +1,23 @@
<script>
let a = $state(0);
let b = $state(0);
let d = $derived(a + b);
let queued = [];
function push(v) {
if (!v) return v;
return new Promise((resolve) => {
queued.push(() => resolve(v));
});
}
$effect(() => console.log(d));
</script>
<button onclick={() => a++}>a++</button>
<button onclick={() => b++}>b++</button>
<button onclick={() => console.log(d)}>log d</button>
<button onclick={() => queued.shift()?.()}>resolve</button>
<p>{await push(a)} {await push(b)} {d}</p>

@ -0,0 +1,35 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
skip: true, // TODO fix
async test({ assert, target, logs }) {
await tick();
const [a, b, log, resolve] = target.querySelectorAll('button');
const [div] = target.querySelectorAll('div');
assert.deepEqual(logs, ['e1 0', 'e2 0']);
logs.length = 0;
a.click();
await tick();
assert.htmlEqual(div.innerHTML, '<p>0</p> <p>0</p> <p>0</p>');
b.click();
await tick();
assert.htmlEqual(div.innerHTML, '<p>0</p> <p>1</p> <p>1</p>');
log.click();
await tick();
assert.deepEqual(logs, ['e1 1', 'e2 1', 'runs 2']); // ideally it's only 2 runs, one or two more would also be acceptable but not the 8 that it's today
logs.length = 0;
resolve.click();
await tick();
log.click();
await tick();
assert.htmlEqual(div.innerHTML, '<p>1</p> <p>2</p> <p>2</p>');
assert.deepEqual(logs, ['e1 2', 'e2 2', 'runs 3']);
}
});

@ -0,0 +1,29 @@
<script>
let a = $state(0);
let b = $state(0);
let runs = 0;
let d = $derived((runs++, a + b));
const queued = [];
function push(v) {
if (!v) return v;
return new Promise((resolve) => {
queued.push(() => resolve(v));
});
}
$effect(() => console.log('e1 ' + d));
$effect(() => console.log('e2 ' + d));
</script>
<button onclick={() => a++}>a++</button>
<button onclick={() => b++}>b++</button>
<button onclick={() => console.log('runs ' + runs)}>log runs</button>
<button onclick={() => queued.shift()?.()}>resolve</button>
<div>
<p>{await push(a)}</p>
<p>{d}</p>
<p>{d}</p>
</div>

@ -0,0 +1,28 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
skip: true, // TODO fix
async test({ assert, target, logs }) {
await tick();
const [a, b, resolve] = target.querySelectorAll('button');
const [p] = target.querySelectorAll('p');
assert.deepEqual(logs, ['b: 0, d: 0']);
logs.length = 0;
a.click();
await tick();
b.click();
await tick();
assert.htmlEqual(p.innerHTML, '0 1');
assert.deepEqual(logs, ['b: 1, d: 0']);
logs.length = 0;
resolve.click();
await tick();
assert.htmlEqual(p.innerHTML, '2 1');
assert.deepEqual(logs, ['b: 1, d: 2']);
}
});

@ -0,0 +1,22 @@
<script>
let a = $state(0);
let b = $state(0);
let d = $derived(a * 2);
const queued = [];
function push(v) {
if (!v) return v;
return new Promise((resolve) => {
queued.push(() => resolve(v));
});
}
$effect(() => console.log(`b: ${b}, d: ${d}`));
</script>
<button onclick={() => a++}>a++</button>
<button onclick={() => b++}>b++</button>
<button onclick={() => queued.shift()?.()}>resolve</button>
<p>{await push(d)} {b}</p>

@ -0,0 +1,7 @@
<script>
let props = $props();
const value = $derived(props.number ?? 0);
</script>
<div>value={value}</div>
<div>eager={$state.eager(value)}</div>
Loading…
Cancel
Save