This release fixes a security issue where HTTP requests to esbuild's
local development server could traverse outside of the serve directory
on Windows using a \ backslash character. It happened due
to the use of Go's path.Clean() function, which only
handles Unix-style / characters. HTTP requests with paths
containing \ are no longer allowed.
The previous release of esbuild added integrity checks to esbuild's
npm install script. This release also adds integrity checks to esbuild's
Deno install script. Now esbuild's Deno API will also fail with an error
if the downloaded esbuild binary contains something other than the
expected content.
Note that esbuild's Deno API installs from
registry.npmjs.org by default, but allows the
NPM_CONFIG_REGISTRY environment variable to override this
with a custom package registry. This change means that the esbuild
executable served by NPM_CONFIG_REGISTRY must now match the
expected content.
Avoid inlining using and await using
declarations (#4482)
Previously esbuild's minifier sometimes incorrectly inlined
using and await using declarations into
subsequent uses of that declaration, which then fails to dispose of the
resource correctly. This bug happened because inlining was done for
let and const declarations by avoiding doing
it for var declarations, which no longer worked when more
declaration types were added. Here's an example:
// Original code
{
using x = new Resource()
x.activate()
}
// Old output (with --minify)
new Resource().activate();
// New output (with --minify)
{using e=new Resource;e.activate()}
Fix module evaluation when an error is thrown (#4461,
#4467)
If an error is thrown during module evaluation, esbuild previously
didn't preserve the state of the module for subsequent module
references. This was observable if import() or
require() is used to import a module multiple times. The
thrown error is supposed to be thrown by every call to
import() or require(), not just the first.
With this release, esbuild will now throw the same error every time you
call import() or require() on a module that
throws during its evaluation.
Fix some edge cases around the new operator (#4477)
Previously esbuild incorrectly printed certain edge cases involving
complex expressions inside the target of a new expression
(specifically an optional chain and/or a tagged template literal). The
generated code for the new target was not correctly wrapped
with parentheses, and either contained a syntax error or had different
semantics. These edge cases have been fixed so that they now correctly
wrap the new target in parentheses. Here is an example of
some affected code:
// Original code
new (foo()`bar`)()
new (foo()?.bar)()
This changelog documents all esbuild versions published in the year
2025 (versions 0.25.0 through 0.27.2).
0.27.2
Allow import path specifiers starting with #/ (#4361)
Previously the specification for package.json disallowed
import path specifiers starting with #/, but this
restriction has recently
been relaxed and support for it is being added across the JavaScript
ecosystem. One use case is using it for a wildcard pattern such as
mapping #/* to ./src/* (previously you had to
use another character such as #_* instead, which was more
confusing). There is some more context in nodejs/node#49182.
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/each-keyed-computed-destructuring-key/main.svelte b/packages/svelte/tests/runtime-runes/samples/each-keyed-computed-destructuring-key/main.svelte
new file mode 100644
index 0000000000..6885a6918e
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/each-keyed-computed-destructuring-key/main.svelte
@@ -0,0 +1,12 @@
+
+
+
+
From af6f1d309b0fc7a9dcde7099b5f231078c9f5738 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Tue, 14 Jul 2026 00:06:53 +0200
Subject: [PATCH 17/33] chore: run every benchmark in its own process (#18542)
While benchmarking it became apparent that when running the full `pnpm
bench:compare` suite results could be wildly different compared to only
comparing single benchmarks. The reason (most likely) is that the
heap/GC/JIT state from one benchmark contaminates the others.
Therefore this adjusts the runners such that each benchmark entry is run
in its own forked process, not just each branch. This should give
better, more stable results.
Also a little fix to compare results by name; I made the mistake of
adding a new benchmark to main which wasn't present on other branches
yet, which made the results really confusing.
---
benchmarking/compare/generate-report.js | 33 +++-
benchmarking/compare/runner.js | 73 +++++++--
benchmarking/run.js | 203 +++++++++++++++---------
3 files changed, 220 insertions(+), 89 deletions(-)
diff --git a/benchmarking/compare/generate-report.js b/benchmarking/compare/generate-report.js
index a61f58909b..a06aaf9fc2 100644
--- a/benchmarking/compare/generate-report.js
+++ b/benchmarking/compare/generate-report.js
@@ -33,11 +33,38 @@ export function generate_report(outdir) {
write('');
- for (let i = 0; i < results[0].length; i += 1) {
- write(`${results[0][i].benchmark}`);
+ // match results by benchmark name — branches may have different benchmark
+ // lists (e.g. a benchmark that only exists on one of the branches), so
+ // pairing by array index would misattribute results
+ const by_name = results.map((result) => new Map(result.map((r) => [r.benchmark, r])));
+
+ /** @type {string[]} */
+ const names = [];
+
+ for (const result of results) {
+ for (const { benchmark } of result) {
+ if (!names.includes(benchmark)) {
+ names.push(benchmark);
+ }
+ }
+ }
+
+ for (const name of names) {
+ const entries = by_name.map((map) => map.get(name));
+ const missing = entries
+ .map((entry, b) => (entry === undefined ? branches[b] : null))
+ .filter((branch) => branch !== null);
+
+ write(`${name}`);
+
+ if (missing.length > 0) {
+ write(` skipped (missing on ${missing.join(', ')})`);
+ write('');
+ continue;
+ }
for (const metric of ['time', 'gc_time']) {
- const times = results.map((result) => +result[i][metric]);
+ const times = entries.map((entry) => +entry[metric]);
let min = Infinity;
let max = -Infinity;
let min_index = -1;
diff --git a/benchmarking/compare/runner.js b/benchmarking/compare/runner.js
index 31a8e6b44b..fa746f9869 100644
--- a/benchmarking/compare/runner.js
+++ b/benchmarking/compare/runner.js
@@ -1,18 +1,67 @@
+import { fork } from 'node:child_process';
+import { fileURLToPath } from 'node:url';
import { reactivity_benchmarks } from '../benchmarks/reactivity/index.js';
import { with_cpu_profile } from '../utils.js';
-const results = [];
-const PROFILE_DIR = process.env.BENCH_PROFILE_DIR;
+const PROFILE_DIR = process.env.BENCH_PROFILE_DIR ?? null;
+const single = process.env.BENCH_SINGLE;
-for (let i = 0; i < reactivity_benchmarks.length; i += 1) {
- const benchmark = reactivity_benchmarks[i];
+if (single) {
+ // child mode — run a single benchmark and report the result to the parent
+ const benchmark = reactivity_benchmarks.find((b) => b.label === single);
- process.stderr.write(`Running ${i + 1}/${reactivity_benchmarks.length} ${benchmark.label} `);
- results.push({
- benchmark: benchmark.label,
- ...(await with_cpu_profile(PROFILE_DIR, benchmark.label, () => benchmark.fn()))
- });
- process.stderr.write('\x1b[2K\r');
-}
+ if (!benchmark) {
+ throw new Error(`Unknown benchmark ${single}`);
+ }
+
+ const result = await with_cpu_profile(PROFILE_DIR, benchmark.label, () => benchmark.fn());
+
+ // exit via the callback so the message is guaranteed to be delivered
+ /** @type {NodeJS.Process} */ (process).send(result, () => process.exit(0));
+} else {
+ // parent mode — run every benchmark in its own child process, so that
+ // heap/GC/JIT state from one benchmark cannot contaminate the others
+ const filename = fileURLToPath(import.meta.url);
+ const results = [];
+
+ for (let i = 0; i < reactivity_benchmarks.length; i += 1) {
+ const benchmark = reactivity_benchmarks[i];
+
+ process.stderr.write(`Running ${i + 1}/${reactivity_benchmarks.length} ${benchmark.label} `);
+
+ const result = await new Promise((fulfil, reject) => {
+ const child = fork(filename, [], {
+ env: {
+ ...process.env,
+ BENCH_SINGLE: benchmark.label
+ }
+ });
+
+ /** @type {object | null} */
+ let message_received = null;
-process.send(results);
+ child.on('message', (message) => {
+ message_received = /** @type {object} */ (message);
+ });
+
+ child.on('error', reject);
+
+ child.on('exit', (code) => {
+ if (message_received === null) {
+ reject(new Error(`benchmark ${benchmark.label} exited with code ${code}`));
+ } else {
+ fulfil(message_received);
+ }
+ });
+ });
+
+ results.push({
+ benchmark: benchmark.label,
+ .../** @type {object} */ (result)
+ });
+
+ process.stderr.write('\x1b[2K\r');
+ }
+
+ /** @type {NodeJS.Process} */ (process).send(results);
+}
diff --git a/benchmarking/run.js b/benchmarking/run.js
index 80e40a5ff1..e44e816dd0 100644
--- a/benchmarking/run.js
+++ b/benchmarking/run.js
@@ -1,94 +1,149 @@
+import { fork } from 'node:child_process';
+import { fileURLToPath } from 'node:url';
import * as $ from '../packages/svelte/src/internal/client/index.js';
import { reactivity_benchmarks } from './benchmarks/reactivity/index.js';
import { ssr_benchmarks } from './benchmarks/ssr/index.js';
import { with_cpu_profile } from './utils.js';
-// e.g. `pnpm bench kairo` to only run the kairo benchmarks
-const filters = process.argv.slice(2);
-
const PROFILE_DIR = './benchmarking/.profiles';
-const suites = [
- {
- benchmarks: reactivity_benchmarks.filter(
- (b) => filters.length === 0 || filters.some((f) => b.label.includes(f))
- ),
- name: 'reactivity benchmarks'
- },
- {
- benchmarks: ssr_benchmarks.filter(
- (b) => filters.length === 0 || filters.some((f) => b.label.includes(f))
- ),
- name: 'server-side rendering benchmarks'
- }
-].filter((suite) => suite.benchmarks.length > 0);
-
-if (suites.length === 0) {
- console.log('No benchmarks matched provided filters');
- process.exit(1);
-}
-
-const COLUMN_WIDTHS = [25, 9, 9];
-const TOTAL_WIDTH = COLUMN_WIDTHS.reduce((a, b) => a + b);
-
-const pad_right = (str, n) => str + ' '.repeat(n - str.length);
-const pad_left = (str, n) => ' '.repeat(n - str.length) + str;
+const single = process.env.BENCH_SINGLE;
-let total_time = 0;
-let total_gc_time = 0;
+if (single) {
+ // child mode — run a single benchmark and report the result to the parent
+ const benchmark = [...reactivity_benchmarks, ...ssr_benchmarks].find((b) => b.label === single);
-$.push({}, true);
+ if (!benchmark) {
+ throw new Error(`Unknown benchmark ${single}`);
+ }
-try {
- for (const { benchmarks, name } of suites) {
- let suite_time = 0;
- let suite_gc_time = 0;
+ $.push({}, true);
+
+ const result = await with_cpu_profile(PROFILE_DIR, benchmark.label, () => benchmark.fn());
+
+ $.pop();
+
+ // exit via the callback so the message is guaranteed to be delivered
+ /** @type {NodeJS.Process} */ (process).send(result, () => process.exit(0));
+} else {
+ // parent mode — run every benchmark in its own child process, so that
+ // heap/GC/JIT state from one benchmark cannot contaminate the others
+
+ // e.g. `pnpm bench kairo` to only run the kairo benchmarks
+ const filters = process.argv.slice(2);
+
+ const suites = [
+ {
+ benchmarks: reactivity_benchmarks.filter(
+ (b) => filters.length === 0 || filters.some((f) => b.label.includes(f))
+ ),
+ name: 'reactivity benchmarks'
+ },
+ {
+ benchmarks: ssr_benchmarks.filter(
+ (b) => filters.length === 0 || filters.some((f) => b.label.includes(f))
+ ),
+ name: 'server-side rendering benchmarks'
+ }
+ ].filter((suite) => suite.benchmarks.length > 0);
- console.log(`\nRunning ${name}...\n`);
- console.log(
- pad_right('Benchmark', COLUMN_WIDTHS[0]) +
- pad_left('Time', COLUMN_WIDTHS[1]) +
- pad_left('GC time', COLUMN_WIDTHS[2])
- );
- console.log('='.repeat(TOTAL_WIDTH));
+ if (suites.length === 0) {
+ console.log('No benchmarks matched provided filters');
+ process.exit(1);
+ }
- for (const benchmark of benchmarks) {
- const results = await with_cpu_profile(PROFILE_DIR, benchmark.label, () => benchmark.fn());
+ const filename = fileURLToPath(import.meta.url);
+
+ /**
+ * @param {string} label
+ * @returns {Promise<{ time: number, gc_time: number }>}
+ */
+ const run_benchmark = (label) => {
+ return new Promise((fulfil, reject) => {
+ const child = fork(filename, [], {
+ env: {
+ ...process.env,
+ BENCH_SINGLE: label
+ }
+ });
+
+ /** @type {{ time: number, gc_time: number } | null} */
+ let result = null;
+
+ child.on('message', (message) => {
+ result = /** @type {{ time: number, gc_time: number }} */ (message);
+ });
+
+ child.on('error', reject);
+
+ child.on('exit', (code) => {
+ if (result === null) {
+ reject(new Error(`benchmark ${label} exited with code ${code}`));
+ } else {
+ fulfil(result);
+ }
+ });
+ });
+ };
+
+ const COLUMN_WIDTHS = [25, 9, 9];
+ const TOTAL_WIDTH = COLUMN_WIDTHS.reduce((a, b) => a + b);
+
+ /** @type {(str: string, n: number) => string} */
+ const pad_right = (str, n) => str + ' '.repeat(n - str.length);
+ /** @type {(str: string, n: number) => string} */
+ const pad_left = (str, n) => ' '.repeat(n - str.length) + str;
+
+ let total_time = 0;
+ let total_gc_time = 0;
+
+ try {
+ for (const { benchmarks, name } of suites) {
+ let suite_time = 0;
+ let suite_gc_time = 0;
+
+ console.log(`\nRunning ${name}...\n`);
+ console.log(
+ pad_right('Benchmark', COLUMN_WIDTHS[0]) +
+ pad_left('Time', COLUMN_WIDTHS[1]) +
+ pad_left('GC time', COLUMN_WIDTHS[2])
+ );
+ console.log('='.repeat(TOTAL_WIDTH));
+
+ for (const benchmark of benchmarks) {
+ const results = await run_benchmark(benchmark.label);
+ console.log(
+ pad_right(benchmark.label, COLUMN_WIDTHS[0]) +
+ pad_left(results.time.toFixed(2), COLUMN_WIDTHS[1]) +
+ pad_left(results.gc_time.toFixed(2), COLUMN_WIDTHS[2])
+ );
+ total_time += results.time;
+ total_gc_time += results.gc_time;
+ suite_time += results.time;
+ suite_gc_time += results.gc_time;
+ }
+
+ console.log('='.repeat(TOTAL_WIDTH));
console.log(
- pad_right(benchmark.label, COLUMN_WIDTHS[0]) +
- pad_left(results.time.toFixed(2), COLUMN_WIDTHS[1]) +
- pad_left(results.gc_time.toFixed(2), COLUMN_WIDTHS[2])
+ pad_right('suite', COLUMN_WIDTHS[0]) +
+ pad_left(suite_time.toFixed(2), COLUMN_WIDTHS[1]) +
+ pad_left(suite_gc_time.toFixed(2), COLUMN_WIDTHS[2])
);
- total_time += results.time;
- total_gc_time += results.gc_time;
- suite_time += results.time;
- suite_gc_time += results.gc_time;
+ console.log('='.repeat(TOTAL_WIDTH));
}
- console.log('='.repeat(TOTAL_WIDTH));
- console.log(
- pad_right('suite', COLUMN_WIDTHS[0]) +
- pad_left(suite_time.toFixed(2), COLUMN_WIDTHS[1]) +
- pad_left(suite_gc_time.toFixed(2), COLUMN_WIDTHS[2])
- );
- console.log('='.repeat(TOTAL_WIDTH));
- }
-
- if (PROFILE_DIR !== null) {
console.log(`\nCPU profiles written to ${PROFILE_DIR}`);
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error(e);
+ process.exit(1);
}
-} catch (e) {
- // eslint-disable-next-line no-console
- console.error(e);
- process.exit(1);
-}
-$.pop();
+ console.log('');
-console.log('');
-
-console.log(
- pad_right('total', COLUMN_WIDTHS[0]) +
- pad_left(total_time.toFixed(2), COLUMN_WIDTHS[1]) +
- pad_left(total_gc_time.toFixed(2), COLUMN_WIDTHS[2])
-);
+ console.log(
+ pad_right('total', COLUMN_WIDTHS[0]) +
+ pad_left(total_time.toFixed(2), COLUMN_WIDTHS[1]) +
+ pad_left(total_gc_time.toFixed(2), COLUMN_WIDTHS[2])
+ );
+}
From 4da9f74cd95e838acc56c4aa0d2e926d5f68972d Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Tue, 14 Jul 2026 00:07:24 +0200
Subject: [PATCH 18/33] chore: bench compare report as HTML (#18543)
Creates a `results.html` file which is much better to look at compared
to the terminal output.
---
.gitignore | 1 +
benchmarking/compare/generate-report.js | 46 +-
benchmarking/compare/index.js | 2 +-
benchmarking/compare/results.template.html | 741 +++++++++++++++++++++
4 files changed, 785 insertions(+), 5 deletions(-)
create mode 100644 benchmarking/compare/results.template.html
diff --git a/.gitignore b/.gitignore
index 556cae6344..e4f9f9cf87 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,4 @@ packages/svelte/scripts/_baseline/
benchmarking/.profiles
benchmarking/compare/.results
benchmarking/compare/.profiles
+benchmarking/compare/results.html
diff --git a/benchmarking/compare/generate-report.js b/benchmarking/compare/generate-report.js
index a06aaf9fc2..70f7b30fbb 100644
--- a/benchmarking/compare/generate-report.js
+++ b/benchmarking/compare/generate-report.js
@@ -2,15 +2,27 @@ import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
-export function generate_report(outdir) {
+const REPORT_DATA_PLACEHOLDER = '%%REPORT_DATA%%';
+const report_template = fs.readFileSync(
+ new URL('./results.template.html', import.meta.url),
+ 'utf-8'
+);
+
+if (!report_template.includes(REPORT_DATA_PLACEHOLDER)) {
+ throw new Error(`Missing ${REPORT_DATA_PLACEHOLDER} in results.template.html`);
+}
+
+export function generate_report(outdir, branches) {
const result_files = fs
.readdirSync(outdir)
- .filter((file) => file.endsWith('.json'))
+ .filter((file) => file.endsWith('.json') && (!branches || branches.includes(file.slice(0, -5))))
.sort((a, b) => a.localeCompare(b));
- const branches = result_files.map((file) => file.slice(0, -5));
+ // always do this so that ordering lines up (branches argument might be passed in a different order than the result files are sorted
+ branches = result_files.map((file) => file.slice(0, -5));
+
const results = result_files.map((file) =>
- JSON.parse(fs.readFileSync(`${outdir}/${file}`, 'utf-8'))
+ JSON.parse(fs.readFileSync(path.join(outdir, file), 'utf-8'))
);
if (results.length === 0) {
@@ -95,6 +107,32 @@ export function generate_report(outdir) {
write('');
}
+
+ const benchmarks = names.map((name) => ({
+ name,
+ values: by_name.map((map) => {
+ const entry = map.get(name);
+
+ if (entry === undefined) return null;
+
+ return {
+ time: Number(entry.time),
+ gc_time: Number(entry.gc_time)
+ };
+ })
+ }));
+ const data = JSON.stringify({
+ generated_at: new Date().toISOString(),
+ branches,
+ benchmarks
+ })
+ .replaceAll('<', '\\u003c')
+ .replaceAll('\u2028', '\\u2028')
+ .replaceAll('\u2029', '\\u2029');
+ const html_file = path.resolve(outdir, '../results.html');
+
+ fs.writeFileSync(html_file, report_template.replace(REPORT_DATA_PLACEHOLDER, data));
+ console.log(`\nHTML report written to ${html_file}`);
}
function char(i) {
diff --git a/benchmarking/compare/index.js b/benchmarking/compare/index.js
index 9064ee7da9..2e76f46e1b 100644
--- a/benchmarking/compare/index.js
+++ b/benchmarking/compare/index.js
@@ -85,4 +85,4 @@ if (PROFILE_DIR !== null) {
console.log(`\nCPU profiles written to ${PROFILE_DIR}`);
}
-generate_report(outdir);
+generate_report(outdir, requested_branches);
diff --git a/benchmarking/compare/results.template.html b/benchmarking/compare/results.template.html
new file mode 100644
index 0000000000..02f208ac4f
--- /dev/null
+++ b/benchmarking/compare/results.template.html
@@ -0,0 +1,741 @@
+
+
+
+
+
+ Benchmark comparison
+
+
+
+
+
Benchmark comparison
+
+ Runtime results across branches. Green cells are fastest for an entry and red cells expose
+ the largest regressions. Overall runtime normalizes every benchmark to its fastest result
+ before averaging, so long-running entries do not outweigh short ones.
+
+
+
+
+
+
+
+
Branch standings
+
+ Wins count the fastest branch for each comparable entry. Normalized runtime is the
+ average slowdown against each entry's fastest result; lower is better. Click a heading
+ to sort.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Results by benchmark
+
+ Each cell shows runtime, difference from the winner, and GC time. Missing entries are
+ excluded from both standings.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From a91b0687866c8b2f78692010702bb811bf27b9a8 Mon Sep 17 00:00:00 2001
From: Fedor Nezhivoi
Date: Tue, 14 Jul 2026 16:40:30 +0700
Subject: [PATCH 19/33] fix: abort deriveds own AbortSignal when it disconnects
(#18400)
Fixes https://github.com/sveltejs/svelte/issues/18301
---------
Co-authored-by: Fedor Nezhivoi
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Co-authored-by: Simon Holthausen
---
.changeset/early-crabs-rest.md | 5 +++++
.../internal/client/reactivity/deriveds.js | 9 +++++++--
.../svelte/src/internal/client/runtime.js | 8 ++++++++
.../abort-signal-derived-destroy/Child.svelte | 20 +++++++++++++++++++
.../abort-signal-derived-destroy/_config.js | 14 +++++++++++++
.../abort-signal-derived-destroy/main.svelte | 14 +++++++++++++
.../abort-signal-derived-set-state/_config.js | 2 ++
.../main.svelte | 8 ++++----
8 files changed, 74 insertions(+), 6 deletions(-)
create mode 100644 .changeset/early-crabs-rest.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/Child.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/main.svelte
diff --git a/.changeset/early-crabs-rest.md b/.changeset/early-crabs-rest.md
new file mode 100644
index 0000000000..d3f290a5c2
--- /dev/null
+++ b/.changeset/early-crabs-rest.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: abort deriveds own AbortSignal when it disconnects
diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js
index fd64f3b45d..6b48bc8a23 100644
--- a/packages/svelte/src/internal/client/reactivity/deriveds.js
+++ b/packages/svelte/src/internal/client/reactivity/deriveds.js
@@ -28,6 +28,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';
@@ -450,14 +451,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);
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index 188d16a820..2ebce07916 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -408,6 +408,14 @@ 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;
+ });
+ }
+
// freeze any effects inside this derived
freeze_derived_effects(derived);
diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/Child.svelte b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/Child.svelte
new file mode 100644
index 0000000000..5a837e5ea4
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/Child.svelte
@@ -0,0 +1,20 @@
+
+
+{der}
diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/_config.js b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/_config.js
new file mode 100644
index 0000000000..3c6ec98fe6
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/_config.js
@@ -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 ');
+ assert.deepEqual(errors, []);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/main.svelte b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/main.svelte
new file mode 100644
index 0000000000..57c467a80c
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/main.svelte
@@ -0,0 +1,14 @@
+
+
+{aborted}
+
+
+
+{#if count % 2 === 0}
+
+{/if}
diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/_config.js b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/_config.js
index 2dacf188d7..aa38dd8280 100644
--- a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/_config.js
@@ -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 ');
assert.deepEqual(errors, []);
}
});
diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/main.svelte b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/main.svelte
index ebefe38fb2..e76cecbac9 100644
--- a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/main.svelte
@@ -9,9 +9,9 @@
const signal = getAbortSignal();
signal.addEventListener("abort", () => {
- try{
+ try {
aborted++;
- }catch(e){
+ } catch(e) {
console.error(e);
}
});
@@ -19,6 +19,6 @@
})
-{der}
+{der}:{aborted}
-
\ No newline at end of file
+
From edeef1f9f713ab1ffdba698ff6d92cce8da1c5b4 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Tue, 14 Jul 2026 11:41:20 +0200
Subject: [PATCH 20/33] chore: add some failing tests (#18528)
During my explorations I collected these new tests which are currently
failing. Two of them work on the incremental-batches branch, all of them
(adjusted for new behavior) pass on my uncommitted entangle batches
branch, and all of them also pass on another uncommitted experimental
branch of overlaying deriveds which will likely not land.
---
.../_config.js | 42 +++++++++++++++++++
.../main.svelte | 23 ++++++++++
.../async-derived-not-overfiring/_config.js | 35 ++++++++++++++++
.../async-derived-not-overfiring/main.svelte | 29 +++++++++++++
.../async-derived-not-underfiring/_config.js | 28 +++++++++++++
.../async-derived-not-underfiring/main.svelte | 22 ++++++++++
6 files changed, 179 insertions(+)
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/main.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring/main.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-derived-not-underfiring/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-derived-not-underfiring/main.svelte
diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/_config.js b/packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/_config.js
new file mode 100644
index 0000000000..1a7ed0eb92
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/_config.js
@@ -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]);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/main.svelte
new file mode 100644
index 0000000000..bf543ba595
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/main.svelte
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
');
+
+ 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, '
From 199ffebca375b29b65fcac53fc1825b452175a53 Mon Sep 17 00:00:00 2001
From: prashantg-pixi
Date: Tue, 14 Jul 2026 15:21:13 +0530
Subject: [PATCH 21/33] fix: clear previous_task reference after abort in Tween
(#18541)
avoids memory leaks
---
.changeset/tween-previous-task-leak.md | 5 +++++
packages/svelte/src/motion/tweened.js | 1 +
2 files changed, 6 insertions(+)
create mode 100644 .changeset/tween-previous-task-leak.md
diff --git a/.changeset/tween-previous-task-leak.md b/.changeset/tween-previous-task-leak.md
new file mode 100644
index 0000000000..6208e7c87a
--- /dev/null
+++ b/.changeset/tween-previous-task-leak.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+fix: clear previous_task reference after abort in Tween to prevent memory leak on interrupted tweens
diff --git a/packages/svelte/src/motion/tweened.js b/packages/svelte/src/motion/tweened.js
index a24148d075..460db0df64 100644
--- a/packages/svelte/src/motion/tweened.js
+++ b/packages/svelte/src/motion/tweened.js
@@ -275,6 +275,7 @@ export class Tween {
}
previous_task?.abort();
+ previous_task = null;
}
const elapsed = now - start;
From a4fd67e361914df612ae432ff75283c2d332ae18 Mon Sep 17 00:00:00 2001
From: adiGuba
Date: Tue, 14 Jul 2026 11:54:13 +0200
Subject: [PATCH 22/33] fix: $state.eager() is sometimes incorrect in SSR
(#18530)
Fix #18529
A simple fix : `context.visit()` was missing on the argument of
`$state.eager()`, so the generated code can be incorrect in some case :
---
.changeset/long-buttons-hunt.md | 5 +++++
.../phases/3-transform/server/visitors/CallExpression.js | 2 +-
.../samples/state-eager/_expected.html | 2 ++
.../server-side-rendering/samples/state-eager/main.svelte | 7 +++++++
4 files changed, 15 insertions(+), 1 deletion(-)
create mode 100644 .changeset/long-buttons-hunt.md
create mode 100644 packages/svelte/tests/server-side-rendering/samples/state-eager/_expected.html
create mode 100644 packages/svelte/tests/server-side-rendering/samples/state-eager/main.svelte
diff --git a/.changeset/long-buttons-hunt.md b/.changeset/long-buttons-hunt.md
new file mode 100644
index 0000000000..68d0b60ff4
--- /dev/null
+++ b/.changeset/long-buttons-hunt.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: ensure `$state.eager()` is correctly transormed for SSR output
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js
index 8525fb6366..ac78f55da9 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js
@@ -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') {
diff --git a/packages/svelte/tests/server-side-rendering/samples/state-eager/_expected.html b/packages/svelte/tests/server-side-rendering/samples/state-eager/_expected.html
new file mode 100644
index 0000000000..9d3ea2f5b4
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/state-eager/_expected.html
@@ -0,0 +1,2 @@
+
value=0
+
eager=0
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/state-eager/main.svelte b/packages/svelte/tests/server-side-rendering/samples/state-eager/main.svelte
new file mode 100644
index 0000000000..d144914f72
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/state-eager/main.svelte
@@ -0,0 +1,7 @@
+
+
+
value={value}
+
eager={$state.eager(value)}
From 602a873b6b82bba4e6edc91b039e2f6defbe3fc4 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 14 Jul 2026 13:59:06 +0200
Subject: [PATCH 23/33] Version Packages (#18497)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.
# Releases
## svelte@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))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/big-months-shout.md | 5 -----
.changeset/common-ways-deny.md | 5 -----
.changeset/curly-wasps-hide.md | 5 -----
.changeset/dull-oranges-fry.md | 5 -----
.changeset/early-crabs-rest.md | 5 -----
.changeset/long-buttons-hunt.md | 5 -----
.changeset/rich-jokes-attack.md | 5 -----
.changeset/shiny-keys-dance.md | 5 -----
.changeset/tame-donkeys-jump.md | 5 -----
.changeset/tween-previous-task-leak.md | 5 -----
.changeset/young-doodles-beam.md | 5 -----
packages/svelte/CHANGELOG.md | 26 ++++++++++++++++++++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
14 files changed, 28 insertions(+), 57 deletions(-)
delete mode 100644 .changeset/big-months-shout.md
delete mode 100644 .changeset/common-ways-deny.md
delete mode 100644 .changeset/curly-wasps-hide.md
delete mode 100644 .changeset/dull-oranges-fry.md
delete mode 100644 .changeset/early-crabs-rest.md
delete mode 100644 .changeset/long-buttons-hunt.md
delete mode 100644 .changeset/rich-jokes-attack.md
delete mode 100644 .changeset/shiny-keys-dance.md
delete mode 100644 .changeset/tame-donkeys-jump.md
delete mode 100644 .changeset/tween-previous-task-leak.md
delete mode 100644 .changeset/young-doodles-beam.md
diff --git a/.changeset/big-months-shout.md b/.changeset/big-months-shout.md
deleted file mode 100644
index 3d8ca0045e..0000000000
--- a/.changeset/big-months-shout.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-chore: drop dead code that make TSGO fail
diff --git a/.changeset/common-ways-deny.md b/.changeset/common-ways-deny.md
deleted file mode 100644
index 75e9375965..0000000000
--- a/.changeset/common-ways-deny.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: don't (re)connect deriveds when read inside branch/root effects
diff --git a/.changeset/curly-wasps-hide.md b/.changeset/curly-wasps-hide.md
deleted file mode 100644
index 7e55d77ba0..0000000000
--- a/.changeset/curly-wasps-hide.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: skip unnecessary derived effect in earlier batch
diff --git a/.changeset/dull-oranges-fry.md b/.changeset/dull-oranges-fry.md
deleted file mode 100644
index 0efcc6fa2d..0000000000
--- a/.changeset/dull-oranges-fry.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: avoid declaration tag warning in event handlers
diff --git a/.changeset/early-crabs-rest.md b/.changeset/early-crabs-rest.md
deleted file mode 100644
index d3f290a5c2..0000000000
--- a/.changeset/early-crabs-rest.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: abort deriveds own AbortSignal when it disconnects
diff --git a/.changeset/long-buttons-hunt.md b/.changeset/long-buttons-hunt.md
deleted file mode 100644
index 68d0b60ff4..0000000000
--- a/.changeset/long-buttons-hunt.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: ensure `$state.eager()` is correctly transormed for SSR output
diff --git a/.changeset/rich-jokes-attack.md b/.changeset/rich-jokes-attack.md
deleted file mode 100644
index 46e7d2d8c8..0000000000
--- a/.changeset/rich-jokes-attack.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: correctly transform declaration tags during SSR
diff --git a/.changeset/shiny-keys-dance.md b/.changeset/shiny-keys-dance.md
deleted file mode 100644
index 20cf754212..0000000000
--- a/.changeset/shiny-keys-dance.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: transform computed keys in keyed `{#each}` destructuring patterns
diff --git a/.changeset/tame-donkeys-jump.md b/.changeset/tame-donkeys-jump.md
deleted file mode 100644
index aae38910db..0000000000
--- a/.changeset/tame-donkeys-jump.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: chain preprocessor sourcemaps with an empty `sources[0]` instead of dropping them
diff --git a/.changeset/tween-previous-task-leak.md b/.changeset/tween-previous-task-leak.md
deleted file mode 100644
index 6208e7c87a..0000000000
--- a/.changeset/tween-previous-task-leak.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"svelte": patch
----
-
-fix: clear previous_task reference after abort in Tween to prevent memory leak on interrupted tweens
diff --git a/.changeset/young-doodles-beam.md b/.changeset/young-doodles-beam.md
deleted file mode 100644
index b68f55d241..0000000000
--- a/.changeset/young-doodles-beam.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: don't treat declaration tags as parts inside each blocks
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 80b61facb0..d80da025c9 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,31 @@
# svelte
+## 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
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 34fa68c8a7..a26a3e4a76 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.56.4",
+ "version": "5.56.5",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index ef468a2f79..80b38c225a 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -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.5';
export const PUBLIC_VERSION = '5';
From 22e0adb75a07442015feb0465cee1d3a61729e90 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Thu, 16 Jul 2026 10:42:31 +0200
Subject: [PATCH 24/33] fix: rerun derived that had an abort controller on
reconnection (#18551)
Follow-up to #18400 - we need to mark a derived with an abort signal
that is frozen as dirty so it is guaranteed to rerun when it
reconnects/is re-requested. Else you could return a stale value, or
worse, you returned a promise from the derived which you aborted, and
it's now in the rejected state until you update one of its dependencies.
---
.changeset/lucky-dolls-yell.md | 5 +++
.../svelte/src/internal/client/runtime.js | 2 ++
.../_config.js | 32 +++++++++++++++++++
.../main.svelte | 32 +++++++++++++++++++
4 files changed, 71 insertions(+)
create mode 100644 .changeset/lucky-dolls-yell.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/main.svelte
diff --git a/.changeset/lucky-dolls-yell.md b/.changeset/lucky-dolls-yell.md
new file mode 100644
index 0000000000..d158f376d1
--- /dev/null
+++ b/.changeset/lucky-dolls-yell.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: rerun derived that had an abort controller on reconnection
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index 2ebce07916..4458595d35 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -413,6 +413,8 @@ function remove_reaction(signal, dependency) {
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);
});
}
diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/_config.js b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/_config.js
new file mode 100644
index 0000000000..7caf765e3b
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/_config.js
@@ -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');
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/main.svelte b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/main.svelte
new file mode 100644
index 0000000000..a047afdd44
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/main.svelte
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
From d0dbe1a48013b9c405b518511f8330baf3a27bc9 Mon Sep 17 00:00:00 2001
From: Joe Schafer
Date: Thu, 16 Jul 2026 04:35:37 -0700
Subject: [PATCH 25/33] perf: skip quadratic blocker analysis when no top-level
await (#18548)
Skip function reference tracing in `calculate_blockers` when a component
has no top-level `await`.
Blockers only represent dependencies on top-level async statements.
Without a top-level `await`, no binding can have a blocker, so tracing
every top-level function cannot affect the generated output. In large
components with many functions and transitive assignments, that
unnecessary work can become quadratic.
---
.changeset/fast-cats-compile.md | 5 +++++
packages/svelte/src/compiler/phases/2-analyze/index.js | 4 ++++
2 files changed, 9 insertions(+)
create mode 100644 .changeset/fast-cats-compile.md
diff --git a/.changeset/fast-cats-compile.md b/.changeset/fast-cats-compile.md
new file mode 100644
index 0000000000..585ce0ed1d
--- /dev/null
+++ b/.changeset/fast-cats-compile.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+perf: skip unnecessary blocker analysis when compiling components without top-level await
diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js
index ef20049697..67e9030188 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/index.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/index.js
@@ -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) {
From 4a6a85b5f149cc96514ed3bf5e59083b9246d394 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 17 Jul 2026 00:21:52 +0200
Subject: [PATCH 26/33] Version Packages (#18552)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.
# Releases
## 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))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/fast-cats-compile.md | 5 -----
.changeset/lucky-dolls-yell.md | 5 -----
packages/svelte/CHANGELOG.md | 8 ++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
5 files changed, 10 insertions(+), 12 deletions(-)
delete mode 100644 .changeset/fast-cats-compile.md
delete mode 100644 .changeset/lucky-dolls-yell.md
diff --git a/.changeset/fast-cats-compile.md b/.changeset/fast-cats-compile.md
deleted file mode 100644
index 585ce0ed1d..0000000000
--- a/.changeset/fast-cats-compile.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-perf: skip unnecessary blocker analysis when compiling components without top-level await
diff --git a/.changeset/lucky-dolls-yell.md b/.changeset/lucky-dolls-yell.md
deleted file mode 100644
index d158f376d1..0000000000
--- a/.changeset/lucky-dolls-yell.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: rerun derived that had an abort controller on reconnection
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index d80da025c9..0f26b571d3 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,13 @@
# 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
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index a26a3e4a76..855ba92a8d 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.56.5",
+ "version": "5.56.6",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 80b38c225a..05d88ef904 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
-export const VERSION = '5.56.5';
+export const VERSION = '5.56.6';
export const PUBLIC_VERSION = '5';
From b791cac54e2db43317f2bc6c8e41e694245c551e Mon Sep 17 00:00:00 2001
From: Manuel <30698007+manuel3108@users.noreply.github.com>
Date: Sat, 18 Jul 2026 14:47:30 +0200
Subject: [PATCH 27/33] chore: provide `indent` option for `print` (#18474)
Relevant for https://github.com/sveltejs/cli/pull/1138.
This is basically just an option from `esrap` that we pass through. That
will allow tools like `sv migrate` to provide a guessed indent based on
the other file contents and therefore allow us to produce way smaller
diffs. Since we are just passing an option, there is no need for a test
here.
Technically a `feat:` but i dont think this is relevant enough.
### Before submitting the PR, please make sure you do the following
- [x] It's really useful if your PR references an issue where it is
discussed ahead of time. In many cases, features are absent for a
reason. For large changes, please create an RFC:
https://github.com/sveltejs/rfcs
- [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`.
- [x] This message body should clearly illustrate what problems it
solves.
- [ ] Ideally, include a test that fails without this PR but passes with
it.
- [x] If this PR changes code within `packages/svelte/src`, add a
changeset (`npx changeset`).
### Tests and linting
- [x] Run the tests with `pnpm test` and lint the project with `pnpm
lint`
---
.changeset/short-otters-find.md | 5 +++++
packages/svelte/src/compiler/print/index.js | 5 ++++-
packages/svelte/src/compiler/print/types.d.ts | 1 +
packages/svelte/types/index.d.ts | 1 +
4 files changed, 11 insertions(+), 1 deletion(-)
create mode 100644 .changeset/short-otters-find.md
diff --git a/.changeset/short-otters-find.md b/.changeset/short-otters-find.md
new file mode 100644
index 0000000000..57194653b6
--- /dev/null
+++ b/.changeset/short-otters-find.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+chore: provide `indent` option for `print`
diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js
index fbd7e86f12..a5fbc96fc8 100644
--- a/packages/svelte/src/compiler/print/index.js
+++ b/packages/svelte/src/compiler/print/index.js
@@ -30,7 +30,10 @@ export function print(ast, options = undefined) {
}),
...svelte_visitors(comments),
...css_visitors
- })
+ }),
+ {
+ indent: options?.indent
+ }
);
}
diff --git a/packages/svelte/src/compiler/print/types.d.ts b/packages/svelte/src/compiler/print/types.d.ts
index d0ff909525..9eccebb6f3 100644
--- a/packages/svelte/src/compiler/print/types.d.ts
+++ b/packages/svelte/src/compiler/print/types.d.ts
@@ -4,4 +4,5 @@ import type ts from 'esrap/languages/ts';
export type Options = {
getLeadingComments?: NonNullable[0]>['getLeadingComments'] | undefined;
getTrailingComments?: NonNullable[0]>['getTrailingComments'] | undefined;
+ indent?: string; // default tab
};
diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts
index 5f41fabf60..d758022ae4 100644
--- a/packages/svelte/types/index.d.ts
+++ b/packages/svelte/types/index.d.ts
@@ -1854,6 +1854,7 @@ declare module 'svelte/compiler' {
type Options = {
getLeadingComments?: NonNullable[0]>['getLeadingComments'] | undefined;
getTrailingComments?: NonNullable[0]>['getTrailingComments'] | undefined;
+ indent?: string; // default tab
};
export {};
From d9e40d17cf751e22dbc3ec42b9cb50920e2d1229 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Sat, 18 Jul 2026 21:00:02 -0400
Subject: [PATCH 28/33] docs: update link on hooks page (#18564)
this docs page updated recently and broke everything
---
documentation/docs/05-special-elements/01-svelte-boundary.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/documentation/docs/05-special-elements/01-svelte-boundary.md b/documentation/docs/05-special-elements/01-svelte-boundary.md
index e1ad00a50b..41ebee1564 100644
--- a/documentation/docs/05-special-elements/01-svelte-boundary.md
+++ b/documentation/docs/05-special-elements/01-svelte-boundary.md
@@ -109,7 +109,7 @@ By default, error boundaries have no effect on the server — if an error occurs
Since 5.51 you can control this behaviour for boundaries with a `failed` snippet, by calling [`render(...)`](imperative-component-api#render) with a `transformError` function.
-> [!NOTE] If you're using Svelte via a framework such as SvelteKit, you most likely don't have direct access to the `render(...)` call — the framework must configure `transformError` on your behalf. SvelteKit will add support for this in the near future, via the [`handleError`](../kit/hooks#Shared-hooks-handleError) hook.
+> [!NOTE] If you're using Svelte via a framework such as SvelteKit, you most likely don't have direct access to the `render(...)` call — the framework must configure `transformError` on your behalf. SvelteKit will add support for this in the near future, via the [`handleError`](../kit/hooks#handleError) hook.
The `transformError` function must return a JSON-stringifiable object which will be used to render the `failed` snippet. This object will be serialized and used to hydrate the snippet in the browser:
From b29d7002ecf9bc0036b18647c0b7677a7cb0a914 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 20 Jul 2026 15:23:49 -0400
Subject: [PATCH 29/33] Version Packages (#18560)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.
# Releases
## svelte@5.56.7
### Patch Changes
- chore: provide `indent` option for `print`
([#18474](https://github.com/sveltejs/svelte/pull/18474))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/short-otters-find.md | 5 -----
packages/svelte/CHANGELOG.md | 6 ++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
4 files changed, 8 insertions(+), 7 deletions(-)
delete mode 100644 .changeset/short-otters-find.md
diff --git a/.changeset/short-otters-find.md b/.changeset/short-otters-find.md
deleted file mode 100644
index 57194653b6..0000000000
--- a/.changeset/short-otters-find.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-chore: provide `indent` option for `print`
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 0f26b571d3..a9b93f5341 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
# svelte
+## 5.56.7
+
+### Patch Changes
+
+- chore: provide `indent` option for `print` ([#18474](https://github.com/sveltejs/svelte/pull/18474))
+
## 5.56.6
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 855ba92a8d..67f349be71 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.56.6",
+ "version": "5.56.7",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 05d88ef904..e9737ec13c 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
-export const VERSION = '5.56.6';
+export const VERSION = '5.56.7';
export const PUBLIC_VERSION = '5';
From 2bace308e37ac1def958be750bd699ed302bb715 Mon Sep 17 00:00:00 2001
From: Floze <88098863+floze-the-genius@users.noreply.github.com>
Date: Wed, 22 Jul 2026 16:17:27 +0400
Subject: [PATCH 30/33] fix: preserve select selection with spread attributes
(#18561)
Fixes #18557
A `