| null} value */
-export function set_captured_signals(value) {
- captured_signals = value;
-}
export function increment_write_version() {
return ++write_version;
@@ -531,9 +524,7 @@ export function get(signal) {
var flags = signal.f;
var is_derived = (flags & DERIVED) !== 0;
- if (captured_signals !== null) {
- captured_signals.add(signal);
- }
+ captured_signals?.add(signal);
// Register the dependency on the current reaction signal.
if (active_reaction !== null && !untracking) {
@@ -713,45 +704,6 @@ export function safe_get(signal) {
return signal && get(signal);
}
-/**
- * Capture an array of all the signals that are read when `fn` is called
- * @template T
- * @param {() => T} fn
- */
-function capture_signals(fn) {
- var previous_captured_signals = captured_signals;
- captured_signals = new Set();
-
- var captured = captured_signals;
- var signal;
-
- try {
- untrack(fn);
- if (previous_captured_signals !== null) {
- for (signal of captured_signals) {
- previous_captured_signals.add(signal);
- }
- }
- } finally {
- captured_signals = previous_captured_signals;
- }
-
- return captured;
-}
-
-/**
- * Invokes a function and captures all signals that are read during the invocation,
- * then invalidates them.
- * @param {() => any} fn
- */
-export function invalidate_inner_signals(fn) {
- var captured = capture_signals(() => untrack(fn));
-
- for (var signal of captured) {
- internal_set(signal, signal.v);
- }
-}
-
/**
* When used inside a [`$derived`](https://svelte.dev/docs/svelte/$derived) or [`$effect`](https://svelte.dev/docs/svelte/$effect),
* any state read inside `fn` will not be treated as a dependency.
From 8afe5ec0c5a2e38b9b3a55b0dec94b99aa10447c Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 20 Jul 2025 17:23:10 -0400
Subject: [PATCH 13/90] Version Packages (#16453)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/angry-hornets-hug.md | 5 -----
.changeset/four-spiders-type.md | 5 -----
.changeset/silent-rockets-tease.md | 5 -----
packages/svelte/CHANGELOG.md | 10 ++++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
6 files changed, 12 insertions(+), 17 deletions(-)
delete mode 100644 .changeset/angry-hornets-hug.md
delete mode 100644 .changeset/four-spiders-type.md
delete mode 100644 .changeset/silent-rockets-tease.md
diff --git a/.changeset/angry-hornets-hug.md b/.changeset/angry-hornets-hug.md
deleted file mode 100644
index ffe59db100..0000000000
--- a/.changeset/angry-hornets-hug.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: always mark reactions of deriveds
diff --git a/.changeset/four-spiders-type.md b/.changeset/four-spiders-type.md
deleted file mode 100644
index 9a4056c50a..0000000000
--- a/.changeset/four-spiders-type.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: add labels to `@const` tags and props
diff --git a/.changeset/silent-rockets-tease.md b/.changeset/silent-rockets-tease.md
deleted file mode 100644
index 1a708c1d69..0000000000
--- a/.changeset/silent-rockets-tease.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: tag stores for `$inspect.trace()`
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index c20618af11..472a2b4a98 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,15 @@
# svelte
+## 5.36.11
+
+### Patch Changes
+
+- fix: always mark reactions of deriveds ([#16457](https://github.com/sveltejs/svelte/pull/16457))
+
+- fix: add labels to `@const` tags and props ([#16454](https://github.com/sveltejs/svelte/pull/16454))
+
+- fix: tag stores for `$inspect.trace()` ([#16452](https://github.com/sveltejs/svelte/pull/16452))
+
## 5.36.10
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 28ccb2884a..4b22911e6e 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.36.10",
+ "version": "5.36.11",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 8c59098e0c..07cf3cdf4d 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.36.10';
+export const VERSION = '5.36.11';
export const PUBLIC_VERSION = '5';
From ce4a99ed6d0b4b53c7abb7a8763e8e4bc4de5431 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 20 Jul 2025 18:11:26 -0400
Subject: [PATCH 14/90] Version Packages (#16459)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/swift-cherries-know.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/swift-cherries-know.md
diff --git a/.changeset/swift-cherries-know.md b/.changeset/swift-cherries-know.md
deleted file mode 100644
index d8bbb1256a..0000000000
--- a/.changeset/swift-cherries-know.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-chore: move `capture_signals` to legacy module
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 472a2b4a98..1234efa0d5 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
# svelte
+## 5.36.12
+
+### Patch Changes
+
+- chore: move `capture_signals` to legacy module ([#16456](https://github.com/sveltejs/svelte/pull/16456))
+
## 5.36.11
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 4b22911e6e..629ec99af8 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.36.11",
+ "version": "5.36.12",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 07cf3cdf4d..465bd73f0f 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.36.11';
+export const VERSION = '5.36.12';
export const PUBLIC_VERSION = '5';
From aabd333d89a4c9241bc3ed57752cb2616dd66b7c Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Mon, 21 Jul 2025 15:22:13 +0200
Subject: [PATCH 15/90] fix: ensure subscriptions are picked up correctly by
deriveds (#16466)
Increment the version to ensure any dependent deriveds are marked dirty when the subscription is picked up again later. If we didn't do this then the comparison of write versions would determine that the derived has a later version than the subscriber, and it would not be re-run.
Fixes #16311
Fixes #15888
---
.changeset/fresh-penguins-impress.md | 5 +++
.../src/reactivity/create-subscriber.js | 4 ++
.../samples/store-inside-derived/_config.js | 45 +++++++++++++++++++
.../samples/store-inside-derived/main.svelte | 36 +++++++++++++++
4 files changed, 90 insertions(+)
create mode 100644 .changeset/fresh-penguins-impress.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/store-inside-derived/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/store-inside-derived/main.svelte
diff --git a/.changeset/fresh-penguins-impress.md b/.changeset/fresh-penguins-impress.md
new file mode 100644
index 0000000000..35ff4f0aaa
--- /dev/null
+++ b/.changeset/fresh-penguins-impress.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: ensure subscriptions are picked up correctly by deriveds
diff --git a/packages/svelte/src/reactivity/create-subscriber.js b/packages/svelte/src/reactivity/create-subscriber.js
index 4dcac4e6f6..6f9313eb0a 100644
--- a/packages/svelte/src/reactivity/create-subscriber.js
+++ b/packages/svelte/src/reactivity/create-subscriber.js
@@ -82,6 +82,10 @@ export function createSubscriber(start) {
if (subscribers === 0) {
stop?.();
stop = undefined;
+ // Increment the version to ensure any dependent deriveds are marked dirty when the subscription is picked up again later.
+ // If we didn't do this then the comparison of write versions would determine that the derived has a later version than
+ // the subscriber, and it would not be re-run.
+ increment(version);
}
});
};
diff --git a/packages/svelte/tests/runtime-runes/samples/store-inside-derived/_config.js b/packages/svelte/tests/runtime-runes/samples/store-inside-derived/_config.js
new file mode 100644
index 0000000000..de078b1e75
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/store-inside-derived/_config.js
@@ -0,0 +1,45 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ test: ({ assert, target }) => {
+ const [loading, increment] = target.querySelectorAll('button');
+
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ $value: 0
+ valueFromStore.current: 0
+ valueDerivedCurrent: 0
+ Loading
+ Increment
+ `
+ );
+
+ loading.click();
+ flushSync();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ $value: Loading...
+ valueFromStore.current: Loading...
+ valueDerivedCurrent: Loading...
+ Loading
+ Increment
+ `
+ );
+
+ increment.click();
+ flushSync();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ $value: 1
+ valueFromStore.current: 1
+ valueDerivedCurrent: 1
+ Loading
+ Increment
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/store-inside-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/store-inside-derived/main.svelte
new file mode 100644
index 0000000000..06d0a0d4b4
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/store-inside-derived/main.svelte
@@ -0,0 +1,36 @@
+
+
+
+ $value: {isLoading ? 'Loading...' : $value}
+
+
+
+ valueFromStore.current: {isLoading ? 'Loading...' : valueFromStore.current}
+
+
+
+ valueDerivedCurrent: {isLoading ? 'Loading...' : valueDerivedCurrent}
+
+
+ {
+ isLoading = true;
+ }}>
+ Loading
+
+
+ {
+ $value++;
+ isLoading = false;
+ }}>
+ Increment
+
From 28403beaeb360fd7b4096fe7c8ef98e43d0676c2 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 21 Jul 2025 13:32:47 -0400
Subject: [PATCH 16/90] Version Packages (#16467)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/fresh-penguins-impress.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/fresh-penguins-impress.md
diff --git a/.changeset/fresh-penguins-impress.md b/.changeset/fresh-penguins-impress.md
deleted file mode 100644
index 35ff4f0aaa..0000000000
--- a/.changeset/fresh-penguins-impress.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: ensure subscriptions are picked up correctly by deriveds
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 1234efa0d5..5a5e532a08 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
# svelte
+## 5.36.13
+
+### Patch Changes
+
+- fix: ensure subscriptions are picked up correctly by deriveds ([#16466](https://github.com/sveltejs/svelte/pull/16466))
+
## 5.36.12
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 629ec99af8..4bf9a5df22 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.36.12",
+ "version": "5.36.13",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 465bd73f0f..7d47fbc5f1 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.36.12';
+export const VERSION = '5.36.13';
export const PUBLIC_VERSION = '5';
From 9412c5861c365610d7c6c0e4ecd3572ac3fe5860 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 21 Jul 2025 22:17:25 -0400
Subject: [PATCH 17/90] chore: log effect functions in log_effect_tree (#16468)
---
packages/svelte/src/internal/client/dev/debug.js | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/packages/svelte/src/internal/client/dev/debug.js b/packages/svelte/src/internal/client/dev/debug.js
index c47080ed2f..2714a3af1f 100644
--- a/packages/svelte/src/internal/client/dev/debug.js
+++ b/packages/svelte/src/internal/client/dev/debug.js
@@ -63,6 +63,13 @@ export function log_effect_tree(effect, depth = 0) {
// eslint-disable-next-line no-console
console.log(callsite);
+ } else {
+ // eslint-disable-next-line no-console
+ console.groupCollapsed(`%cfn`, `font-weight: normal`);
+ // eslint-disable-next-line no-console
+ console.log(effect.fn);
+ // eslint-disable-next-line no-console
+ console.groupEnd();
}
if (effect.deps !== null) {
From 1deb31082a383fb5f4f9ae86f1ff12657823bcd7 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 22 Jul 2025 12:56:32 -0400
Subject: [PATCH 18/90] fix: abort and reschedule `$effect.pre` when necessary
(#16335)
* unskip failing test
* fix
* tidy up
* skip_no_async
* add comment
---
.../runtime-runes/samples/effect-order-7/_config.js | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/packages/svelte/tests/runtime-runes/samples/effect-order-7/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-order-7/_config.js
index 29c33c7b18..f0a9c2e867 100644
--- a/packages/svelte/tests/runtime-runes/samples/effect-order-7/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/effect-order-7/_config.js
@@ -2,14 +2,18 @@ import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
- skip: true,
+ // For this to work in non-async mode, we would need to abort
+ // inside `#traverse_effect_tree`, which would be very
+ // complicated and annoying. Since this hasn't been
+ // a real issue (AFAICT), we ignore it
+ skip_no_async: true,
- async test({ assert, target, logs }) {
+ async test({ target }) {
const [open, close] = target.querySelectorAll('button');
flushSync(() => open.click());
- flushSync(() => close.click());
- assert.deepEqual(logs, [true]);
+ // if the effect queue isn't aborted after the state change, this will throw
+ flushSync(() => close.click());
}
});
From 68372460e999e48ac09b1c2bd4935ceaea8c33e9 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 22 Jul 2025 13:29:15 -0400
Subject: [PATCH 19/90] chore: small tidy up (#16476)
---
.../src/internal/client/reactivity/batch.js | 66 +++++++++----------
1 file changed, 33 insertions(+), 33 deletions(-)
diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js
index ec082bb595..c452211894 100644
--- a/packages/svelte/src/internal/client/reactivity/batch.js
+++ b/packages/svelte/src/internal/client/reactivity/batch.js
@@ -193,6 +193,8 @@ export class Batch {
// if we didn't start any new async work, and no async work
// is outstanding from a previous flush, commit
if (this.#async_effects.length === 0 && this.#pending === 0) {
+ this.#commit();
+
var render_effects = this.#render_effects;
var effects = this.#effects;
@@ -200,8 +202,6 @@ export class Batch {
this.#effects = [];
this.#block_effects = [];
- this.#commit();
-
flush_queued_effects(render_effects);
flush_queued_effects(effects);
@@ -539,43 +539,43 @@ function flush_queued_effects(effects) {
var length = effects.length;
if (length === 0) return;
- for (var i = 0; i < length; i++) {
- var effect = effects[i];
-
- if ((effect.f & (DESTROYED | INERT)) === 0) {
- if (is_dirty(effect)) {
- var wv = write_version;
-
- update_effect(effect);
-
- // Effects with no dependencies or teardown do not get added to the effect tree.
- // Deferred effects (e.g. `$effect(...)`) _are_ added to the tree because we
- // don't know if we need to keep them until they are executed. Doing the check
- // here (rather than in `update_effect`) allows us to skip the work for
- // immediate effects.
- if (effect.deps === null && effect.first === null && effect.nodes_start === null) {
- // if there's no teardown or abort controller we completely unlink
- // the effect from the graph
- if (effect.teardown === null && effect.ac === null) {
- // remove this effect from the graph
- unlink_effect(effect);
- } else {
- // keep the effect in the graph, but free up some memory
- effect.fn = null;
- }
- }
+ var i = 0;
- // if state is written in a user effect, abort and re-schedule, lest we run
- // effects that should be removed as a result of the state change
- if (write_version > wv && (effect.f & USER_EFFECT) !== 0) {
- break;
+ while (i < length) {
+ var effect = effects[i++];
+
+ if ((effect.f & (DESTROYED | INERT)) === 0 && is_dirty(effect)) {
+ var wv = write_version;
+
+ update_effect(effect);
+
+ // Effects with no dependencies or teardown do not get added to the effect tree.
+ // Deferred effects (e.g. `$effect(...)`) _are_ added to the tree because we
+ // don't know if we need to keep them until they are executed. Doing the check
+ // here (rather than in `update_effect`) allows us to skip the work for
+ // immediate effects.
+ if (effect.deps === null && effect.first === null && effect.nodes_start === null) {
+ // if there's no teardown or abort controller we completely unlink
+ // the effect from the graph
+ if (effect.teardown === null && effect.ac === null) {
+ // remove this effect from the graph
+ unlink_effect(effect);
+ } else {
+ // keep the effect in the graph, but free up some memory
+ effect.fn = null;
}
}
+
+ // if state is written in a user effect, abort and re-schedule, lest we run
+ // effects that should be removed as a result of the state change
+ if (write_version > wv && (effect.f & USER_EFFECT) !== 0) {
+ break;
+ }
}
}
- for (; i < length; i += 1) {
- schedule_effect(effects[i]);
+ while (i < length) {
+ schedule_effect(effects[i++]);
}
}
From 8e2f4b51c50a2e10bc482fac6d900be921dd8b74 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 23 Jul 2025 07:55:51 -0400
Subject: [PATCH 20/90] fix: unset batch before flushing queued effects
(#16482)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* - add state changes resulting from an $effect to a separate new batch
- schedule rerunning effects based on the sources that are dirty, not just rerunning them all blindly (excempting async effects which will have run by that time already)
* test
* better fix
* tests
* this fixes the last test somehow
* fix #16477
* typo
* copy over changeset from #16477
* copy over changeset from #16464
* changeset
* dedupe
* move flushing_sync check inside Batch.ensure
* unused
* flushing_sync -> is_flushing_sync
* remove flush_effects method
* dedupe declaration
* tweak
* tweak
* update comment — it _does_ feel slightly wrong, but no wronger than the rest of this cursed function
---------
Co-authored-by: Simon Holthausen
---
.changeset/grumpy-boats-beg.md | 5 +
.changeset/shiny-walls-fix.md | 5 +
.changeset/thick-mice-kick.md | 5 +
.../src/internal/client/reactivity/batch.js | 190 ++++++++++--------
.../src/internal/client/reactivity/sources.js | 11 +-
.../Component.svelte | 7 +
.../1000-reading-derived-effects/_config.js | 5 +
.../1000-reading-derived-effects/main.svelte | 8 +
.../_config.js | 26 +++
.../main.svelte | 27 +++
.../async-effect-triggers-await/_config.js | 32 +++
.../async-effect-triggers-await/main.svelte | 24 +++
.../binding-update-while-focused-2/_config.js | 24 +++
.../main.svelte | 21 ++
14 files changed, 298 insertions(+), 92 deletions(-)
create mode 100644 .changeset/grumpy-boats-beg.md
create mode 100644 .changeset/shiny-walls-fix.md
create mode 100644 .changeset/thick-mice-kick.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/main.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/main.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/main.svelte
diff --git a/.changeset/grumpy-boats-beg.md b/.changeset/grumpy-boats-beg.md
new file mode 100644
index 0000000000..f677743def
--- /dev/null
+++ b/.changeset/grumpy-boats-beg.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: keep input in sync when binding updated via effect
diff --git a/.changeset/shiny-walls-fix.md b/.changeset/shiny-walls-fix.md
new file mode 100644
index 0000000000..91ed548728
--- /dev/null
+++ b/.changeset/shiny-walls-fix.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: prevent infinite async loop
diff --git a/.changeset/thick-mice-kick.md b/.changeset/thick-mice-kick.md
new file mode 100644
index 0000000000..eec55b77ee
--- /dev/null
+++ b/.changeset/thick-mice-kick.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: exclude derived writes from effect abort and rescheduling
diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js
index c452211894..ce413fa1e1 100644
--- a/packages/svelte/src/internal/client/reactivity/batch.js
+++ b/packages/svelte/src/internal/client/reactivity/batch.js
@@ -21,14 +21,13 @@ import {
is_updating_effect,
set_is_updating_effect,
set_signal_status,
- update_effect,
- write_version
+ update_effect
} from '../runtime.js';
import * as e from '../errors.js';
import { flush_tasks } from '../dom/task.js';
import { DEV } from 'esm-env';
import { invoke_error_boundary } from '../error-handling.js';
-import { old_values } from './sources.js';
+import { mark_reactions, old_values } from './sources.js';
import { unlink_effect } from './effects.js';
import { unset_context } from './async.js';
@@ -70,13 +69,15 @@ let last_scheduled_effect = null;
let is_flushing = false;
+let is_flushing_sync = false;
+
export class Batch {
/**
* The current values of any sources that are updated in this batch
* They keys of this map are identical to `this.#previous`
* @type {Map}
*/
- #current = new Map();
+ current = new Map();
/**
* The values of any sources that are updated in this batch _before_ those updates took place.
@@ -156,7 +157,7 @@ export class Batch {
*
* @param {Effect[]} root_effects
*/
- #process(root_effects) {
+ process(root_effects) {
queued_root_effects = [];
/** @type {Map | null} */
@@ -169,7 +170,7 @@ export class Batch {
current_values = new Map();
batch_deriveds = new Map();
- for (const [source, current] of this.#current) {
+ for (const [source, current] of this.current) {
current_values.set(source, { v: source.v, wv: source.wv });
source.v = current;
}
@@ -202,9 +203,22 @@ export class Batch {
this.#effects = [];
this.#block_effects = [];
+ // If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with
+ // newly updated sources, which could lead to infinite loops when effects run over and over again.
+ current_batch = null;
+
flush_queued_effects(render_effects);
flush_queued_effects(effects);
+ // Reinstate the current batch if there was no new one created, as `process()` runs in a loop in `flush_effects()`.
+ // That method expects `current_batch` to be set, and could run the loop again if effects result in new effects
+ // being scheduled but without writes happening in which case no new batch is created.
+ if (current_batch === null) {
+ current_batch = this;
+ } else {
+ batches.delete(this);
+ }
+
this.#deferred?.resolve();
} else {
// otherwise mark effects clean so they get scheduled on the next run
@@ -300,7 +314,7 @@ export class Batch {
this.#previous.set(source, value);
}
- this.#current.set(source, source.v);
+ this.current.set(source, source.v);
}
activate() {
@@ -327,13 +341,13 @@ export class Batch {
flush() {
if (queued_root_effects.length > 0) {
- this.flush_effects();
+ flush_effects();
} else {
this.#commit();
}
if (current_batch !== this) {
- // this can happen if a `flushSync` occurred during `this.flush_effects()`,
+ // this can happen if a `flushSync` occurred during `flush_effects()`,
// which is permitted in legacy mode despite being a terrible idea
return;
}
@@ -345,52 +359,6 @@ export class Batch {
this.deactivate();
}
- flush_effects() {
- var was_updating_effect = is_updating_effect;
- is_flushing = true;
-
- try {
- var flush_count = 0;
- set_is_updating_effect(true);
-
- while (queued_root_effects.length > 0) {
- if (flush_count++ > 1000) {
- if (DEV) {
- var updates = new Map();
-
- for (const source of this.#current.keys()) {
- for (const [stack, update] of source.updated ?? []) {
- var entry = updates.get(stack);
-
- if (!entry) {
- entry = { error: update.error, count: 0 };
- updates.set(stack, entry);
- }
-
- entry.count += update.count;
- }
- }
-
- for (const update of updates.values()) {
- // eslint-disable-next-line no-console
- console.error(update.error);
- }
- }
-
- infinite_loop_guard();
- }
-
- this.#process(queued_root_effects);
- old_values.clear();
- }
- } finally {
- is_flushing = false;
- set_is_updating_effect(was_updating_effect);
-
- last_scheduled_effect = null;
- }
- }
-
/**
* Append and remove branches to/from the DOM
*/
@@ -412,19 +380,8 @@ export class Batch {
this.#pending -= 1;
if (this.#pending === 0) {
- for (const e of this.#render_effects) {
- set_signal_status(e, DIRTY);
- schedule_effect(e);
- }
-
- for (const e of this.#effects) {
- set_signal_status(e, DIRTY);
- schedule_effect(e);
- }
-
- for (const e of this.#block_effects) {
- set_signal_status(e, DIRTY);
- schedule_effect(e);
+ for (const source of this.current.keys()) {
+ mark_reactions(source, DIRTY, false);
}
this.#render_effects = [];
@@ -445,12 +402,12 @@ export class Batch {
return (this.#deferred ??= deferred()).promise;
}
- static ensure(autoflush = true) {
+ static ensure() {
if (current_batch === null) {
const batch = (current_batch = new Batch());
batches.add(current_batch);
- if (autoflush) {
+ if (!is_flushing_sync) {
Batch.enqueue(() => {
if (current_batch !== batch) {
// a flushSync happened in the meantime
@@ -487,32 +444,85 @@ export function flushSync(fn) {
e.flush_sync_in_effect();
}
- var result;
+ var was_flushing_sync = is_flushing_sync;
+ is_flushing_sync = true;
- const batch = Batch.ensure(false);
+ try {
+ var result;
- if (fn) {
- batch.flush_effects();
+ if (fn) {
+ flush_effects();
+ result = fn();
+ }
- result = fn();
- }
+ while (true) {
+ flush_tasks();
- while (true) {
- flush_tasks();
+ if (queued_root_effects.length === 0) {
+ current_batch?.flush();
- if (queued_root_effects.length === 0) {
- if (batch === current_batch) {
- batch.flush();
+ // we need to check again, in case we just updated an `$effect.pending()`
+ if (queued_root_effects.length === 0) {
+ // this would be reset in `flush_effects()` but since we are early returning here,
+ // we need to reset it here as well in case the first time there's 0 queued root effects
+ last_scheduled_effect = null;
+
+ return /** @type {T} */ (result);
+ }
}
- // this would be reset in `batch.flush_effects()` but since we are early returning here,
- // we need to reset it here as well in case the first time there's 0 queued root effects
- last_scheduled_effect = null;
+ flush_effects();
+ }
+ } finally {
+ is_flushing_sync = was_flushing_sync;
+ }
+}
+
+function flush_effects() {
+ var was_updating_effect = is_updating_effect;
+ is_flushing = true;
+
+ try {
+ var flush_count = 0;
+ set_is_updating_effect(true);
+
+ while (queued_root_effects.length > 0) {
+ var batch = Batch.ensure();
+
+ if (flush_count++ > 1000) {
+ if (DEV) {
+ var updates = new Map();
+
+ for (const source of batch.current.keys()) {
+ for (const [stack, update] of source.updated ?? []) {
+ var entry = updates.get(stack);
+
+ if (!entry) {
+ entry = { error: update.error, count: 0 };
+ updates.set(stack, entry);
+ }
+
+ entry.count += update.count;
+ }
+ }
+
+ for (const update of updates.values()) {
+ // eslint-disable-next-line no-console
+ console.error(update.error);
+ }
+ }
+
+ infinite_loop_guard();
+ }
- return /** @type {T} */ (result);
+ batch.process(queued_root_effects);
+ old_values.clear();
}
+ } finally {
+ is_flushing = false;
+ set_is_updating_effect(was_updating_effect);
- batch.flush_effects();
+ last_scheduled_effect = null;
}
}
@@ -545,7 +555,7 @@ function flush_queued_effects(effects) {
var effect = effects[i++];
if ((effect.f & (DESTROYED | INERT)) === 0 && is_dirty(effect)) {
- var wv = write_version;
+ var n = current_batch ? current_batch.current.size : 0;
update_effect(effect);
@@ -568,7 +578,11 @@ function flush_queued_effects(effects) {
// if state is written in a user effect, abort and re-schedule, lest we run
// effects that should be removed as a result of the state change
- if (write_version > wv && (effect.f & USER_EFFECT) !== 0) {
+ if (
+ current_batch !== null &&
+ current_batch.current.size > n &&
+ (effect.f & USER_EFFECT) !== 0
+ ) {
break;
}
}
diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js
index f6b14f3360..3b28c8fdce 100644
--- a/packages/svelte/src/internal/client/reactivity/sources.js
+++ b/packages/svelte/src/internal/client/reactivity/sources.js
@@ -179,7 +179,7 @@ export function internal_set(source, value) {
source.v = value;
- const batch = Batch.ensure();
+ var batch = Batch.ensure();
batch.capture(source, old_value);
if (DEV) {
@@ -301,9 +301,10 @@ export function increment(source) {
/**
* @param {Value} signal
* @param {number} status should be DIRTY or MAYBE_DIRTY
+ * @param {boolean} schedule_async
* @returns {void}
*/
-function mark_reactions(signal, status) {
+export function mark_reactions(signal, status, schedule_async = true) {
var reactions = signal.reactions;
if (reactions === null) return;
@@ -323,14 +324,16 @@ function mark_reactions(signal, status) {
continue;
}
+ var should_schedule = (flags & DIRTY) === 0 && (schedule_async || (flags & ASYNC) === 0);
+
// don't set a DIRTY reaction to MAYBE_DIRTY
- if ((flags & DIRTY) === 0) {
+ if (should_schedule) {
set_signal_status(reaction, status);
}
if ((flags & DERIVED) !== 0) {
mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY);
- } else if ((flags & DIRTY) === 0) {
+ } else if (should_schedule) {
schedule_effect(/** @type {Effect} */ (reaction));
}
}
diff --git a/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte
new file mode 100644
index 0000000000..7a54323cb9
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js
new file mode 100644
index 0000000000..2e4a27cf09
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js
@@ -0,0 +1,5 @@
+import { test } from '../../test';
+
+export default test({
+ async test() {}
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte
new file mode 100644
index 0000000000..bd326edfb9
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte
@@ -0,0 +1,8 @@
+
+
+{#each arr}
+
+{/each}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/_config.js b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/_config.js
new file mode 100644
index 0000000000..782ae945f9
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/_config.js
@@ -0,0 +1,26 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target }) {
+ await tick();
+
+ const [input] = target.querySelectorAll('input');
+
+ input.focus();
+ input.value = '3';
+ input.dispatchEvent(new InputEvent('input', { bubbles: true }));
+ await tick();
+
+ assert.equal(input.value, '3');
+ assert.htmlEqual(target.innerHTML, `3
`);
+
+ input.focus();
+ input.value = '1';
+ input.dispatchEvent(new InputEvent('input', { bubbles: true }));
+ await tick();
+
+ assert.equal(input.value, '2');
+ assert.htmlEqual(target.innerHTML, `2
`);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/main.svelte
new file mode 100644
index 0000000000..763ce6ebf0
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/main.svelte
@@ -0,0 +1,27 @@
+
+
+
+ {await value}
+
+
+ {#snippet pending()}
+ loading...
+ {/snippet}
+
diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/_config.js b/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/_config.js
new file mode 100644
index 0000000000..c551cc6b8c
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/_config.js
@@ -0,0 +1,32 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target }) {
+ await tick();
+
+ const [increment] = target.querySelectorAll('button');
+
+ increment.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ increment
+ 1
+ 1
+ `
+ );
+
+ increment.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ increment
+ 2
+ 2
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/main.svelte
new file mode 100644
index 0000000000..153fe03f0d
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/main.svelte
@@ -0,0 +1,24 @@
+
+
+
+ count += 1}>increment
+ {JSON.stringify((await data), null, 2)}
+ {#if true}
+
+ {unrelated}
+ {/if}
+
+ {#snippet pending()}
+ loading...
+ {/snippet}
+
diff --git a/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/_config.js b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/_config.js
new file mode 100644
index 0000000000..7a56c79d71
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/_config.js
@@ -0,0 +1,24 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ test({ assert, target }) {
+ const [input] = target.querySelectorAll('input');
+
+ input.focus();
+ input.value = '3';
+ input.dispatchEvent(new InputEvent('input', { bubbles: true }));
+ flushSync();
+
+ assert.equal(input.value, '3');
+ assert.htmlEqual(target.innerHTML, `3
`);
+
+ input.focus();
+ input.value = '1';
+ input.dispatchEvent(new InputEvent('input', { bubbles: true }));
+ flushSync();
+
+ assert.equal(input.value, '2');
+ assert.htmlEqual(target.innerHTML, `2
`);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/main.svelte
new file mode 100644
index 0000000000..b0597c223b
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/main.svelte
@@ -0,0 +1,21 @@
+
+
+{value}
+
From c26365bdda1a13e98a2e75f17da655e08ff4f8bc Mon Sep 17 00:00:00 2001
From: Hyunbin Seo <47051820+hyunbinseo@users.noreply.github.com>
Date: Wed, 23 Jul 2025 21:02:28 +0900
Subject: [PATCH 21/90] chore: rename form `accept-charset` attribute (#16478)
* chore: rename form accept-charset attribute
* chore: utf-8 to lowercase
Co-authored-by: Rich Harris
* Update .changeset/healthy-carpets-deny.md
---------
Co-authored-by: Rich Harris
---
.changeset/healthy-carpets-deny.md | 5 +++++
packages/svelte/elements.d.ts | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
create mode 100644 .changeset/healthy-carpets-deny.md
diff --git a/.changeset/healthy-carpets-deny.md b/.changeset/healthy-carpets-deny.md
new file mode 100644
index 0000000000..8f8db7fa9c
--- /dev/null
+++ b/.changeset/healthy-carpets-deny.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: rename form accept-charset attribute
diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts
index 1492f77792..604241592a 100644
--- a/packages/svelte/elements.d.ts
+++ b/packages/svelte/elements.d.ts
@@ -996,7 +996,7 @@ export interface HTMLFieldsetAttributes extends HTMLAttributes {
- acceptcharset?: string | undefined | null;
+ 'accept-charset'?: 'utf-8' | (string & {}) | undefined | null;
action?: string | undefined | null;
autocomplete?: AutoFillBase | undefined | null;
enctype?:
From f8820956d2591288de53927c5e173242a13f125a Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 23 Jul 2025 11:23:52 -0400
Subject: [PATCH 22/90] Version Packages (#16484)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/grumpy-boats-beg.md | 5 -----
.changeset/healthy-carpets-deny.md | 5 -----
.changeset/shiny-walls-fix.md | 5 -----
.changeset/thick-mice-kick.md | 5 -----
packages/svelte/CHANGELOG.md | 12 ++++++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
7 files changed, 14 insertions(+), 22 deletions(-)
delete mode 100644 .changeset/grumpy-boats-beg.md
delete mode 100644 .changeset/healthy-carpets-deny.md
delete mode 100644 .changeset/shiny-walls-fix.md
delete mode 100644 .changeset/thick-mice-kick.md
diff --git a/.changeset/grumpy-boats-beg.md b/.changeset/grumpy-boats-beg.md
deleted file mode 100644
index f677743def..0000000000
--- a/.changeset/grumpy-boats-beg.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: keep input in sync when binding updated via effect
diff --git a/.changeset/healthy-carpets-deny.md b/.changeset/healthy-carpets-deny.md
deleted file mode 100644
index 8f8db7fa9c..0000000000
--- a/.changeset/healthy-carpets-deny.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: rename form accept-charset attribute
diff --git a/.changeset/shiny-walls-fix.md b/.changeset/shiny-walls-fix.md
deleted file mode 100644
index 91ed548728..0000000000
--- a/.changeset/shiny-walls-fix.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: prevent infinite async loop
diff --git a/.changeset/thick-mice-kick.md b/.changeset/thick-mice-kick.md
deleted file mode 100644
index eec55b77ee..0000000000
--- a/.changeset/thick-mice-kick.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: exclude derived writes from effect abort and rescheduling
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 5a5e532a08..8cd3850460 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,17 @@
# svelte
+## 5.36.14
+
+### Patch Changes
+
+- fix: keep input in sync when binding updated via effect ([#16482](https://github.com/sveltejs/svelte/pull/16482))
+
+- fix: rename form accept-charset attribute ([#16478](https://github.com/sveltejs/svelte/pull/16478))
+
+- fix: prevent infinite async loop ([#16482](https://github.com/sveltejs/svelte/pull/16482))
+
+- fix: exclude derived writes from effect abort and rescheduling ([#16482](https://github.com/sveltejs/svelte/pull/16482))
+
## 5.36.13
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 4bf9a5df22..7fe1d161f2 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.36.13",
+ "version": "5.36.14",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 7d47fbc5f1..cd9d8b459c 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.36.13';
+export const VERSION = '5.36.14';
export const PUBLIC_VERSION = '5';
From 53417ea8f785f2ec884b095ac1bacd08aa30da86 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Thu, 24 Jul 2025 01:48:32 -0400
Subject: [PATCH 23/90] fix: preserve dirty status of deferred effects (#16487)
* don't mark_reactions inside decrement, it can cause infinite loops
* revert mark_reactions changes
* preserve DIRTY/MAYBE_DIRTY status of deferred effects
* changeset
* tweak
---
.changeset/cool-insects-argue.md | 5 ++
.../src/internal/client/reactivity/batch.js | 63 ++++++++++++++-----
.../src/internal/client/reactivity/sources.js | 9 +--
.../async-effect-conservative/_config.js | 28 +++++++++
.../async-effect-conservative/main.svelte | 17 +++++
5 files changed, 101 insertions(+), 21 deletions(-)
create mode 100644 .changeset/cool-insects-argue.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-conservative/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-conservative/main.svelte
diff --git a/.changeset/cool-insects-argue.md b/.changeset/cool-insects-argue.md
new file mode 100644
index 0000000000..ff1c520b7b
--- /dev/null
+++ b/.changeset/cool-insects-argue.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: preserve dirty status of deferred effects
diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js
index ce413fa1e1..89bad947c7 100644
--- a/packages/svelte/src/internal/client/reactivity/batch.js
+++ b/packages/svelte/src/internal/client/reactivity/batch.js
@@ -10,7 +10,8 @@ import {
INERT,
RENDER_EFFECT,
ROOT_EFFECT,
- USER_EFFECT
+ USER_EFFECT,
+ MAYBE_DIRTY
} from '#client/constants';
import { async_mode_flag } from '../../flags/index.js';
import { deferred, define_property } from '../../shared/utils.js';
@@ -27,7 +28,7 @@ import * as e from '../errors.js';
import { flush_tasks } from '../dom/task.js';
import { DEV } from 'esm-env';
import { invoke_error_boundary } from '../error-handling.js';
-import { mark_reactions, old_values } from './sources.js';
+import { old_values } from './sources.js';
import { unlink_effect } from './effects.js';
import { unset_context } from './async.js';
@@ -146,6 +147,18 @@ export class Batch {
*/
#block_effects = [];
+ /**
+ * Deferred effects (which run after async work has completed) that are DIRTY
+ * @type {Effect[]}
+ */
+ #dirty_effects = [];
+
+ /**
+ * Deferred effects that are MAYBE_DIRTY
+ * @type {Effect[]}
+ */
+ #maybe_dirty_effects = [];
+
/**
* A set of branches that still exist, but will be destroyed when this batch
* is committed — we skip over these during `process`
@@ -221,10 +234,9 @@ export class Batch {
this.#deferred?.resolve();
} else {
- // otherwise mark effects clean so they get scheduled on the next run
- for (const e of this.#render_effects) set_signal_status(e, CLEAN);
- for (const e of this.#effects) set_signal_status(e, CLEAN);
- for (const e of this.#block_effects) set_signal_status(e, CLEAN);
+ this.#defer_effects(this.#render_effects);
+ this.#defer_effects(this.#effects);
+ this.#defer_effects(this.#block_effects);
}
if (current_values) {
@@ -271,15 +283,15 @@ export class Batch {
if (!skip && effect.fn !== null) {
if (is_branch) {
effect.f ^= CLEAN;
- } else if ((flags & EFFECT) !== 0) {
- this.#effects.push(effect);
- } else if (async_mode_flag && (flags & RENDER_EFFECT) !== 0) {
- this.#render_effects.push(effect);
- } else if (is_dirty(effect)) {
- if ((flags & ASYNC) !== 0) {
+ } else if ((flags & CLEAN) === 0) {
+ if ((flags & EFFECT) !== 0) {
+ this.#effects.push(effect);
+ } else if (async_mode_flag && (flags & RENDER_EFFECT) !== 0) {
+ this.#render_effects.push(effect);
+ } else if ((flags & ASYNC) !== 0) {
var effects = effect.b?.pending ? this.#boundary_async_effects : this.#async_effects;
effects.push(effect);
- } else {
+ } else if (is_dirty(effect)) {
if ((effect.f & BLOCK_EFFECT) !== 0) this.#block_effects.push(effect);
update_effect(effect);
}
@@ -303,6 +315,21 @@ export class Batch {
}
}
+ /**
+ * @param {Effect[]} effects
+ */
+ #defer_effects(effects) {
+ for (const e of effects) {
+ const target = (e.f & DIRTY) !== 0 ? this.#dirty_effects : this.#maybe_dirty_effects;
+ target.push(e);
+
+ // mark as clean so they get scheduled if they depend on pending async state
+ set_signal_status(e, CLEAN);
+ }
+
+ effects.length = 0;
+ }
+
/**
* Associate a change to a given source with the current
* batch, noting its previous and current values
@@ -380,8 +407,14 @@ export class Batch {
this.#pending -= 1;
if (this.#pending === 0) {
- for (const source of this.current.keys()) {
- mark_reactions(source, DIRTY, false);
+ for (const e of this.#dirty_effects) {
+ set_signal_status(e, DIRTY);
+ schedule_effect(e);
+ }
+
+ for (const e of this.#maybe_dirty_effects) {
+ set_signal_status(e, MAYBE_DIRTY);
+ schedule_effect(e);
}
this.#render_effects = [];
diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js
index 3b28c8fdce..7b5198542a 100644
--- a/packages/svelte/src/internal/client/reactivity/sources.js
+++ b/packages/svelte/src/internal/client/reactivity/sources.js
@@ -301,10 +301,9 @@ export function increment(source) {
/**
* @param {Value} signal
* @param {number} status should be DIRTY or MAYBE_DIRTY
- * @param {boolean} schedule_async
* @returns {void}
*/
-export function mark_reactions(signal, status, schedule_async = true) {
+function mark_reactions(signal, status) {
var reactions = signal.reactions;
if (reactions === null) return;
@@ -324,16 +323,14 @@ export function mark_reactions(signal, status, schedule_async = true) {
continue;
}
- var should_schedule = (flags & DIRTY) === 0 && (schedule_async || (flags & ASYNC) === 0);
-
// don't set a DIRTY reaction to MAYBE_DIRTY
- if (should_schedule) {
+ if ((flags & DIRTY) === 0) {
set_signal_status(reaction, status);
}
if ((flags & DERIVED) !== 0) {
mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY);
- } else if (should_schedule) {
+ } else {
schedule_effect(/** @type {Effect} */ (reaction));
}
}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-conservative/_config.js b/packages/svelte/tests/runtime-runes/samples/async-effect-conservative/_config.js
new file mode 100644
index 0000000000..bab06a203d
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-effect-conservative/_config.js
@@ -0,0 +1,28 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target, logs }) {
+ await tick();
+
+ const [increment] = target.querySelectorAll('button');
+
+ assert.deepEqual(logs, [false]);
+ assert.htmlEqual(target.innerHTML, 'increment 0
');
+
+ increment.click();
+ await tick();
+ assert.deepEqual(logs, [false]);
+ assert.htmlEqual(target.innerHTML, 'increment 1
');
+
+ increment.click();
+ await tick();
+ assert.deepEqual(logs, [false, true]);
+ assert.htmlEqual(target.innerHTML, 'increment 2
');
+
+ increment.click();
+ await tick();
+ assert.deepEqual(logs, [false, true]);
+ assert.htmlEqual(target.innerHTML, 'increment 3
');
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-conservative/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-effect-conservative/main.svelte
new file mode 100644
index 0000000000..5305067a5a
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-effect-conservative/main.svelte
@@ -0,0 +1,17 @@
+
+
+
+ count += 1}>increment
+ {await count}
+
+ {#snippet pending()}
+ loading...
+ {/snippet}
+
From 46d3261ed9a7d6acb9908f52e989013795555a56 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Thu, 24 Jul 2025 11:39:54 +0200
Subject: [PATCH 24/90] chore: don't do effect scheduling unnecessarily
(#16489)
mini-cleanup post #16487 - we don't need to do the work of scheduling an effect that's already dirty which means it already scheduled its root effect to run
---
packages/svelte/src/internal/client/reactivity/sources.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js
index 7b5198542a..3b2087d56b 100644
--- a/packages/svelte/src/internal/client/reactivity/sources.js
+++ b/packages/svelte/src/internal/client/reactivity/sources.js
@@ -323,14 +323,16 @@ function mark_reactions(signal, status) {
continue;
}
+ var not_dirty = (flags & DIRTY) === 0;
+
// don't set a DIRTY reaction to MAYBE_DIRTY
- if ((flags & DIRTY) === 0) {
+ if (not_dirty) {
set_signal_status(reaction, status);
}
if ((flags & DERIVED) !== 0) {
mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY);
- } else {
+ } else if (not_dirty) {
schedule_effect(/** @type {Effect} */ (reaction));
}
}
From 4e74cd35fedf55e65b60c957041f79ca94dfc197 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 24 Jul 2025 11:43:28 +0200
Subject: [PATCH 25/90] Version Packages (#16488)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/cool-insects-argue.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/cool-insects-argue.md
diff --git a/.changeset/cool-insects-argue.md b/.changeset/cool-insects-argue.md
deleted file mode 100644
index ff1c520b7b..0000000000
--- a/.changeset/cool-insects-argue.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: preserve dirty status of deferred effects
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 8cd3850460..5bffa5f70e 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
# svelte
+## 5.36.15
+
+### Patch Changes
+
+- fix: preserve dirty status of deferred effects ([#16487](https://github.com/sveltejs/svelte/pull/16487))
+
## 5.36.14
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 7fe1d161f2..051f82ec3a 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.36.14",
+ "version": "5.36.15",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index cd9d8b459c..1d469f29b0 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.36.14';
+export const VERSION = '5.36.15';
export const PUBLIC_VERSION = '5';
From 7eb11e0e247d9da4edb88dc5652101ceb55fc530 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Thu, 24 Jul 2025 16:00:43 +0200
Subject: [PATCH 26/90] fix: don't destroy effect roots created inside of
deriveds (#16492)
We were wrongfully adding effect roots to `derived.effects`, too, which meant those were destroyed when the derived reran.
---
.changeset/lemon-weeks-call.md | 5 ++
.../src/internal/client/reactivity/effects.js | 6 ++-
.../src/internal/client/reactivity/types.d.ts | 2 +-
packages/svelte/tests/signals/test.ts | 51 +++++++++++++++++++
4 files changed, 62 insertions(+), 2 deletions(-)
create mode 100644 .changeset/lemon-weeks-call.md
diff --git a/.changeset/lemon-weeks-call.md b/.changeset/lemon-weeks-call.md
new file mode 100644
index 0000000000..ae62305630
--- /dev/null
+++ b/.changeset/lemon-weeks-call.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: don't destroy effect roots created inside of deriveds
diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js
index c4edd2bf8d..f44efa32f1 100644
--- a/packages/svelte/src/internal/client/reactivity/effects.js
+++ b/packages/svelte/src/internal/client/reactivity/effects.js
@@ -148,7 +148,11 @@ function create_effect(type, fn, sync, push = true) {
}
// if we're in a derived, add the effect there too
- if (active_reaction !== null && (active_reaction.f & DERIVED) !== 0) {
+ if (
+ active_reaction !== null &&
+ (active_reaction.f & DERIVED) !== 0 &&
+ (type & ROOT_EFFECT) === 0
+ ) {
var derived = /** @type {Derived} */ (active_reaction);
(derived.effects ??= []).push(effect);
}
diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts
index 72187e84a7..81f7197b80 100644
--- a/packages/svelte/src/internal/client/reactivity/types.d.ts
+++ b/packages/svelte/src/internal/client/reactivity/types.d.ts
@@ -54,7 +54,7 @@ export interface Reaction extends Signal {
export interface Derived extends Value, Reaction {
/** The derived function */
fn: () => V;
- /** Effects created inside this signal */
+ /** Effects created inside this signal. Used to destroy those effects when the derived reruns or is cleaned up */
effects: null | Effect[];
/** Parent effect or derived */
parent: Effect | Derived | null;
diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts
index 937324727b..eff6d6166a 100644
--- a/packages/svelte/tests/signals/test.ts
+++ b/packages/svelte/tests/signals/test.ts
@@ -1390,4 +1390,55 @@ describe('signals', () => {
destroy();
};
});
+
+ test('$effect.root inside deriveds stay alive independently', () => {
+ const log: any[] = [];
+ const c = state(0);
+ const cleanup: any[] = [];
+ const inner_states: any[] = [];
+
+ const d = derived(() => {
+ const destroy = effect_root(() => {
+ const x = state(0);
+ inner_states.push(x);
+
+ effect(() => {
+ log.push('inner ' + $.get(x));
+ return () => {
+ log.push('inner destroyed');
+ };
+ });
+ });
+
+ cleanup.push(destroy);
+
+ return $.get(c);
+ });
+
+ return () => {
+ log.push($.get(d));
+ flushSync();
+
+ assert.deepEqual(log, [0, 'inner 0']);
+ log.length = 0;
+
+ set(inner_states[0], 1);
+ flushSync();
+
+ assert.deepEqual(log, ['inner destroyed', 'inner 1']);
+ log.length = 0;
+
+ set(c, 1);
+ log.push($.get(d));
+ flushSync();
+
+ assert.deepEqual(log, [1, 'inner 0']);
+ log.length = 0;
+
+ cleanup.forEach((fn) => fn());
+ flushSync();
+
+ assert.deepEqual(log, ['inner destroyed', 'inner destroyed']);
+ };
+ });
});
From dc043fb2d3c87619e1c26315f600acd4fe19dfa4 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Thu, 24 Jul 2025 10:16:15 -0400
Subject: [PATCH 27/90] fix: don't update a focused input with values from its
own past (#16491)
* fix: don't update a focused input with values from its own past
* remove
* fix
---
.changeset/fast-mails-fail.md | 5 +++
.../client/dom/elements/bindings/input.js | 11 ++++--
.../src/internal/client/reactivity/batch.js | 12 +++++-
.../_config.js | 37 +++++++++++++++++++
.../main.svelte | 25 +++++++++++++
5 files changed, 86 insertions(+), 4 deletions(-)
create mode 100644 .changeset/fast-mails-fail.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/main.svelte
diff --git a/.changeset/fast-mails-fail.md b/.changeset/fast-mails-fail.md
new file mode 100644
index 0000000000..027cb01548
--- /dev/null
+++ b/.changeset/fast-mails-fail.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: don't update a focused input with values from its own past
diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/input.js b/packages/svelte/src/internal/client/dom/elements/bindings/input.js
index 7c1fccea0f..7c73280dd6 100644
--- a/packages/svelte/src/internal/client/dom/elements/bindings/input.js
+++ b/packages/svelte/src/internal/client/dom/elements/bindings/input.js
@@ -8,7 +8,7 @@ import { queue_micro_task } from '../../task.js';
import { hydrating } from '../../hydration.js';
import { untrack } from '../../../runtime.js';
import { is_runes } from '../../../context.js';
-import { current_batch } from '../../../reactivity/batch.js';
+import { current_batch, previous_batch } from '../../../reactivity/batch.js';
/**
* @param {HTMLInputElement} input
@@ -76,13 +76,18 @@ export function bind_value(input, get, set = get) {
var value = get();
- if (input === document.activeElement && batches.has(/** @type {Batch} */ (current_batch))) {
+ if (input === document.activeElement) {
+ // we need both, because in non-async mode, render effects run before previous_batch is set
+ var batch = /** @type {Batch} */ (previous_batch ?? current_batch);
+
// Never rewrite the contents of a focused input. We can get here if, for example,
// an update is deferred because of async work depending on the input:
//
//
// {await find(query)}
- return;
+ if (batches.has(batch)) {
+ return;
+ }
}
if (is_numberlike_input(input) && value === to_number(input.value)) {
diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js
index 89bad947c7..123bc95d16 100644
--- a/packages/svelte/src/internal/client/reactivity/batch.js
+++ b/packages/svelte/src/internal/client/reactivity/batch.js
@@ -38,6 +38,13 @@ const batches = new Set();
/** @type {Batch | null} */
export let current_batch = null;
+/**
+ * This is needed to avoid overwriting inputs in non-async mode
+ * TODO 6.0 remove this, as non-async mode will go away
+ * @type {Batch | null}
+ */
+export let previous_batch = null;
+
/**
* When time travelling, we re-evaluate deriveds based on the temporary
* values of their dependencies rather than their actual values, and cache
@@ -71,7 +78,6 @@ let last_scheduled_effect = null;
let is_flushing = false;
let is_flushing_sync = false;
-
export class Batch {
/**
* The current values of any sources that are updated in this batch
@@ -173,6 +179,8 @@ export class Batch {
process(root_effects) {
queued_root_effects = [];
+ previous_batch = null;
+
/** @type {Map | null} */
var current_values = null;
@@ -218,6 +226,7 @@ export class Batch {
// If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with
// newly updated sources, which could lead to infinite loops when effects run over and over again.
+ previous_batch = current_batch;
current_batch = null;
flush_queued_effects(render_effects);
@@ -350,6 +359,7 @@ export class Batch {
deactivate() {
current_batch = null;
+ previous_batch = null;
for (const update of effect_pending_updates) {
effect_pending_updates.delete(update);
diff --git a/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/_config.js b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/_config.js
new file mode 100644
index 0000000000..b0772ad3c0
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/_config.js
@@ -0,0 +1,37 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target, instance }) {
+ instance.shift();
+ await tick();
+
+ const [input] = target.querySelectorAll('input');
+
+ input.focus();
+ input.value = '1';
+ input.dispatchEvent(new InputEvent('input', { bubbles: true }));
+ await tick();
+
+ assert.htmlEqual(target.innerHTML, ` 0
`);
+ assert.equal(input.value, '1');
+
+ input.focus();
+ input.value = '2';
+ input.dispatchEvent(new InputEvent('input', { bubbles: true }));
+ await tick();
+
+ assert.htmlEqual(target.innerHTML, ` 0
`);
+ assert.equal(input.value, '2');
+
+ instance.shift();
+ await tick();
+ assert.htmlEqual(target.innerHTML, ` 1
`);
+ assert.equal(input.value, '2');
+
+ instance.shift();
+ await tick();
+ assert.htmlEqual(target.innerHTML, ` 2
`);
+ assert.equal(input.value, '2');
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/main.svelte
new file mode 100644
index 0000000000..2fc898e654
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/main.svelte
@@ -0,0 +1,25 @@
+
+
+
+
+ {await push(count)}
+
+ {#snippet pending()}
+ loading...
+ {/snippet}
+
From 8ad02e4a8c920823034051e5e2a13df58f25ebd5 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 24 Jul 2025 16:22:30 +0200
Subject: [PATCH 28/90] Version Packages (#16493)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/fast-mails-fail.md | 5 -----
.changeset/lemon-weeks-call.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-mails-fail.md
delete mode 100644 .changeset/lemon-weeks-call.md
diff --git a/.changeset/fast-mails-fail.md b/.changeset/fast-mails-fail.md
deleted file mode 100644
index 027cb01548..0000000000
--- a/.changeset/fast-mails-fail.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: don't update a focused input with values from its own past
diff --git a/.changeset/lemon-weeks-call.md b/.changeset/lemon-weeks-call.md
deleted file mode 100644
index ae62305630..0000000000
--- a/.changeset/lemon-weeks-call.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: don't destroy effect roots created inside of deriveds
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 5bffa5f70e..450ecde53b 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,13 @@
# svelte
+## 5.36.16
+
+### Patch Changes
+
+- fix: don't update a focused input with values from its own past ([#16491](https://github.com/sveltejs/svelte/pull/16491))
+
+- fix: don't destroy effect roots created inside of deriveds ([#16492](https://github.com/sveltejs/svelte/pull/16492))
+
## 5.36.15
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 051f82ec3a..07954026b5 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.36.15",
+ "version": "5.36.16",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 1d469f29b0..5d76fc3f29 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.36.15';
+export const VERSION = '5.36.16';
export const PUBLIC_VERSION = '5';
From 0bba84cc203c7cab945527cc3928597d3612a37d Mon Sep 17 00:00:00 2001
From: Bladesheng
Date: Fri, 25 Jul 2025 14:43:41 +0200
Subject: [PATCH 29/90] fix: add types for `part` attribute to svg attributes
(#16499)
---
.changeset/seven-colts-obey.md | 5 +++++
packages/svelte/elements.d.ts | 1 +
2 files changed, 6 insertions(+)
create mode 100644 .changeset/seven-colts-obey.md
diff --git a/.changeset/seven-colts-obey.md b/.changeset/seven-colts-obey.md
new file mode 100644
index 0000000000..b41216f649
--- /dev/null
+++ b/.changeset/seven-colts-obey.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: add types for `part` attribute to svg attributes
diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts
index 604241592a..2e1042dfd6 100644
--- a/packages/svelte/elements.d.ts
+++ b/packages/svelte/elements.d.ts
@@ -1553,6 +1553,7 @@ export interface SVGAttributes extends AriaAttributes, DO
height?: number | string | undefined | null;
id?: string | undefined | null;
lang?: string | undefined | null;
+ part?: string | undefined | null;
max?: number | string | undefined | null;
media?: string | undefined | null;
// On the `textPath` element
From b0f9ea3ae62cfb9eb4437898bf8a6b90e0531657 Mon Sep 17 00:00:00 2001
From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com>
Date: Fri, 25 Jul 2025 15:57:32 -0700
Subject: [PATCH 30/90] fix: throw on duplicate class field declarations
(#16502)
* fix: throw on duplicate class field declarations
* doh
* handle assignment in constructor
* fix
* apply suggestion from review
* fix
* fix failing test
---
.changeset/odd-phones-taste.md | 5 ++
.../98-reference/.generated/compile-errors.md | 6 +++
.../svelte/messages/compile-errors/script.md | 4 ++
packages/svelte/src/compiler/errors.js | 10 ++++
.../phases/2-analyze/visitors/ClassBody.js | 53 ++++++++++++++++++-
.../class-state-constructor-9/errors.json | 12 ++---
6 files changed, 82 insertions(+), 8 deletions(-)
create mode 100644 .changeset/odd-phones-taste.md
diff --git a/.changeset/odd-phones-taste.md b/.changeset/odd-phones-taste.md
new file mode 100644
index 0000000000..ec9534b741
--- /dev/null
+++ b/.changeset/odd-phones-taste.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: throw on duplicate class field declarations
diff --git a/documentation/docs/98-reference/.generated/compile-errors.md b/documentation/docs/98-reference/.generated/compile-errors.md
index 20f57770d1..957a9f67c7 100644
--- a/documentation/docs/98-reference/.generated/compile-errors.md
+++ b/documentation/docs/98-reference/.generated/compile-errors.md
@@ -364,6 +364,12 @@ The $ name is reserved, and cannot be used for variables and imports
The $ prefix is reserved, and cannot be used for variables and imports
```
+### duplicate_class_field
+
+```
+`%name%` has already been declared
+```
+
### each_item_invalid_assignment
```
diff --git a/packages/svelte/messages/compile-errors/script.md b/packages/svelte/messages/compile-errors/script.md
index 2b0c5eafdf..5c1080aced 100644
--- a/packages/svelte/messages/compile-errors/script.md
+++ b/packages/svelte/messages/compile-errors/script.md
@@ -30,6 +30,10 @@
> The $ prefix is reserved, and cannot be used for variables and imports
+## duplicate_class_field
+
+> `%name%` has already been declared
+
## each_item_invalid_assignment
> Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`)
diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js
index 599d3e8248..e763a6e073 100644
--- a/packages/svelte/src/compiler/errors.js
+++ b/packages/svelte/src/compiler/errors.js
@@ -152,6 +152,16 @@ export function dollar_prefix_invalid(node) {
e(node, 'dollar_prefix_invalid', `The $ prefix is reserved, and cannot be used for variables and imports\nhttps://svelte.dev/e/dollar_prefix_invalid`);
}
+/**
+ * `%name%` has already been declared
+ * @param {null | number | NodeLike} node
+ * @param {string} name
+ * @returns {never}
+ */
+export function duplicate_class_field(node, name) {
+ e(node, 'duplicate_class_field', `\`${name}\` has already been declared\nhttps://svelte.dev/e/duplicate_class_field`);
+}
+
/**
* Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`)
* @param {null | number | NodeLike} node
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js
index ffc39ac00d..2bfc1dbce3 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js
@@ -33,6 +33,9 @@ export function ClassBody(node, context) {
/** @type {Map} */
const state_fields = new Map();
+ /** @type {Map>} */
+ const fields = new Map();
+
context.state.analysis.classes.set(node, state_fields);
/** @type {MethodDefinition | null} */
@@ -54,6 +57,14 @@ export function ClassBody(node, context) {
e.state_field_duplicate(node, name);
}
+ const _key = (key.type === 'PrivateIdentifier' ? '#' : '') + name;
+ const field = fields.get(_key);
+
+ // if there's already a method or assigned field, error
+ if (field && !(field.length === 1 && field[0] === 'prop')) {
+ e.duplicate_class_field(node, _key);
+ }
+
state_fields.set(name, {
node,
type: rune,
@@ -67,10 +78,48 @@ export function ClassBody(node, context) {
for (const child of node.body) {
if (child.type === 'PropertyDefinition' && !child.computed && !child.static) {
handle(child, child.key, child.value);
+ const key = (child.key.type === 'PrivateIdentifier' ? '#' : '') + get_name(child.key);
+ const field = fields.get(key);
+ if (!field) {
+ fields.set(key, [child.value ? 'assigned_prop' : 'prop']);
+ continue;
+ }
+ e.duplicate_class_field(child, key);
}
- if (child.type === 'MethodDefinition' && child.kind === 'constructor') {
- constructor = child;
+ if (child.type === 'MethodDefinition') {
+ if (child.kind === 'constructor') {
+ constructor = child;
+ } else if (!child.computed) {
+ const key = (child.key.type === 'PrivateIdentifier' ? '#' : '') + get_name(child.key);
+ const field = fields.get(key);
+ if (!field) {
+ fields.set(key, [child.kind]);
+ continue;
+ }
+ if (
+ field.includes(child.kind) ||
+ field.includes('prop') ||
+ field.includes('assigned_prop')
+ ) {
+ e.duplicate_class_field(child, key);
+ }
+ if (child.kind === 'get') {
+ if (field.length === 1 && field[0] === 'set') {
+ field.push('get');
+ continue;
+ }
+ } else if (child.kind === 'set') {
+ if (field.length === 1 && field[0] === 'get') {
+ field.push('set');
+ continue;
+ }
+ } else {
+ field.push(child.kind);
+ continue;
+ }
+ e.duplicate_class_field(child, key);
+ }
}
}
diff --git a/packages/svelte/tests/validator/samples/class-state-constructor-9/errors.json b/packages/svelte/tests/validator/samples/class-state-constructor-9/errors.json
index b7dd4c8ed4..94b5f191c2 100644
--- a/packages/svelte/tests/validator/samples/class-state-constructor-9/errors.json
+++ b/packages/svelte/tests/validator/samples/class-state-constructor-9/errors.json
@@ -1,14 +1,14 @@
[
{
- "code": "state_field_invalid_assignment",
- "message": "Cannot assign to a state field before its declaration",
+ "code": "duplicate_class_field",
+ "message": "`count` has already been declared",
"start": {
- "line": 2,
- "column": 1
+ "line": 5,
+ "column": 2
},
"end": {
- "line": 2,
- "column": 12
+ "line": 5,
+ "column": 24
}
}
]
From d0ebd42986c4d3e4db0420f1aaebd337afdb230f Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 25 Jul 2025 16:46:12 -0700
Subject: [PATCH 31/90] Version Packages (#16500)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/odd-phones-taste.md | 5 -----
.changeset/seven-colts-obey.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/odd-phones-taste.md
delete mode 100644 .changeset/seven-colts-obey.md
diff --git a/.changeset/odd-phones-taste.md b/.changeset/odd-phones-taste.md
deleted file mode 100644
index ec9534b741..0000000000
--- a/.changeset/odd-phones-taste.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: throw on duplicate class field declarations
diff --git a/.changeset/seven-colts-obey.md b/.changeset/seven-colts-obey.md
deleted file mode 100644
index b41216f649..0000000000
--- a/.changeset/seven-colts-obey.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: add types for `part` attribute to svg attributes
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 450ecde53b..766c83763a 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,13 @@
# svelte
+## 5.36.17
+
+### Patch Changes
+
+- fix: throw on duplicate class field declarations ([#16502](https://github.com/sveltejs/svelte/pull/16502))
+
+- fix: add types for `part` attribute to svg attributes ([#16499](https://github.com/sveltejs/svelte/pull/16499))
+
## 5.36.16
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 07954026b5..22bc8cc8fa 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.36.16",
+ "version": "5.36.17",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 5d76fc3f29..d2992a3dcd 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.36.16';
+export const VERSION = '5.36.17';
export const PUBLIC_VERSION = '5';
From c7851789d71cc3d0b5d89ce9c759a39be6caa51a Mon Sep 17 00:00:00 2001
From: 7nik
Date: Sat, 26 Jul 2025 21:27:22 +0300
Subject: [PATCH 32/90] fix: always mark props as stateful (#16504)
---
.changeset/gorgeous-jeans-begin.md | 5 +++++
.../phases/2-analyze/visitors/Identifier.js | 5 ++++-
.../props-default-value-function/_config.js | 15 +++++++++++++++
.../props-default-value-function/inner.svelte | 4 ++++
.../props-default-value-function/main.svelte | 14 ++++++++++++++
.../props-default-value-function/wrapper.svelte | 7 +++++++
.../props-default-value-function/wrapper2.svelte | 7 +++++++
7 files changed, 56 insertions(+), 1 deletion(-)
create mode 100644 .changeset/gorgeous-jeans-begin.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/props-default-value-function/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/props-default-value-function/inner.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/props-default-value-function/main.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper2.svelte
diff --git a/.changeset/gorgeous-jeans-begin.md b/.changeset/gorgeous-jeans-begin.md
new file mode 100644
index 0000000000..586c4d8a57
--- /dev/null
+++ b/.changeset/gorgeous-jeans-begin.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: always mark props as stateful
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js
index cced326f9b..4dfdfe5af1 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js
@@ -93,7 +93,10 @@ export function Identifier(node, context) {
context.state.expression.references.add(binding);
context.state.expression.has_state ||=
binding.kind !== 'static' &&
- !binding.is_function() &&
+ (binding.kind === 'prop' ||
+ binding.kind === 'bindable_prop' ||
+ binding.kind === 'rest_prop' ||
+ !binding.is_function()) &&
!context.state.scope.evaluate(node).is_known;
}
diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-function/_config.js b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/_config.js
new file mode 100644
index 0000000000..6b281f04f0
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/_config.js
@@ -0,0 +1,15 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ mode: ['client'],
+
+ test({ assert, target }) {
+ const btn = target.querySelector('button');
+
+ assert.htmlEqual(target.innerHTML, `inc Inner: 0 Inner: 0`);
+ btn?.click();
+ flushSync();
+ assert.htmlEqual(target.innerHTML, `inc Inner: 1 Inner: 1`);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-function/inner.svelte b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/inner.svelte
new file mode 100644
index 0000000000..6bde0a15a8
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/inner.svelte
@@ -0,0 +1,4 @@
+
+Inner: {getter()}
diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-function/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/main.svelte
new file mode 100644
index 0000000000..2cb2f67b82
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/main.svelte
@@ -0,0 +1,14 @@
+
+
+ count++}>inc
+
+
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper.svelte b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper.svelte
new file mode 100644
index 0000000000..525494ddfb
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper2.svelte b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper2.svelte
new file mode 100644
index 0000000000..9498f432d8
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper2.svelte
@@ -0,0 +1,7 @@
+
+
+
From 7cc4d56263f724c4d76ab6140af4d117cbb41f1d Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Sat, 26 Jul 2025 22:16:40 -0400
Subject: [PATCH 33/90] feat: ignore component options in `compileModule`
(#16362)
---
.changeset/violet-ways-sleep.md | 5 +
.../svelte/src/compiler/validate-options.js | 172 +++++++++---------
2 files changed, 96 insertions(+), 81 deletions(-)
create mode 100644 .changeset/violet-ways-sleep.md
diff --git a/.changeset/violet-ways-sleep.md b/.changeset/violet-ways-sleep.md
new file mode 100644
index 0000000000..749ecb1719
--- /dev/null
+++ b/.changeset/violet-ways-sleep.md
@@ -0,0 +1,5 @@
+---
+'svelte': minor
+---
+
+feat: ignore component options in `compileModule`
diff --git a/packages/svelte/src/compiler/validate-options.js b/packages/svelte/src/compiler/validate-options.js
index ed83375d22..2b727ad093 100644
--- a/packages/svelte/src/compiler/validate-options.js
+++ b/packages/svelte/src/compiler/validate-options.js
@@ -8,7 +8,7 @@ import * as w from './warnings.js';
* @typedef {(input: Input, keypath: string) => Required} Validator
*/
-const common = {
+const common_options = {
filename: string('(unknown)'),
// default to process.cwd() where it exists to replicate svelte4 behavior (and make Deno work with this as well)
@@ -48,110 +48,120 @@ const common = {
})
};
-export const validate_module_options =
- /** @type {Validator} */ (
- object({
- ...common
- })
- );
+const component_options = {
+ accessors: deprecate(w.options_deprecated_accessors, boolean(false)),
-export const validate_component_options =
- /** @type {Validator} */ (
- object({
- ...common,
+ css: validator('external', (input) => {
+ if (input === true || input === false) {
+ throw_error(
+ 'The boolean options have been removed from the css option. Use "external" instead of false and "injected" instead of true'
+ );
+ }
+ if (input === 'none') {
+ throw_error(
+ 'css: "none" is no longer a valid option. If this was crucial for you, please open an issue on GitHub with your use case.'
+ );
+ }
- accessors: deprecate(w.options_deprecated_accessors, boolean(false)),
+ if (input !== 'external' && input !== 'injected') {
+ throw_error(`css should be either "external" (default, recommended) or "injected"`);
+ }
- css: validator('external', (input) => {
- if (input === true || input === false) {
- throw_error(
- 'The boolean options have been removed from the css option. Use "external" instead of false and "injected" instead of true'
- );
- }
- if (input === 'none') {
- throw_error(
- 'css: "none" is no longer a valid option. If this was crucial for you, please open an issue on GitHub with your use case.'
- );
- }
+ return input;
+ }),
- if (input !== 'external' && input !== 'injected') {
- throw_error(`css should be either "external" (default, recommended) or "injected"`);
- }
+ cssHash: fun(({ css, hash }) => {
+ return `svelte-${hash(css)}`;
+ }),
+
+ // TODO this is a sourcemap option, would be good to put under a sourcemap namespace
+ cssOutputFilename: string(undefined),
+
+ customElement: boolean(false),
+
+ discloseVersion: boolean(true),
- return input;
- }),
+ immutable: deprecate(w.options_deprecated_immutable, boolean(false)),
- cssHash: fun(({ css, hash }) => {
- return `svelte-${hash(css)}`;
- }),
+ legacy: removed(
+ 'The legacy option has been removed. If you are using this because of legacy.componentApi, use compatibility.componentApi instead'
+ ),
+
+ compatibility: object({
+ componentApi: list([4, 5], 5)
+ }),
+
+ loopGuardTimeout: warn_removed(w.options_removed_loop_guard_timeout),
+
+ name: string(undefined),
- // TODO this is a sourcemap option, would be good to put under a sourcemap namespace
- cssOutputFilename: string(undefined),
+ namespace: list(['html', 'mathml', 'svg']),
- customElement: boolean(false),
+ modernAst: boolean(false),
- discloseVersion: boolean(true),
+ outputFilename: string(undefined),
- immutable: deprecate(w.options_deprecated_immutable, boolean(false)),
+ preserveComments: boolean(false),
- legacy: removed(
- 'The legacy option has been removed. If you are using this because of legacy.componentApi, use compatibility.componentApi instead'
- ),
+ fragments: list(['html', 'tree']),
- compatibility: object({
- componentApi: list([4, 5], 5)
- }),
+ preserveWhitespace: boolean(false),
- loopGuardTimeout: warn_removed(w.options_removed_loop_guard_timeout),
+ runes: boolean(undefined),
- name: string(undefined),
+ hmr: boolean(false),
- namespace: list(['html', 'mathml', 'svg']),
+ sourcemap: validator(undefined, (input) => {
+ // Source maps can take on a variety of values, including string, JSON, map objects from magic-string and source-map,
+ // so there's no good way to check type validity here
+ return input;
+ }),
- modernAst: boolean(false),
+ enableSourcemap: warn_removed(w.options_removed_enable_sourcemap),
- outputFilename: string(undefined),
+ hydratable: warn_removed(w.options_removed_hydratable),
- preserveComments: boolean(false),
+ format: removed(
+ 'The format option has been removed in Svelte 4, the compiler only outputs ESM now. Remove "format" from your compiler options. ' +
+ 'If you did not set this yourself, bump the version of your bundler plugin (vite-plugin-svelte/rollup-plugin-svelte/svelte-loader)'
+ ),
- fragments: list(['html', 'tree']),
+ tag: removed(
+ 'The tag option has been removed in Svelte 5. Use ` ` inside the component instead. ' +
+ 'If that does not solve your use case, please open an issue on GitHub with details.'
+ ),
- preserveWhitespace: boolean(false),
+ sveltePath: removed(
+ 'The sveltePath option has been removed in Svelte 5. ' +
+ 'If this option was crucial for you, please open an issue on GitHub with your use case.'
+ ),
- runes: boolean(undefined),
+ // These two were primarily created for svelte-preprocess (https://github.com/sveltejs/svelte/pull/6194),
+ // but with new TypeScript compilation modes strictly separating types it's not necessary anymore
+ errorMode: removed(
+ 'The errorMode option has been removed. If you are using this through svelte-preprocess with TypeScript, ' +
+ 'use the https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax setting instead'
+ ),
- hmr: boolean(false),
+ varsReport: removed(
+ 'The vars option has been removed. If you are using this through svelte-preprocess with TypeScript, ' +
+ 'use the https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax setting instead'
+ )
+};
- sourcemap: validator(undefined, (input) => {
- // Source maps can take on a variety of values, including string, JSON, map objects from magic-string and source-map,
- // so there's no good way to check type validity here
- return input;
- }),
+export const validate_module_options =
+ /** @type {Validator} */ (
+ object({
+ ...common_options,
+ ...Object.fromEntries(Object.keys(component_options).map((key) => [key, () => {}]))
+ })
+ );
- enableSourcemap: warn_removed(w.options_removed_enable_sourcemap),
- hydratable: warn_removed(w.options_removed_hydratable),
- format: removed(
- 'The format option has been removed in Svelte 4, the compiler only outputs ESM now. Remove "format" from your compiler options. ' +
- 'If you did not set this yourself, bump the version of your bundler plugin (vite-plugin-svelte/rollup-plugin-svelte/svelte-loader)'
- ),
- tag: removed(
- 'The tag option has been removed in Svelte 5. Use ` ` inside the component instead. ' +
- 'If that does not solve your use case, please open an issue on GitHub with details.'
- ),
- sveltePath: removed(
- 'The sveltePath option has been removed in Svelte 5. ' +
- 'If this option was crucial for you, please open an issue on GitHub with your use case.'
- ),
- // These two were primarily created for svelte-preprocess (https://github.com/sveltejs/svelte/pull/6194),
- // but with new TypeScript compilation modes strictly separating types it's not necessary anymore
- errorMode: removed(
- 'The errorMode option has been removed. If you are using this through svelte-preprocess with TypeScript, ' +
- 'use the https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax setting instead'
- ),
- varsReport: removed(
- 'The vars option has been removed. If you are using this through svelte-preprocess with TypeScript, ' +
- 'use the https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax setting instead'
- )
+export const validate_component_options =
+ /** @type {Validator} */ (
+ object({
+ ...common_options,
+ ...component_options
})
);
From 39ee7cf4c247965ebacdd16fc365a31f00506026 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Sat, 26 Jul 2025 22:22:14 -0700
Subject: [PATCH 34/90] Version Packages (#16505)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/gorgeous-jeans-begin.md | 5 -----
.changeset/violet-ways-sleep.md | 5 -----
packages/svelte/CHANGELOG.md | 10 ++++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
5 files changed, 12 insertions(+), 12 deletions(-)
delete mode 100644 .changeset/gorgeous-jeans-begin.md
delete mode 100644 .changeset/violet-ways-sleep.md
diff --git a/.changeset/gorgeous-jeans-begin.md b/.changeset/gorgeous-jeans-begin.md
deleted file mode 100644
index 586c4d8a57..0000000000
--- a/.changeset/gorgeous-jeans-begin.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: always mark props as stateful
diff --git a/.changeset/violet-ways-sleep.md b/.changeset/violet-ways-sleep.md
deleted file mode 100644
index 749ecb1719..0000000000
--- a/.changeset/violet-ways-sleep.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': minor
----
-
-feat: ignore component options in `compileModule`
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 766c83763a..f939f69d28 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,15 @@
# svelte
+## 5.37.0
+
+### Minor Changes
+
+- feat: ignore component options in `compileModule` ([#16362](https://github.com/sveltejs/svelte/pull/16362))
+
+### Patch Changes
+
+- fix: always mark props as stateful ([#16504](https://github.com/sveltejs/svelte/pull/16504))
+
## 5.36.17
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 22bc8cc8fa..c781eda8c8 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.36.17",
+ "version": "5.37.0",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index d2992a3dcd..e83ba6fb30 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.36.17';
+export const VERSION = '5.37.0';
export const PUBLIC_VERSION = '5';
From 1773df94845bc53d55a1c9e9eb033d08056c845a Mon Sep 17 00:00:00 2001
From: fkobi
Date: Sun, 27 Jul 2025 14:17:50 +0000
Subject: [PATCH 35/90] docs: use sh instead of bash in source blocks (#16506)
* use sh instead of bash in source blocks
no bash-specific functionality is used
* regenerate
---------
Co-authored-by: Rich Harris
---
CONTRIBUTING.md | 4 ++--
documentation/docs/01-introduction/02-getting-started.md | 2 +-
documentation/docs/07-misc/02-testing.md | 4 ++--
.../docs/98-reference/.generated/compile-warnings.md | 2 +-
packages/svelte/README.md | 2 +-
packages/svelte/messages/compile-warnings/template.md | 2 +-
6 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c2d3e45049..0653b08b76 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -101,13 +101,13 @@ Test samples are kept in `/test/xxx/samples` folder.
1. To run test, run `pnpm test`.
1. To run a particular test suite, use `pnpm test `, for example:
- ```bash
+ ```sh
pnpm test validator
```
1. To filter tests _within_ a test suite, use `pnpm test -t `, for example:
- ```bash
+ ```sh
pnpm test validator -t a11y-alt-text
```
diff --git a/documentation/docs/01-introduction/02-getting-started.md b/documentation/docs/01-introduction/02-getting-started.md
index c7351729ff..e97a46ad34 100644
--- a/documentation/docs/01-introduction/02-getting-started.md
+++ b/documentation/docs/01-introduction/02-getting-started.md
@@ -4,7 +4,7 @@ title: Getting started
We recommend using [SvelteKit](../kit), which lets you [build almost anything](../kit/project-types). It's the official application framework from the Svelte team and powered by [Vite](https://vite.dev/). Create a new project with:
-```bash
+```sh
npx sv create myapp
cd myapp
npm install
diff --git a/documentation/docs/07-misc/02-testing.md b/documentation/docs/07-misc/02-testing.md
index db99b70770..bcec4db0a3 100644
--- a/documentation/docs/07-misc/02-testing.md
+++ b/documentation/docs/07-misc/02-testing.md
@@ -10,7 +10,7 @@ Unit tests allow you to test small isolated parts of your code. Integration test
To setup Vitest manually, first install it:
-```bash
+```sh
npm install -D vitest
```
@@ -166,7 +166,7 @@ It is possible to test your components in isolation using Vitest.
To get started, install jsdom (a library that shims DOM APIs):
-```bash
+```sh
npm install -D jsdom
```
diff --git a/documentation/docs/98-reference/.generated/compile-warnings.md b/documentation/docs/98-reference/.generated/compile-warnings.md
index 2af9021a6a..01003f30c5 100644
--- a/documentation/docs/98-reference/.generated/compile-warnings.md
+++ b/documentation/docs/98-reference/.generated/compile-warnings.md
@@ -683,7 +683,7 @@ Some templating languages (including Svelte) will 'fix' HTML by turning `
Date: Sun, 27 Jul 2025 19:50:22 +0200
Subject: [PATCH 36/90] fix: `append_styles` in an effect to make them
available on mount (#16509)
Co-authored-by: Rich Harris
---
.changeset/shaggy-comics-fail.md | 5 +++++
.changeset/wise-hairs-pay.md | 5 +++++
.../src/compiler/phases/2-analyze/index.js | 16 +++++++-------
.../3-transform/client/transform-client.js | 5 +++--
.../svelte/src/internal/client/dom/css.js | 4 ++--
.../host-rune-access-injected-css/_config.js | 21 +++++++++++++++++++
.../host-rune-access-injected-css/main.svelte | 16 ++++++++++++++
.../Thing.svelte | 9 ++++++++
.../custom-element-injected-styles/_config.js | 15 +++++++++++++
.../main.svelte | 5 +++++
10 files changed, 90 insertions(+), 11 deletions(-)
create mode 100644 .changeset/shaggy-comics-fail.md
create mode 100644 .changeset/wise-hairs-pay.md
create mode 100644 packages/svelte/tests/runtime-browser/custom-elements-samples/host-rune-access-injected-css/_config.js
create mode 100644 packages/svelte/tests/runtime-browser/custom-elements-samples/host-rune-access-injected-css/main.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/Thing.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/main.svelte
diff --git a/.changeset/shaggy-comics-fail.md b/.changeset/shaggy-comics-fail.md
new file mode 100644
index 0000000000..981a25c978
--- /dev/null
+++ b/.changeset/shaggy-comics-fail.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: `append_styles` in an effect to make them available on mount
diff --git a/.changeset/wise-hairs-pay.md b/.changeset/wise-hairs-pay.md
new file mode 100644
index 0000000000..7d96c1daab
--- /dev/null
+++ b/.changeset/wise-hairs-pay.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: always inject styles when compiling as a custom element
diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js
index d407b44556..cd44fd998a 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/index.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/index.js
@@ -451,6 +451,8 @@ export function analyze_component(root, source, options) {
}
}
+ const is_custom_element = !!options.customElementOptions || options.customElement;
+
// TODO remove all the ?? stuff, we don't need it now that we're validating the config
/** @type {ComponentAnalysis} */
const analysis = {
@@ -500,13 +502,13 @@ export function analyze_component(root, source, options) {
needs_props: false,
event_directive_node: null,
uses_event_attributes: false,
- custom_element: options.customElementOptions ?? options.customElement,
- inject_styles: options.css === 'injected' || options.customElement,
- accessors: options.customElement
- ? true
- : (runes ? false : !!options.accessors) ||
- // because $set method needs accessors
- options.compatibility?.componentApi === 4,
+ custom_element: is_custom_element,
+ inject_styles: options.css === 'injected' || is_custom_element,
+ accessors:
+ is_custom_element ||
+ (runes ? false : !!options.accessors) ||
+ // because $set method needs accessors
+ options.compatibility?.componentApi === 4,
reactive_statements: new Map(),
binding_groups: new Map(),
slot_names: new Map(),
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
index 124438a9da..a56aca9c5f 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
@@ -621,8 +621,9 @@ export function client_component(analysis, options) {
);
}
- if (analysis.custom_element) {
- const ce = analysis.custom_element;
+ const ce = options.customElementOptions ?? options.customElement;
+
+ if (ce) {
const ce_props = typeof ce === 'boolean' ? {} : ce.props || {};
/** @type {ESTree.Property[]} */
diff --git a/packages/svelte/src/internal/client/dom/css.js b/packages/svelte/src/internal/client/dom/css.js
index 52be36aa1f..8e6faa0e32 100644
--- a/packages/svelte/src/internal/client/dom/css.js
+++ b/packages/svelte/src/internal/client/dom/css.js
@@ -1,6 +1,6 @@
import { DEV } from 'esm-env';
-import { queue_micro_task } from './task.js';
import { register_style } from '../dev/css.js';
+import { effect } from '../reactivity/effects.js';
/**
* @param {Node} anchor
@@ -8,7 +8,7 @@ import { register_style } from '../dev/css.js';
*/
export function append_styles(anchor, css) {
// Use `queue_micro_task` to ensure `anchor` is in the DOM, otherwise getRootNode() will yield wrong results
- queue_micro_task(() => {
+ effect(() => {
var root = anchor.getRootNode();
var target = /** @type {ShadowRoot} */ (root).host
diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/host-rune-access-injected-css/_config.js b/packages/svelte/tests/runtime-browser/custom-elements-samples/host-rune-access-injected-css/_config.js
new file mode 100644
index 0000000000..99a223492b
--- /dev/null
+++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/host-rune-access-injected-css/_config.js
@@ -0,0 +1,21 @@
+import { test } from '../../assert';
+const tick = () => Promise.resolve();
+
+export default test({
+ async test({ assert, target }) {
+ target.innerHTML = ' ';
+ /** @type {any} */
+ const el = target.querySelector('custom-element');
+
+ /** @type {string} */
+ let html = '';
+ const handle_evt = (e) => (html = e.detail);
+ el.addEventListener('html', handle_evt);
+
+ await tick();
+ await tick();
+ await tick();
+
+ assert.ok(html.includes('
+
+
+
+btn
+
+
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/Thing.svelte b/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/Thing.svelte
new file mode 100644
index 0000000000..0a2b139274
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/Thing.svelte
@@ -0,0 +1,9 @@
+
+
+hello
+
+
diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/_config.js b/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/_config.js
new file mode 100644
index 0000000000..74597504bd
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/_config.js
@@ -0,0 +1,15 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ mode: ['client'],
+ async test({ assert, target }) {
+ const thing = /** @type HTMLElement & { object: { test: true }; } */ (
+ target.querySelector('my-thing')
+ );
+
+ await tick();
+
+ assert.include(thing.shadowRoot?.innerHTML, 'red');
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/main.svelte b/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/main.svelte
new file mode 100644
index 0000000000..ba5b788da9
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/main.svelte
@@ -0,0 +1,5 @@
+
+
+
From 51771447c6cf941da7a07733435a5b18a91ddd28 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Sun, 27 Jul 2025 16:59:29 -0400
Subject: [PATCH 37/90] chore: remove `parser.template_untrimmed` (#16511)
---
.changeset/shiny-berries-call.md | 5 +++++
packages/svelte/src/compiler/phases/1-parse/index.js | 7 -------
.../svelte/src/compiler/phases/1-parse/state/element.js | 8 --------
3 files changed, 5 insertions(+), 15 deletions(-)
create mode 100644 .changeset/shiny-berries-call.md
diff --git a/.changeset/shiny-berries-call.md b/.changeset/shiny-berries-call.md
new file mode 100644
index 0000000000..adc62b3cd0
--- /dev/null
+++ b/.changeset/shiny-berries-call.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+chore: remove `parser.template_untrimmed`
diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js
index 77cc2bf3fa..f5e0693b31 100644
--- a/packages/svelte/src/compiler/phases/1-parse/index.js
+++ b/packages/svelte/src/compiler/phases/1-parse/index.js
@@ -23,12 +23,6 @@ export class Parser {
*/
template;
- /**
- * @readonly
- * @type {string}
- */
- template_untrimmed;
-
/**
* Whether or not we're in loose parsing mode, in which
* case we try to continue parsing as much as possible
@@ -67,7 +61,6 @@ export class Parser {
}
this.loose = loose;
- this.template_untrimmed = template;
this.template = template.trimEnd();
let match_lang;
diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js
index 87332f647d..ed1b047d55 100644
--- a/packages/svelte/src/compiler/phases/1-parse/state/element.js
+++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js
@@ -370,14 +370,6 @@ export default function element(parser) {
// ... or we're followed by whitespace, for example near the end of the template,
// which we want to take in so that language tools has more room to work with
parser.allow_whitespace();
- if (parser.index === parser.template.length) {
- while (
- parser.index < parser.template_untrimmed.length &&
- regex_whitespace.test(parser.template_untrimmed[parser.index])
- ) {
- parser.index++;
- }
- }
}
}
}
From 03f2e44757925d0ca53f4e563965c3135ef81f16 Mon Sep 17 00:00:00 2001
From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com>
Date: Mon, 28 Jul 2025 11:56:41 -0700
Subject: [PATCH 38/90] chore: remove some todos (#16515)
* chore: remove some todos
* fix
* fix
* doh
* convert `TemplateComment` back to `Comment`
* `Evaluation.has_unknown`
---
.changeset/happy-countries-dance.md | 5 +++++
packages/svelte/src/compiler/migrate/index.js | 4 ++--
packages/svelte/src/compiler/phases/1-parse/index.js | 1 -
.../3-transform/client/visitors/CallExpression.js | 4 +++-
.../3-transform/client/visitors/OnDirective.js | 4 ++--
packages/svelte/src/compiler/phases/scope.js | 11 +++++++++++
packages/svelte/src/compiler/types/template.d.ts | 12 +++++++++++-
packages/svelte/types/index.d.ts | 12 +++++++++++-
8 files changed, 45 insertions(+), 8 deletions(-)
create mode 100644 .changeset/happy-countries-dance.md
diff --git a/.changeset/happy-countries-dance.md b/.changeset/happy-countries-dance.md
new file mode 100644
index 0000000000..641cf21fd8
--- /dev/null
+++ b/.changeset/happy-countries-dance.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+chore: remove some todos
diff --git a/packages/svelte/src/compiler/migrate/index.js b/packages/svelte/src/compiler/migrate/index.js
index 6b2e6cda70..eb0e4eff8c 100644
--- a/packages/svelte/src/compiler/migrate/index.js
+++ b/packages/svelte/src/compiler/migrate/index.js
@@ -1707,14 +1707,14 @@ function extract_type_and_comment(declarator, state, path) {
}
// Ensure modifiers are applied in the same order as Svelte 4
-const modifier_order = [
+const modifier_order = /** @type {const} */ ([
'preventDefault',
'stopPropagation',
'stopImmediatePropagation',
'self',
'trusted',
'once'
-];
+]);
/**
* @param {AST.RegularElement | AST.SvelteElement | AST.SvelteWindow | AST.SvelteDocument | AST.SvelteBody} element
diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js
index f5e0693b31..8f7ef76be5 100644
--- a/packages/svelte/src/compiler/phases/1-parse/index.js
+++ b/packages/svelte/src/compiler/phases/1-parse/index.js
@@ -1,5 +1,4 @@
/** @import { AST } from '#compiler' */
-/** @import { Comment } from 'estree' */
// @ts-expect-error acorn type definitions are borked in the release we use
import { isIdentifierStart, isIdentifierChar } from 'acorn';
import fragment from './state/fragment.js';
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js
index 3e2f1414e6..c126742d3c 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js
@@ -82,7 +82,9 @@ export function CallExpression(node, context) {
['debug', 'dir', 'error', 'group', 'groupCollapsed', 'info', 'log', 'trace', 'warn'].includes(
node.callee.property.name
) &&
- node.arguments.some((arg) => arg.type !== 'Literal') // TODO more cases?
+ node.arguments.some(
+ (arg) => arg.type === 'SpreadElement' || context.state.scope.evaluate(arg).has_unknown
+ )
) {
return b.call(
node.callee,
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/OnDirective.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/OnDirective.js
index 7a66a8ecbb..0ee3b0fb10 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/OnDirective.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/OnDirective.js
@@ -3,14 +3,14 @@
import * as b from '#compiler/builders';
import { build_event, build_event_handler } from './shared/events.js';
-const modifiers = [
+const modifiers = /** @type {const} */ ([
'stopPropagation',
'stopImmediatePropagation',
'preventDefault',
'self',
'trusted',
'once'
-];
+]);
/**
* @param {AST.OnDirective} node
diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js
index 700e098e45..eaacf5dda9 100644
--- a/packages/svelte/src/compiler/phases/scope.js
+++ b/packages/svelte/src/compiler/phases/scope.js
@@ -180,6 +180,13 @@ class Evaluation {
*/
is_known = true;
+ /**
+ * True if the possible values contains `UNKNOWN`
+ * @readonly
+ * @type {boolean}
+ */
+ has_unknown = false;
+
/**
* True if the value is known to not be null/undefined
* @readonly
@@ -540,6 +547,10 @@ class Evaluation {
if (value == null || value === UNKNOWN) {
this.is_defined = false;
}
+
+ if (value === UNKNOWN) {
+ this.has_unknown = true;
+ }
}
if (this.values.size > 1 || typeof this.value === 'symbol') {
diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts
index de06a41469..058a1a8e66 100644
--- a/packages/svelte/src/compiler/types/template.d.ts
+++ b/packages/svelte/src/compiler/types/template.d.ts
@@ -247,7 +247,17 @@ export namespace AST {
name: string;
/** The 'y' in `on:x={y}` */
expression: null | Expression;
- modifiers: string[]; // TODO specify
+ modifiers: Array<
+ | 'capture'
+ | 'nonpassive'
+ | 'once'
+ | 'passive'
+ | 'preventDefault'
+ | 'self'
+ | 'stopImmediatePropagation'
+ | 'stopPropagation'
+ | 'trusted'
+ >;
/** @internal */
metadata: {
expression: ExpressionMetadata;
diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts
index 9ea45af7e6..64aa9e23ba 100644
--- a/packages/svelte/types/index.d.ts
+++ b/packages/svelte/types/index.d.ts
@@ -1296,7 +1296,17 @@ declare module 'svelte/compiler' {
name: string;
/** The 'y' in `on:x={y}` */
expression: null | Expression;
- modifiers: string[];
+ modifiers: Array<
+ | 'capture'
+ | 'nonpassive'
+ | 'once'
+ | 'passive'
+ | 'preventDefault'
+ | 'self'
+ | 'stopImmediatePropagation'
+ | 'stopPropagation'
+ | 'trusted'
+ >;
}
/** A `style:` directive */
From 48f2fa22c0c941ac6e550151212845adc628b3de Mon Sep 17 00:00:00 2001
From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com>
Date: Mon, 28 Jul 2025 12:04:06 -0700
Subject: [PATCH 39/90] fix: allow await expressions inside `{#await ...}`
argument (#16514)
* fix: allow await expressions inside `{#await ...}` argument
* add test
* apply suggestion from review
---------
Co-authored-by: 7nik
---
.changeset/long-roses-train.md | 5 +++
.../3-transform/client/visitors/AwaitBlock.js | 7 ++-
.../samples/async-await/_config.js | 43 +++++++++++++++++++
.../samples/async-await/main.svelte | 22 ++++++++++
4 files changed, 75 insertions(+), 2 deletions(-)
create mode 100644 .changeset/long-roses-train.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-await/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-await/main.svelte
diff --git a/.changeset/long-roses-train.md b/.changeset/long-roses-train.md
new file mode 100644
index 0000000000..10920ac5c4
--- /dev/null
+++ b/.changeset/long-roses-train.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: allow await expressions inside `{#await ...}` argument
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js
index c550c8e17b..4246091bcf 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js
@@ -1,7 +1,7 @@
/** @import { BlockStatement, Pattern, Statement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
-import { extract_identifiers } from '../../../../utils/ast.js';
+import { extract_identifiers, is_expression_async } from '../../../../utils/ast.js';
import * as b from '#compiler/builders';
import { create_derived } from '../utils.js';
import { get_value } from './shared/declarations.js';
@@ -15,7 +15,10 @@ export function AwaitBlock(node, context) {
context.state.template.push_comment();
// Visit {#await } first to ensure that scopes are in the correct order
- const expression = b.thunk(build_expression(context, node.expression, node.metadata.expression));
+ const expression = b.thunk(
+ build_expression(context, node.expression, node.metadata.expression),
+ node.metadata.expression.has_await
+ );
let then_block;
let catch_block;
diff --git a/packages/svelte/tests/runtime-runes/samples/async-await/_config.js b/packages/svelte/tests/runtime-runes/samples/async-await/_config.js
new file mode 100644
index 0000000000..dda6a7a895
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-await/_config.js
@@ -0,0 +1,43 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target }) {
+ const [reset, one, two, reject] = target.querySelectorAll('button');
+
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ 'reset one two reject waiting'
+ );
+
+ one.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ 'reset one two reject one_res'
+ );
+
+ reset.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ 'reset one two reject waiting'
+ );
+
+ two.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ 'reset one two reject two_res'
+ );
+
+ reset.click();
+ reject.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ 'reset one two reject reject_catch'
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-await/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-await/main.svelte
new file mode 100644
index 0000000000..8673e45414
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-await/main.svelte
@@ -0,0 +1,22 @@
+
+
+ deferred = Promise.withResolvers()}>reset
+ deferred.resolve("one")}>one
+ deferred.resolve("two")}>two
+ deferred.reject("reject")}>reject
+
+
+ {#await await deferred.promise + "_res"}
+ waiting
+ {:then res}
+ {res}
+ {:catch err}
+ {err}_catch
+ {/await}
+
+ {#snippet pending()}
+ pending
+ {/snippet}
+
From d82edf6b1dfaac8af70462b1009f3b9da47a701a Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 28 Jul 2025 12:07:57 -0700
Subject: [PATCH 40/90] Version Packages (#16513)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/happy-countries-dance.md | 5 -----
.changeset/long-roses-train.md | 5 -----
.changeset/shaggy-comics-fail.md | 5 -----
.changeset/shiny-berries-call.md | 5 -----
.changeset/wise-hairs-pay.md | 5 -----
packages/svelte/CHANGELOG.md | 14 ++++++++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
8 files changed, 16 insertions(+), 27 deletions(-)
delete mode 100644 .changeset/happy-countries-dance.md
delete mode 100644 .changeset/long-roses-train.md
delete mode 100644 .changeset/shaggy-comics-fail.md
delete mode 100644 .changeset/shiny-berries-call.md
delete mode 100644 .changeset/wise-hairs-pay.md
diff --git a/.changeset/happy-countries-dance.md b/.changeset/happy-countries-dance.md
deleted file mode 100644
index 641cf21fd8..0000000000
--- a/.changeset/happy-countries-dance.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-chore: remove some todos
diff --git a/.changeset/long-roses-train.md b/.changeset/long-roses-train.md
deleted file mode 100644
index 10920ac5c4..0000000000
--- a/.changeset/long-roses-train.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: allow await expressions inside `{#await ...}` argument
diff --git a/.changeset/shaggy-comics-fail.md b/.changeset/shaggy-comics-fail.md
deleted file mode 100644
index 981a25c978..0000000000
--- a/.changeset/shaggy-comics-fail.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: `append_styles` in an effect to make them available on mount
diff --git a/.changeset/shiny-berries-call.md b/.changeset/shiny-berries-call.md
deleted file mode 100644
index adc62b3cd0..0000000000
--- a/.changeset/shiny-berries-call.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-chore: remove `parser.template_untrimmed`
diff --git a/.changeset/wise-hairs-pay.md b/.changeset/wise-hairs-pay.md
deleted file mode 100644
index 7d96c1daab..0000000000
--- a/.changeset/wise-hairs-pay.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: always inject styles when compiling as a custom element
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index f939f69d28..59c44dd4f9 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,19 @@
# svelte
+## 5.37.1
+
+### Patch Changes
+
+- chore: remove some todos ([#16515](https://github.com/sveltejs/svelte/pull/16515))
+
+- fix: allow await expressions inside `{#await ...}` argument ([#16514](https://github.com/sveltejs/svelte/pull/16514))
+
+- fix: `append_styles` in an effect to make them available on mount ([#16509](https://github.com/sveltejs/svelte/pull/16509))
+
+- chore: remove `parser.template_untrimmed` ([#16511](https://github.com/sveltejs/svelte/pull/16511))
+
+- fix: always inject styles when compiling as a custom element ([#16509](https://github.com/sveltejs/svelte/pull/16509))
+
## 5.37.0
### Minor Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index c781eda8c8..826850ea60 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.37.0",
+ "version": "5.37.1",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index e83ba6fb30..2d5083a2a5 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.37.0';
+export const VERSION = '5.37.1';
export const PUBLIC_VERSION = '5';
From a91e0154d4bc4b0a3209956647f868e518d0acba Mon Sep 17 00:00:00 2001
From: Laszlo Korte
Date: Thu, 31 Jul 2025 09:45:52 +0200
Subject: [PATCH 41/90] fix: double event processing in Firefox (#16522)
(#16527)
* Fix double event processing (#16522)
Firefox seems to garbage collect event objects during event propagation if no global reference to the event object is kept.
That discards the __root marker set on the event object to early,
leading to duplicate processing.
* minor tweaks
---------
Co-authored-by: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com>
---
.changeset/chilly-bananas-train.md | 5 +++++
.../svelte/src/internal/client/dom/elements/events.js | 9 +++++++++
2 files changed, 14 insertions(+)
create mode 100644 .changeset/chilly-bananas-train.md
diff --git a/.changeset/chilly-bananas-train.md b/.changeset/chilly-bananas-train.md
new file mode 100644
index 0000000000..b0305f9e61
--- /dev/null
+++ b/.changeset/chilly-bananas-train.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: double event processing in firefox due to event object being garbage collected
diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js
index fa3bf0b021..19bd1cfa18 100644
--- a/packages/svelte/src/internal/client/dom/elements/events.js
+++ b/packages/svelte/src/internal/client/dom/elements/events.js
@@ -141,6 +141,13 @@ export function delegate(events) {
}
}
+// used to store the reference to the currently propagated event
+// to prevent garbage collection between microtasks in Firefox
+// If the event object is GCed too early, the expando __root property
+// set on the event object is lost, causing the event delegation
+// to process the event twice
+let last_propagated_event = null;
+
/**
* @this {EventTarget}
* @param {Event} event
@@ -153,6 +160,8 @@ export function handle_event_propagation(event) {
var path = event.composedPath?.() || [];
var current_target = /** @type {null | Element} */ (path[0] || event.target);
+ last_propagated_event = event;
+
// composedPath contains list of nodes the event has propagated through.
// We check __root to skip all nodes below it in case this is a
// parent of the __root node, which indicates that there's nested
From f5950f866c51d9325a432ebfd5362bcf2647763a Mon Sep 17 00:00:00 2001
From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com>
Date: Thu, 31 Jul 2025 02:11:52 -0700
Subject: [PATCH 42/90] fix: correctly differentiate static fields before
emitting `duplicate_class_field` (#16526)
* fix: correctly differentiate static fields before emitting `duplicate_class_field`
* remove unnecessary `#` concatenation
* add test
---
.changeset/nine-cups-film.md | 5 +++++
.../src/compiler/phases/2-analyze/visitors/ClassBody.js | 6 +++---
.../samples/class-state-constructor-9/input.svelte.js | 2 +-
3 files changed, 9 insertions(+), 4 deletions(-)
create mode 100644 .changeset/nine-cups-film.md
diff --git a/.changeset/nine-cups-film.md b/.changeset/nine-cups-film.md
new file mode 100644
index 0000000000..ba72337dac
--- /dev/null
+++ b/.changeset/nine-cups-film.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: correctly differentiate static fields before emitting `duplicate_class_field`
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js
index 2bfc1dbce3..dd21637174 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js
@@ -57,7 +57,7 @@ export function ClassBody(node, context) {
e.state_field_duplicate(node, name);
}
- const _key = (key.type === 'PrivateIdentifier' ? '#' : '') + name;
+ const _key = (node.type === 'AssignmentExpression' || !node.static ? '' : '@') + name;
const field = fields.get(_key);
// if there's already a method or assigned field, error
@@ -78,7 +78,7 @@ export function ClassBody(node, context) {
for (const child of node.body) {
if (child.type === 'PropertyDefinition' && !child.computed && !child.static) {
handle(child, child.key, child.value);
- const key = (child.key.type === 'PrivateIdentifier' ? '#' : '') + get_name(child.key);
+ const key = /** @type {string} */ (get_name(child.key));
const field = fields.get(key);
if (!field) {
fields.set(key, [child.value ? 'assigned_prop' : 'prop']);
@@ -91,7 +91,7 @@ export function ClassBody(node, context) {
if (child.kind === 'constructor') {
constructor = child;
} else if (!child.computed) {
- const key = (child.key.type === 'PrivateIdentifier' ? '#' : '') + get_name(child.key);
+ const key = (child.static ? '@' : '') + get_name(child.key);
const field = fields.get(key);
if (!field) {
fields.set(key, [child.kind]);
diff --git a/packages/svelte/tests/validator/samples/class-state-constructor-9/input.svelte.js b/packages/svelte/tests/validator/samples/class-state-constructor-9/input.svelte.js
index a8469e13af..3806046f3f 100644
--- a/packages/svelte/tests/validator/samples/class-state-constructor-9/input.svelte.js
+++ b/packages/svelte/tests/validator/samples/class-state-constructor-9/input.svelte.js
@@ -1,6 +1,6 @@
export class Counter {
count = -1;
-
+ static count() {}
constructor() {
this.count = $state(0);
}
From 72e46d31c075eeea232a1cc320efc272f5ef87d7 Mon Sep 17 00:00:00 2001
From: Elliot Bentley
Date: Thu, 31 Jul 2025 11:34:28 +0100
Subject: [PATCH 43/90] fix: add types for SVG bindable attributes (#16525)
* fix: types for SVG bind: attributes
* add changeset and tweak
---------
Co-authored-by: 7nik
---
.changeset/cuddly-feet-doubt.md | 5 +++++
packages/svelte/elements.d.ts | 14 ++++++++------
2 files changed, 13 insertions(+), 6 deletions(-)
create mode 100644 .changeset/cuddly-feet-doubt.md
diff --git a/.changeset/cuddly-feet-doubt.md b/.changeset/cuddly-feet-doubt.md
new file mode 100644
index 0000000000..8d7d955daa
--- /dev/null
+++ b/.changeset/cuddly-feet-doubt.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: add bindable dimension attributes types to SVG and MathML elements
diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts
index 2e1042dfd6..b3d44d9ed6 100644
--- a/packages/svelte/elements.d.ts
+++ b/packages/svelte/elements.d.ts
@@ -464,6 +464,14 @@ export interface DOMAttributes {
onfullscreenerror?: EventHandler | undefined | null;
onfullscreenerrorcapture?: EventHandler | undefined | null;
+ // Dimensions
+ readonly 'bind:contentRect'?: DOMRectReadOnly | undefined | null;
+ readonly 'bind:contentBoxSize'?: Array | undefined | null;
+ readonly 'bind:borderBoxSize'?: Array | undefined | null;
+ readonly 'bind:devicePixelContentBoxSize'?: Array | undefined | null;
+ readonly 'bind:clientWidth'?: number | undefined | null;
+ readonly 'bind:clientHeight'?: number | undefined | null;
+
xmlns?: string | undefined | null;
}
@@ -839,13 +847,7 @@ export interface HTMLAttributes extends AriaAttributes, D
*/
'bind:innerText'?: string | undefined | null;
- readonly 'bind:contentRect'?: DOMRectReadOnly | undefined | null;
- readonly 'bind:contentBoxSize'?: Array | undefined | null;
- readonly 'bind:borderBoxSize'?: Array | undefined | null;
- readonly 'bind:devicePixelContentBoxSize'?: Array | undefined | null;
readonly 'bind:focused'?: boolean | undefined | null;
- readonly 'bind:clientWidth'?: number | undefined | null;
- readonly 'bind:clientHeight'?: number | undefined | null;
readonly 'bind:offsetWidth'?: number | undefined | null;
readonly 'bind:offsetHeight'?: number | undefined | null;
From c04975d3db74e59f8612ffd0f64d3b3bb3133ef1 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Thu, 31 Jul 2025 18:28:01 -0400
Subject: [PATCH 44/90] fix: prevent last_propagated_event from being DCE'd
(#16538)
* fix: prevent last_propagated_event from being DCE'd
* changeset
---
.changeset/serious-cars-hear.md | 5 +++++
packages/svelte/src/internal/client/dom/elements/events.js | 5 ++++-
2 files changed, 9 insertions(+), 1 deletion(-)
create mode 100644 .changeset/serious-cars-hear.md
diff --git a/.changeset/serious-cars-hear.md b/.changeset/serious-cars-hear.md
new file mode 100644
index 0000000000..4647e89ed9
--- /dev/null
+++ b/.changeset/serious-cars-hear.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: prevent last_propagated_event from being DCE'd
diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js
index 19bd1cfa18..15544d7426 100644
--- a/packages/svelte/src/internal/client/dom/elements/events.js
+++ b/packages/svelte/src/internal/client/dom/elements/events.js
@@ -168,8 +168,11 @@ export function handle_event_propagation(event) {
// mounted apps. In this case we don't want to trigger events multiple times.
var path_idx = 0;
+ // the `last_propagated_event === event` check is redundant, but
+ // without it the variable will be DCE'd and things will
+ // fail mysteriously in Firefox
// @ts-expect-error is added below
- var handled_at = event.__root;
+ var handled_at = last_propagated_event === event && event.__root;
if (handled_at) {
var at_idx = path.indexOf(handled_at);
From 9134856fece750608ba81da45c4a330b3d8ebb1e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 31 Jul 2025 19:15:45 -0400
Subject: [PATCH 45/90] Version Packages (#16529)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/chilly-bananas-train.md | 5 -----
.changeset/cuddly-feet-doubt.md | 5 -----
.changeset/nine-cups-film.md | 5 -----
.changeset/serious-cars-hear.md | 5 -----
packages/svelte/CHANGELOG.md | 12 ++++++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
7 files changed, 14 insertions(+), 22 deletions(-)
delete mode 100644 .changeset/chilly-bananas-train.md
delete mode 100644 .changeset/cuddly-feet-doubt.md
delete mode 100644 .changeset/nine-cups-film.md
delete mode 100644 .changeset/serious-cars-hear.md
diff --git a/.changeset/chilly-bananas-train.md b/.changeset/chilly-bananas-train.md
deleted file mode 100644
index b0305f9e61..0000000000
--- a/.changeset/chilly-bananas-train.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: double event processing in firefox due to event object being garbage collected
diff --git a/.changeset/cuddly-feet-doubt.md b/.changeset/cuddly-feet-doubt.md
deleted file mode 100644
index 8d7d955daa..0000000000
--- a/.changeset/cuddly-feet-doubt.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: add bindable dimension attributes types to SVG and MathML elements
diff --git a/.changeset/nine-cups-film.md b/.changeset/nine-cups-film.md
deleted file mode 100644
index ba72337dac..0000000000
--- a/.changeset/nine-cups-film.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: correctly differentiate static fields before emitting `duplicate_class_field`
diff --git a/.changeset/serious-cars-hear.md b/.changeset/serious-cars-hear.md
deleted file mode 100644
index 4647e89ed9..0000000000
--- a/.changeset/serious-cars-hear.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: prevent last_propagated_event from being DCE'd
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 59c44dd4f9..6e0a199ddc 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,17 @@
# svelte
+## 5.37.2
+
+### Patch Changes
+
+- fix: double event processing in firefox due to event object being garbage collected ([#16527](https://github.com/sveltejs/svelte/pull/16527))
+
+- fix: add bindable dimension attributes types to SVG and MathML elements ([#16525](https://github.com/sveltejs/svelte/pull/16525))
+
+- fix: correctly differentiate static fields before emitting `duplicate_class_field` ([#16526](https://github.com/sveltejs/svelte/pull/16526))
+
+- fix: prevent last_propagated_event from being DCE'd ([#16538](https://github.com/sveltejs/svelte/pull/16538))
+
## 5.37.1
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 826850ea60..8536135ca8 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.37.1",
+ "version": "5.37.2",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 2d5083a2a5..b8f3bcfdd5 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.37.1';
+export const VERSION = '5.37.2';
export const PUBLIC_VERSION = '5';
From 80678411706e99c0224b8bf995959c2d4e7fd4db Mon Sep 17 00:00:00 2001
From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com>
Date: Thu, 31 Jul 2025 16:16:47 -0700
Subject: [PATCH 46/90] docs: add note about attachments to `svelte/action`
docs (#16537)
---
documentation/docs/98-reference/21-svelte-action.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/documentation/docs/98-reference/21-svelte-action.md b/documentation/docs/98-reference/21-svelte-action.md
index 53423ec409..ef3ebfbf70 100644
--- a/documentation/docs/98-reference/21-svelte-action.md
+++ b/documentation/docs/98-reference/21-svelte-action.md
@@ -2,4 +2,6 @@
title: svelte/action
---
+This module provides types for [actions](use), which have been superseded by [attachments](@attach).
+
> MODULE: svelte/action
From 540f1b19ec56a69dde85f24471f25d9d465937e4 Mon Sep 17 00:00:00 2001
From: 7nik
Date: Sat, 2 Aug 2025 22:29:10 +0300
Subject: [PATCH 47/90] fix: reset attribute cache after setting corresponding
property (#16543)
---
.changeset/afraid-carrots-study.md | 5 +++++
.../client/dom/elements/attributes.js | 4 +++-
.../attribute-after-property/_config.js | 19 +++++++++++++++++++
.../attribute-after-property/main.svelte | 6 ++++++
4 files changed, 33 insertions(+), 1 deletion(-)
create mode 100644 .changeset/afraid-carrots-study.md
create mode 100644 packages/svelte/tests/runtime-legacy/samples/attribute-after-property/_config.js
create mode 100644 packages/svelte/tests/runtime-legacy/samples/attribute-after-property/main.svelte
diff --git a/.changeset/afraid-carrots-study.md b/.changeset/afraid-carrots-study.md
new file mode 100644
index 0000000000..71f4232edb
--- /dev/null
+++ b/.changeset/afraid-carrots-study.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: reset attribute cache after setting corresponding property
diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js
index 22e532f5e4..2fa5d4541c 100644
--- a/packages/svelte/src/internal/client/dom/elements/attributes.js
+++ b/packages/svelte/src/internal/client/dom/elements/attributes.js
@@ -19,7 +19,7 @@ import { attach } from './attachments.js';
import { clsx } from '../../../shared/attributes.js';
import { set_class } from './class.js';
import { set_style } from './style.js';
-import { ATTACHMENT_KEY, NAMESPACE_HTML } from '../../../../constants.js';
+import { ATTACHMENT_KEY, NAMESPACE_HTML, UNINITIALIZED } from '../../../../constants.js';
import { block, branch, destroy_effect, effect } from '../../reactivity/effects.js';
import { init_select, select_option } from './bindings/select.js';
import { flatten } from '../../reactivity/async.js';
@@ -446,6 +446,8 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal
) {
// @ts-ignore
element[name] = value;
+ // remove it from attributes's cache
+ if (name in attributes) attributes[name] = UNINITIALIZED;
} else if (typeof value !== 'function') {
set_attribute(element, name, value, skip_warning);
}
diff --git a/packages/svelte/tests/runtime-legacy/samples/attribute-after-property/_config.js b/packages/svelte/tests/runtime-legacy/samples/attribute-after-property/_config.js
new file mode 100644
index 0000000000..f6a98b1797
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/attribute-after-property/_config.js
@@ -0,0 +1,19 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ test({ target, assert }) {
+ const input = target.querySelector('input');
+ const button = target.querySelector('button');
+
+ assert.equal(input?.step, 'any');
+
+ button?.click();
+ flushSync();
+ assert.equal(input?.step, '10');
+
+ button?.click();
+ flushSync();
+ assert.equal(input?.step, 'any');
+ }
+});
diff --git a/packages/svelte/tests/runtime-legacy/samples/attribute-after-property/main.svelte b/packages/svelte/tests/runtime-legacy/samples/attribute-after-property/main.svelte
new file mode 100644
index 0000000000..2921e4e241
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/attribute-after-property/main.svelte
@@ -0,0 +1,6 @@
+
+
+
+ step = step === "any" ? 10 : "any"}>change step
\ No newline at end of file
From 0cbb5becf6d62ae3334b78606a76d21a91eda32b Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Sat, 2 Aug 2025 21:17:58 -0700
Subject: [PATCH 48/90] Version Packages (#16544)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/afraid-carrots-study.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/afraid-carrots-study.md
diff --git a/.changeset/afraid-carrots-study.md b/.changeset/afraid-carrots-study.md
deleted file mode 100644
index 71f4232edb..0000000000
--- a/.changeset/afraid-carrots-study.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: reset attribute cache after setting corresponding property
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 6e0a199ddc..9766e3f06b 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
# svelte
+## 5.37.3
+
+### Patch Changes
+
+- fix: reset attribute cache after setting corresponding property ([#16543](https://github.com/sveltejs/svelte/pull/16543))
+
## 5.37.2
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 8536135ca8..e8927fcf56 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.37.2",
+ "version": "5.37.3",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index b8f3bcfdd5..127b43e205 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.37.2';
+export const VERSION = '5.37.3';
export const PUBLIC_VERSION = '5';
From a3f18351af4f70df4b9029aaa7198ac5dfeac6dc Mon Sep 17 00:00:00 2001
From: Anthony
Date: Tue, 5 Aug 2025 05:38:30 +0200
Subject: [PATCH 49/90] docs: corrections (#16550)
* Add missing hyphen in "server-side rendering"
* Fix incorrect use of "as such"
* regenerate types
---------
Co-authored-by: Chew Tee Ming
---
documentation/docs/02-runes/04-$effect.md | 2 +-
documentation/docs/03-template-syntax/06-snippet.md | 2 +-
documentation/docs/03-template-syntax/08-@html.md | 2 +-
.../docs/05-special-elements/07-svelte-options.md | 2 +-
documentation/docs/06-runtime/03-lifecycle-hooks.md | 2 +-
documentation/docs/07-misc/07-v5-migration-guide.md | 12 ++++++------
.../docs/98-reference/.generated/compile-errors.md | 2 +-
.../docs/98-reference/.generated/compile-warnings.md | 2 +-
packages/svelte/messages/compile-errors/script.md | 2 +-
.../svelte/messages/compile-warnings/template.md | 2 +-
packages/svelte/src/ambient.d.ts | 4 ++--
packages/svelte/types/index.d.ts | 4 ++--
12 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/documentation/docs/02-runes/04-$effect.md b/documentation/docs/02-runes/04-$effect.md
index 5820e178a0..6c42f55795 100644
--- a/documentation/docs/02-runes/04-$effect.md
+++ b/documentation/docs/02-runes/04-$effect.md
@@ -135,7 +135,7 @@ An effect only reruns when the object it reads changes, not when a property insi
An effect only depends on the values that it read the last time it ran. This has interesting implications for effects that have conditional code.
-For instance, if `condition` is `true` in the code snippet below, the code inside the `if` block will run and `color` will be evaluated. As such, changes to either `condition` or `color` [will cause the effect to re-run](/playground/untitled#H4sIAAAAAAAAE21RQW6DMBD8ytaNBJHaJFLViwNIVZ8RcnBgXVk1xsILTYT4e20TQg89IOPZ2fHM7siMaJBx9tmaWpFqjQNlAKXEihx7YVJpdIyfRkY3G4gB8Pi97cPanRtQU8AuwuF_eNUaQuPlOMtc1SlLRWlKUo1tOwJflUikQHZtA0klzCDc64Imx0ANn8bInV1CDhtHgjClrsftcSXotluLybOUb3g4JJHhOZs5WZpuIS9gjNqkJKQP5e2ClrR4SMdZ13E4xZ8zTPOTJU2A2uE_PQ9COCI926_hTVarIU4hu_REPlBrKq2q73ycrf1N-vS4TMUsulaVg3EtR8H9rFgsg8uUsT1B2F9eshigZHBRpuaD0D3mY8Qm2BfB5N2YyRzdNEYVDy0Ja-WsFjcOUuP1HvFLWA6H3XuHTUSmmDV2--0TXonxsKbp7G9C6R__NONS-MFNvxj_d6mBAgAA).
+For instance, if `condition` is `true` in the code snippet below, the code inside the `if` block will run and `color` will be evaluated. This means that changes to either `condition` or `color` [will cause the effect to re-run](/playground/untitled#H4sIAAAAAAAAE21RQW6DMBD8ytaNBJHaJFLViwNIVZ8RcnBgXVk1xsILTYT4e20TQg89IOPZ2fHM7siMaJBx9tmaWpFqjQNlAKXEihx7YVJpdIyfRkY3G4gB8Pi97cPanRtQU8AuwuF_eNUaQuPlOMtc1SlLRWlKUo1tOwJflUikQHZtA0klzCDc64Imx0ANn8bInV1CDhtHgjClrsftcSXotluLybOUb3g4JJHhOZs5WZpuIS9gjNqkJKQP5e2ClrR4SMdZ13E4xZ8zTPOTJU2A2uE_PQ9COCI926_hTVarIU4hu_REPlBrKq2q73ycrf1N-vS4TMUsulaVg3EtR8H9rFgsg8uUsT1B2F9eshigZHBRpuaD0D3mY8Qm2BfB5N2YyRzdNEYVDy0Ja-WsFjcOUuP1HvFLWA6H3XuHTUSmmDV2--0TXonxsKbp7G9C6R__NONS-MFNvxj_d6mBAgAA).
Conversely, if `condition` is `false`, `color` will not be evaluated, and the effect will _only_ re-run again when `condition` changes.
diff --git a/documentation/docs/03-template-syntax/06-snippet.md b/documentation/docs/03-template-syntax/06-snippet.md
index ab536c6e5c..02f58e0f6c 100644
--- a/documentation/docs/03-template-syntax/06-snippet.md
+++ b/documentation/docs/03-template-syntax/06-snippet.md
@@ -277,4 +277,4 @@ Snippets can be created programmatically with the [`createRawSnippet`](svelte#cr
## Snippets and slots
-In Svelte 4, content can be passed to components using [slots](legacy-slots). Snippets are more powerful and flexible, and as such slots are deprecated in Svelte 5.
+In Svelte 4, content can be passed to components using [slots](legacy-slots). Snippets are more powerful and flexible, and so slots have been deprecated in Svelte 5.
diff --git a/documentation/docs/03-template-syntax/08-@html.md b/documentation/docs/03-template-syntax/08-@html.md
index 30456fa666..a92accd093 100644
--- a/documentation/docs/03-template-syntax/08-@html.md
+++ b/documentation/docs/03-template-syntax/08-@html.md
@@ -22,7 +22,7 @@ It also will not compile Svelte code.
## Styling
-Content rendered this way is 'invisible' to Svelte and as such will not receive [scoped styles](scoped-styles) — in other words, this will not work, and the `a` and `img` styles will be regarded as unused:
+Content rendered this way is 'invisible' to Svelte and thus will not receive [scoped styles](scoped-styles). In other words, this will not work, and the `a` and `img` styles will be regarded as unused:
```svelte
diff --git a/documentation/docs/05-special-elements/07-svelte-options.md b/documentation/docs/05-special-elements/07-svelte-options.md
index d29042e278..a2e47aa04f 100644
--- a/documentation/docs/05-special-elements/07-svelte-options.md
+++ b/documentation/docs/05-special-elements/07-svelte-options.md
@@ -12,7 +12,7 @@ The `` element provides a place to specify per-component compile
- `runes={false}` — forces a component into _legacy mode_
- `namespace="..."` — the namespace where this component will be used, can be "html" (the default), "svg" or "mathml"
- `customElement={...}` — the [options](custom-elements#Component-options) to use when compiling this component as a custom element. If a string is passed, it is used as the `tag` option
-- `css="injected"` — the component will inject its styles inline: During server side rendering, it's injected as a `