From 5d9b4c66ca4df817c0c8146b71d30fbbdb958872 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 13 Oct 2025 20:25:35 +0200 Subject: [PATCH 01/15] fix: better input cursor restoration for `bind:value` (#16925) If cursor was at end and new input is longer, move cursor to new end No test because not possible to reproduce using our test setup. Follow-up to #14649, helps with #16577 --- .changeset/fair-aliens-wait.md | 5 +++++ .../internal/client/dom/elements/bindings/input.js | 12 ++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 .changeset/fair-aliens-wait.md diff --git a/.changeset/fair-aliens-wait.md b/.changeset/fair-aliens-wait.md new file mode 100644 index 0000000000..66e4cf2bd1 --- /dev/null +++ b/.changeset/fair-aliens-wait.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: better input cursor restoration for `bind:value` 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 815acde7c5..23ad6f5cdc 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/input.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/input.js @@ -43,14 +43,22 @@ export function bind_value(input, get, set = get) { if (value !== (value = get())) { var start = input.selectionStart; var end = input.selectionEnd; + var length = input.value.length; // the value is coerced on assignment input.value = value ?? ''; // Restore selection if (end !== null) { - input.selectionStart = start; - input.selectionEnd = Math.min(end, input.value.length); + var new_length = input.value.length; + // If cursor was at end and new input is longer, move cursor to new end + if (start === end && end === length && new_length > length) { + input.selectionStart = new_length; + input.selectionEnd = new_length; + } else { + input.selectionStart = start; + input.selectionEnd = Math.min(end, new_length); + } } } }); From 420468a04167bf30c745476a96942ca7bf7d1850 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 14:52:11 -0400 Subject: [PATCH 02/15] Version Packages (#16920) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/fair-aliens-wait.md | 5 ----- .changeset/rude-frogs-train.md | 5 ----- .changeset/spicy-rabbits-drive.md | 5 ----- .changeset/wicked-goats-begin.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/fair-aliens-wait.md delete mode 100644 .changeset/rude-frogs-train.md delete mode 100644 .changeset/spicy-rabbits-drive.md delete mode 100644 .changeset/wicked-goats-begin.md diff --git a/.changeset/fair-aliens-wait.md b/.changeset/fair-aliens-wait.md deleted file mode 100644 index 66e4cf2bd1..0000000000 --- a/.changeset/fair-aliens-wait.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: better input cursor restoration for `bind:value` diff --git a/.changeset/rude-frogs-train.md b/.changeset/rude-frogs-train.md deleted file mode 100644 index 06da5dcc1e..0000000000 --- a/.changeset/rude-frogs-train.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: track the user's getter of `bind:this` diff --git a/.changeset/spicy-rabbits-drive.md b/.changeset/spicy-rabbits-drive.md deleted file mode 100644 index 01834294e1..0000000000 --- a/.changeset/spicy-rabbits-drive.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: generate correct SSR code for the case where `pending` is an attribute diff --git a/.changeset/wicked-goats-begin.md b/.changeset/wicked-goats-begin.md deleted file mode 100644 index 04a22aa310..0000000000 --- a/.changeset/wicked-goats-begin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: generate correct code for `each` blocks with async body diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 2302acd033..70f549ce29 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # svelte +## 5.39.12 + +### Patch Changes + +- fix: better input cursor restoration for `bind:value` ([#16925](https://github.com/sveltejs/svelte/pull/16925)) + +- fix: track the user's getter of `bind:this` ([#16916](https://github.com/sveltejs/svelte/pull/16916)) + +- fix: generate correct SSR code for the case where `pending` is an attribute ([#16919](https://github.com/sveltejs/svelte/pull/16919)) + +- fix: generate correct code for `each` blocks with async body ([#16923](https://github.com/sveltejs/svelte/pull/16923)) + ## 5.39.11 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 822f19f001..a2d5a6e401 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.39.11", + "version": "5.39.12", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 0f723c4eb0..e520d1248a 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.39.11'; +export const VERSION = '5.39.12'; export const PUBLIC_VERSION = '5'; From b91f9de129d41c2674ff8bd818eb8bbcc984683e Mon Sep 17 00:00:00 2001 From: Hyunbin Seo <47051820+hyunbinseo@users.noreply.github.com> Date: Tue, 14 Oct 2025 04:19:55 +0900 Subject: [PATCH 03/15] docs: await no longer need pending (#16900) --- documentation/docs/05-special-elements/01-svelte-boundary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/05-special-elements/01-svelte-boundary.md b/documentation/docs/05-special-elements/01-svelte-boundary.md index 3e91af5d83..40e8d144e1 100644 --- a/documentation/docs/05-special-elements/01-svelte-boundary.md +++ b/documentation/docs/05-special-elements/01-svelte-boundary.md @@ -24,7 +24,7 @@ For the boundary to do anything, one or more of the following must be provided. ### `pending` -As of Svelte 5.36, boundaries with a `pending` snippet can contain [`await`](await-expressions) expressions. This snippet will be shown when the boundary is first created, and will remain visible until all the `await` expressions inside the boundary have resolved ([demo](/playground/untitled#H4sIAAAAAAAAE21QQW6DQAz8ytY9BKQVpFdKkPqDHnorPWzAaSwt3tWugUaIv1eE0KpKD5as8YxnNBOw6RAKKOOAVrA4up5bEy6VGknOyiO3xJ8qMnmPAhpOZDFC8T6BXPyiXADQ258X77P1FWg4moj_4Y1jQZZ49W0CealqruXUcyPkWLVozQXbZDC2R606spYiNo7bqA7qab_fp2paFLUElD6wYhzVa3AdRUySgNHZAVN1qDZaLRHljTp0vSTJ9XJjrSbpX5f0eZXN6zLXXOa_QfmurIVU-moyoyH5ib87o7XuYZfOZe6vnGWmx1uZW7lJOq9upa-sMwuUZdkmmfIbfQ1xZwwaBL8ECgk9zh8axJAdiVsoTsZGnL8Bg4tX_OMBAAA=)): +This snippet will be shown when the boundary is first created, and will remain visible until all the [`await`](await-expressions) expressions inside the boundary have resolved ([demo](/playground/untitled#H4sIAAAAAAAAE21QQW6DQAz8ytY9BKQVpFdKkPqDHnorPWzAaSwt3tWugUaIv1eE0KpKD5as8YxnNBOw6RAKKOOAVrA4up5bEy6VGknOyiO3xJ8qMnmPAhpOZDFC8T6BXPyiXADQ258X77P1FWg4moj_4Y1jQZZ49W0CealqruXUcyPkWLVozQXbZDC2R606spYiNo7bqA7qab_fp2paFLUElD6wYhzVa3AdRUySgNHZAVN1qDZaLRHljTp0vSTJ9XJjrSbpX5f0eZXN6zLXXOa_QfmurIVU-moyoyH5ib87o7XuYZfOZe6vnGWmx1uZW7lJOq9upa-sMwuUZdkmmfIbfQ1xZwwaBL8ECgk9zh8axJAdiVsoTsZGnL8Bg4tX_OMBAAA=)): ```svelte From ba782f9fdea1fd028a5e54abcb2c2ab05a67dcc7 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Mon, 13 Oct 2025 12:20:22 -0700 Subject: [PATCH 04/15] docs: link to custom renderer issue in Svelte Native discussion (#16896) --- documentation/docs/07-misc/99-faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/07-misc/99-faq.md b/documentation/docs/07-misc/99-faq.md index 1816998299..86489e87e4 100644 --- a/documentation/docs/07-misc/99-faq.md +++ b/documentation/docs/07-misc/99-faq.md @@ -99,7 +99,7 @@ However, you can use any router library. A sampling of available routers are hig While most mobile apps are written without using JavaScript, if you'd like to leverage your existing Svelte components and knowledge of Svelte when building mobile apps, you can turn a [SvelteKit SPA](https://kit.svelte.dev/docs/single-page-apps) into a mobile app with [Tauri](https://v2.tauri.app/start/frontend/sveltekit/) or [Capacitor](https://capacitorjs.com/solution/svelte). Mobile features like the camera, geolocation, and push notifications are available via plugins for both platforms. -Svelte Native was an option available for Svelte 4, but note that Svelte 5 does not currently support it. Svelte Native lets you write NativeScript apps using Svelte components that contain [NativeScript UI components](https://docs.nativescript.org/ui/) rather than DOM elements, which may be familiar for users coming from React Native. +Some work has been completed towards [custom renderer support in Svelte 5](https://github.com/sveltejs/svelte/issues/15470), but this feature is not yet available. The custom rendering API would support additional mobile frameworks like Lynx JS and Svelte Native. Svelte Native was an option available for Svelte 4, but Svelte 5 does not currently support it. Svelte Native lets you write NativeScript apps using Svelte components that contain [NativeScript UI components](https://docs.nativescript.org/ui/) rather than DOM elements, which may be familiar for users coming from React Native. ## Can I tell Svelte not to remove my unused styles? From b05e12fd63a9cf1957ac2ecd27440fc6248ac298 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 13 Oct 2025 15:30:58 -0400 Subject: [PATCH 05/15] fix code block (#16937) Updated code block syntax from Svelte to JavaScript for clarity. --- documentation/docs/02-runes/03-$derived.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/02-runes/03-$derived.md b/documentation/docs/02-runes/03-$derived.md index 0123868c4e..308693d19c 100644 --- a/documentation/docs/02-runes/03-$derived.md +++ b/documentation/docs/02-runes/03-$derived.md @@ -85,7 +85,7 @@ Derived expressions are recalculated when their dependencies change, but you can Unlike `$state`, which converts objects and arrays to [deeply reactive proxies]($state#Deep-state), `$derived` values are left as-is. For example, [in a case like this](/playground/untitled#H4sIAAAAAAAAE4VU22rjMBD9lUHd3aaQi9PdstS1A3t5XvpQ2Ic4D7I1iUUV2UjjNMX431eS7TRdSosxgjMzZ45mjt0yzffIYibvy0ojFJWqDKCQVBk2ZVup0LJ43TJ6rn2aBxw-FP2o67k9oCKP5dziW3hRaUJNjoYltjCyplWmM1JIIAn3FlL4ZIkTTtYez6jtj4w8WwyXv9GiIXiQxLVs9pfTMR7EuoSLIuLFbX7Z4930bZo_nBrD1bs834tlfvsBz9_SyX6PZXu9XaL4gOWn4sXjeyzftv4ZWfyxubpzxzg6LfD4MrooxELEosKCUPigQCMPKCZh0OtQE1iSxcsmdHuBvCiHZXALLXiN08EL3RRkaJ_kDVGle0HcSD5TPEeVtj67O4Nrg9aiSNtBY5oODJkrL5QsHtN2cgXp6nSJMWzpWWGasdlsGEMbzi5jPr5KFr0Ep7pdeM2-TCelCddIhDxAobi1jqF3cMaC1RKp64bAW9iFAmXGIHfd4wNXDabtOLN53w8W53VvJoZLh7xk4Rr3CoL-UNoLhWHrT1JQGcM17u96oES5K-kc2XOzkzqGCKL5De79OUTyyrg1zgwXsrEx3ESfx4Bz0M5UjVMHB24mw9SuXtXFoN13fYKOM1tyUT3FbvbWmSWCZX2Er-41u5xPoml45svRahl9Wb9aasbINJixDZwcPTbyTLZSUsAvrg_cPuCR7s782_WU8343Y72Qtlb8OYatwuOQvuN13M_hJKNfxann1v1U_B1KZ_D_mzhzhz24fw85CSz2irtN9w9HshBK7AQAAA==)... -```svelte +```js let items = $state([...]); let index = $state(0); From 23e2bb3b89cf27bd4526c59a03cee86df1bd3eb2 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 13 Oct 2025 23:22:44 +0200 Subject: [PATCH 06/15] fix: unset context on stale promises (#16935) * fix: unset context on stale promises When a stale promise is rejected in `async_derived`, and the promise eventually resolves, `d.resolve` will be noop and `d.promise.then(handler, ...)` will never run. That in turns means any restored context (via `(await save(..))()`) will never be unset. We have to handle this case and unset the context to prevent errors such as false-positive state mutation errors * fix: unset context on stale promises (slightly different approach) (#16936) * slightly different approach to #16935 * move unset_context call * get rid of logs --------- Co-authored-by: Rich Harris --- .changeset/major-beans-fry.md | 5 +++ .../internal/client/reactivity/deriveds.js | 6 ++-- .../samples/async-resolve-stale/_config.js | 26 ++++++++++++++ .../samples/async-resolve-stale/main.svelte | 34 +++++++++++++++++++ 4 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 .changeset/major-beans-fry.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte diff --git a/.changeset/major-beans-fry.md b/.changeset/major-beans-fry.md new file mode 100644 index 0000000000..8f35683cd6 --- /dev/null +++ b/.changeset/major-beans-fry.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: unset context on stale promises diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 5d5976a6c1..076a919236 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -126,9 +126,11 @@ export function async_derived(fn, location) { try { // If this code is changed at some point, make sure to still access the then property // of fn() to read any signals it might access, so that we track them as dependencies. - Promise.resolve(fn()).then(d.resolve, d.reject); + // We call `unset_context` to undo any `save` calls that happen inside `fn()` + Promise.resolve(fn()).then(d.resolve, d.reject).then(unset_context); } catch (error) { d.reject(error); + unset_context(); } if (DEV) current_async_effect = null; @@ -185,8 +187,6 @@ export function async_derived(fn, location) { boundary.update_pending_count(-1); if (!pending) batch.decrement(); } - - unset_context(); }; d.promise.then(handler, (e) => handler(null, e || 'unknown')); diff --git a/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js new file mode 100644 index 0000000000..bccf12562a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js @@ -0,0 +1,26 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + // We gotta wait a bit more in this test because of the macrotasks in App.svelte + function macrotask(t = 3) { + return new Promise((r) => setTimeout(r, t)); + } + + await macrotask(); + assert.htmlEqual(target.innerHTML, ' 1 | '); + + const [input] = target.querySelectorAll('input'); + + input.value = '1'; + input.dispatchEvent(new Event('input', { bubbles: true })); + await macrotask(); + assert.htmlEqual(target.innerHTML, ' 1 | '); + + input.value = '12'; + input.dispatchEvent(new Event('input', { bubbles: true })); + await macrotask(6); + // TODO this is wrong (separate bug), this should be 3 | 12 + assert.htmlEqual(target.innerHTML, ' 5 | 12'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte new file mode 100644 index 0000000000..dc4a157928 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte @@ -0,0 +1,34 @@ + + + + +{count} | {x} From e8330ee7bfff1eef61d5990e99c75dc6e225b5bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20R=C3=BCger?= Date: Tue, 14 Oct 2025 15:56:41 +0200 Subject: [PATCH 07/15] fix: svg `radialGradient` `fr` attribute missing in types (#16943) * fix(svg radialGradient): fr attribute missing in types * chore: add changeset --- .changeset/grumpy-towns-stop.md | 5 +++++ packages/svelte/elements.d.ts | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/grumpy-towns-stop.md diff --git a/.changeset/grumpy-towns-stop.md b/.changeset/grumpy-towns-stop.md new file mode 100644 index 0000000000..2b146818f5 --- /dev/null +++ b/.changeset/grumpy-towns-stop.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +add missing type for `fr` attribute for `radialGradient` tags in svg diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index b0c2fae2de..17ff100729 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -1658,6 +1658,7 @@ export interface SVGAttributes extends AriaAttributes, DO 'font-variant'?: number | string | undefined | null; 'font-weight'?: number | string | undefined | null; format?: number | string | undefined | null; + fr?: number | string | undefined | null; from?: number | string | undefined | null; fx?: number | string | undefined | null; fy?: number | string | undefined | null; From 2a951391dc31326fd5df14bf1bfcf425bd13d5ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 09:59:09 -0400 Subject: [PATCH 08/15] Version Packages (#16940) * Version Packages * Update packages/svelte/CHANGELOG.md --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Rich Harris --- .changeset/grumpy-towns-stop.md | 5 ----- .changeset/major-beans-fry.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/grumpy-towns-stop.md delete mode 100644 .changeset/major-beans-fry.md diff --git a/.changeset/grumpy-towns-stop.md b/.changeset/grumpy-towns-stop.md deleted file mode 100644 index 2b146818f5..0000000000 --- a/.changeset/grumpy-towns-stop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -add missing type for `fr` attribute for `radialGradient` tags in svg diff --git a/.changeset/major-beans-fry.md b/.changeset/major-beans-fry.md deleted file mode 100644 index 8f35683cd6..0000000000 --- a/.changeset/major-beans-fry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: unset context on stale promises diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 70f549ce29..b3af39eb4c 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.39.13 + +### Patch Changes + +- fix: add missing type for `fr` attribute for `radialGradient` tags in svg ([#16943](https://github.com/sveltejs/svelte/pull/16943)) + +- fix: unset context on stale promises ([#16935](https://github.com/sveltejs/svelte/pull/16935)) + ## 5.39.12 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index a2d5a6e401..55b44fb2b6 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.39.12", + "version": "5.39.13", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index e520d1248a..536a2260c9 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.39.12'; +export const VERSION = '5.39.13'; export const PUBLIC_VERSION = '5'; From a7c958a2a5a89e1d22fa530e2bacd2b0e3ba7ba6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 14 Oct 2025 11:12:07 -0400 Subject: [PATCH 09/15] chore: simplify `batch.apply()` (#16945) * chore: simplify `batch.apply()` * belt and braces * note to self --- .changeset/pretty-llamas-explode.md | 5 ++ .../src/internal/client/reactivity/batch.js | 67 +++++++------------ .../internal/client/reactivity/deriveds.js | 8 ++- .../svelte/src/internal/client/runtime.js | 10 ++- 4 files changed, 41 insertions(+), 49 deletions(-) create mode 100644 .changeset/pretty-llamas-explode.md diff --git a/.changeset/pretty-llamas-explode.md b/.changeset/pretty-llamas-explode.md new file mode 100644 index 0000000000..00109112de --- /dev/null +++ b/.changeset/pretty-llamas-explode.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: simplify `batch.apply()` diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 102d0670b6..0dc149260a 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -44,12 +44,12 @@ export let current_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 - * the results in this map rather than on the deriveds themselves - * @type {Map | null} + * When time travelling (i.e. working in one batch, while other batches + * still have ongoing work), we ignore the real values of affected + * signals in favour of their values within the batch + * @type {Map | null} */ -export let batch_deriveds = null; +export let batch_values = null; /** @type {Set<() => void>} */ export let effect_pending_updates = new Set(); @@ -152,7 +152,7 @@ export class Batch { previous_batch = null; - var revert = Batch.apply(this); + this.apply(); for (const root of root_effects) { this.#traverse_effect_tree(root); @@ -161,6 +161,10 @@ 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.#pending === 0) { + // TODO we need this because we commit _then_ flush effects... + // maybe there's a way we can reverse the order? + var previous_batch_sources = batch_values; + this.#commit(); var render_effects = this.#render_effects; @@ -175,6 +179,7 @@ export class Batch { previous_batch = this; current_batch = null; + batch_values = previous_batch_sources; flush_queued_effects(render_effects); flush_queued_effects(effects); @@ -187,7 +192,7 @@ export class Batch { this.#defer_effects(this.#block_effects); } - revert(); + batch_values = null; for (const effect of this.#boundary_async_effects) { update_effect(effect); @@ -274,6 +279,7 @@ export class Batch { } this.current.set(source, source.v); + batch_values?.set(source, source.v); } activate() { @@ -282,6 +288,7 @@ export class Batch { deactivate() { current_batch = null; + batch_values = null; } flush() { @@ -352,14 +359,14 @@ export class Batch { if (queued_root_effects.length > 0) { current_batch = batch; - const revert = Batch.apply(batch); + batch.apply(); for (const root of queued_root_effects) { batch.#traverse_effect_tree(root); } queued_root_effects = []; - revert(); + batch.deactivate(); } } @@ -423,49 +430,23 @@ export class Batch { queue_micro_task(task); } - /** - * @param {Batch} current_batch - */ - static apply(current_batch) { - if (!async_mode_flag || batches.size === 1) { - return noop; - } + apply() { + if (!async_mode_flag || batches.size === 1) return; // if there are multiple batches, we are 'time travelling' — - // we need to undo the changes belonging to any batch - // other than the current one - - /** @type {Map} */ - var current_values = new Map(); - batch_deriveds = new Map(); - - for (const [source, current] of current_batch.current) { - current_values.set(source, { v: source.v, wv: source.wv }); - source.v = current; - } + // we need to override values with the ones in this batch... + batch_values = new Map(this.current); + // ...and undo changes belonging to other batches for (const batch of batches) { - if (batch === current_batch) continue; + if (batch === this) continue; for (const [source, previous] of batch.#previous) { - if (!current_values.has(source)) { - current_values.set(source, { v: source.v, wv: source.wv }); - source.v = previous; + if (!batch_values.has(source)) { + batch_values.set(source, previous); } } } - - return () => { - for (const [source, { v, wv }] of current_values) { - // reset the source to the current value (unless - // it got a newer value as a result of effects running) - if (source.wv <= wv) { - source.v = v; - } - } - - batch_deriveds = null; - }; } } diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 076a919236..bf8733cfe5 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -33,7 +33,7 @@ import { async_mode_flag, tracing_mode_flag } from '../../flags/index.js'; import { Boundary } from '../dom/blocks/boundary.js'; import { component_context } from '../context.js'; import { UNINITIALIZED } from '../../../constants.js'; -import { batch_deriveds, current_batch } from './batch.js'; +import { batch_values, current_batch } from './batch.js'; import { unset_context } from './async.js'; import { deferred } from '../../shared/utils.js'; @@ -336,6 +336,8 @@ export function update_derived(derived) { var value = execute_derived(derived); if (!derived.equals(value)) { + // TODO can we avoid setting `derived.v` when `batch_values !== null`, + // without causing the value to be stale later? derived.v = value; derived.wv = increment_write_version(); } @@ -346,8 +348,8 @@ export function update_derived(derived) { return; } - if (batch_deriveds !== null) { - batch_deriveds.set(derived, derived.v); + if (batch_values !== null) { + batch_values.set(derived, derived.v); } else { var status = (skip_reaction || (derived.f & UNOWNED) !== 0) && derived.deps !== null ? MAYBE_DIRTY : CLEAN; diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index b8f5f5ffc9..a146659bf6 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -42,7 +42,7 @@ import { set_dev_stack } from './context.js'; import * as w from './warnings.js'; -import { Batch, batch_deriveds, flushSync, schedule_effect } from './reactivity/batch.js'; +import { Batch, batch_values, flushSync, schedule_effect } from './reactivity/batch.js'; import { handle_error } from './error-handling.js'; import { UNINITIALIZED } from '../../constants.js'; import { captured_signals } from './legacy.js'; @@ -671,8 +671,8 @@ export function get(signal) { } else if (is_derived) { derived = /** @type {Derived} */ (signal); - if (batch_deriveds?.has(derived)) { - return batch_deriveds.get(derived); + if (batch_values?.has(derived)) { + return batch_values.get(derived); } if (is_dirty(derived)) { @@ -680,6 +680,10 @@ export function get(signal) { } } + if (batch_values?.has(signal)) { + return batch_values.get(signal); + } + if ((signal.f & ERROR_VALUE) !== 0) { throw signal.v; } From d50701d277e5c57204f1e51314926b0d73b0cbf9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 14 Oct 2025 11:36:55 -0400 Subject: [PATCH 10/15] unused --- packages/svelte/src/internal/client/reactivity/batch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 0dc149260a..2edfc1343a 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -14,7 +14,7 @@ import { DERIVED } from '#client/constants'; import { async_mode_flag } from '../../flags/index.js'; -import { deferred, define_property, noop } from '../../shared/utils.js'; +import { deferred, define_property } from '../../shared/utils.js'; import { active_effect, is_dirty, From 28765f846e956c0da0e41e634de8c1066da9e6d5 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 14 Oct 2025 17:59:02 +0200 Subject: [PATCH 11/15] fix: don't rerun async effects unnecessarily (#16944) Since #16866, when an async effect runs multiple times, we rebase older batches and rerun those effects. This can have unintended consequences: In a case where an async effect only depends on a single source, and that single source was updated in a later batch, we know that we don't need to / should not rerun the older batch. This PR makes it so: We collect all the sources of older batches that are not part of the current batch that just committed, and then only mark those async effects as dirty which depend on one of those other sources. Fixes the bug I noticed while working on #16935 --- .changeset/wild-mirrors-take.md | 5 ++ .../src/internal/client/reactivity/batch.js | 67 ++++++++++++++----- .../internal/client/reactivity/deriveds.js | 7 ++ .../samples/async-resolve-stale/_config.js | 3 +- 4 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 .changeset/wild-mirrors-take.md diff --git a/.changeset/wild-mirrors-take.md b/.changeset/wild-mirrors-take.md new file mode 100644 index 0000000000..faf28e7695 --- /dev/null +++ b/.changeset/wild-mirrors-take.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't rerun async effects unnecessarily diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 2edfc1343a..2956e7ed6a 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -1,4 +1,4 @@ -/** @import { Derived, Effect, Source, Value } from '#client' */ +/** @import { Derived, Effect, Reaction, Source, Value } from '#client' */ import { BLOCK_EFFECT, BRANCH_EFFECT, @@ -342,31 +342,46 @@ export class Batch { continue; } + /** @type {Source[]} */ + const sources = []; + for (const [source, value] of this.current) { if (batch.current.has(source)) { - if (is_earlier) { + if (is_earlier && value !== batch.current.get(source)) { // bring the value up to date batch.current.set(source, value); } else { - // later batch has more recent value, + // same value or later batch has more recent value, // no need to re-run these effects continue; } } - mark_effects(source); + sources.push(source); } - if (queued_root_effects.length > 0) { - current_batch = batch; - batch.apply(); + if (sources.length === 0) { + continue; + } - for (const root of queued_root_effects) { - batch.#traverse_effect_tree(root); + // Re-run async/block effects that depend on distinct values changed in both batches + const others = [...batch.current.keys()].filter((s) => !this.current.has(s)); + if (others.length > 0) { + for (const source of sources) { + mark_effects(source, others); } - queued_root_effects = []; - batch.deactivate(); + if (queued_root_effects.length > 0) { + current_batch = batch; + batch.apply(); + + for (const root of queued_root_effects) { + batch.#traverse_effect_tree(root); + } + + queued_root_effects = []; + batch.deactivate(); + } } } @@ -621,17 +636,19 @@ function flush_queued_effects(effects) { /** * This is similar to `mark_reactions`, but it only marks async/block effects - * so that these can re-run after another batch has been committed + * depending on `value` and at least one of the other `sources`, so that + * these effects can re-run after another batch has been committed * @param {Value} value + * @param {Source[]} sources */ -function mark_effects(value) { +function mark_effects(value, sources) { if (value.reactions !== null) { for (const reaction of value.reactions) { const flags = reaction.f; if ((flags & DERIVED) !== 0) { - mark_effects(/** @type {Derived} */ (reaction)); - } else if ((flags & (ASYNC | BLOCK_EFFECT)) !== 0) { + mark_effects(/** @type {Derived} */ (reaction), sources); + } else if ((flags & (ASYNC | BLOCK_EFFECT)) !== 0 && depends_on(reaction, sources)) { set_signal_status(reaction, DIRTY); schedule_effect(/** @type {Effect} */ (reaction)); } @@ -639,6 +656,26 @@ function mark_effects(value) { } } +/** + * @param {Reaction} reaction + * @param {Source[]} sources + */ +function depends_on(reaction, sources) { + if (reaction.deps !== null) { + for (const dep of reaction.deps) { + if (sources.includes(dep)) { + return true; + } + + if ((dep.f & DERIVED) !== 0 && depends_on(/** @type {Derived} */ (dep), sources)) { + return true; + } + } + } + + return false; +} + /** * @param {Effect} signal * @returns {void} diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index bf8733cfe5..fa780013e1 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -171,6 +171,13 @@ export function async_derived(fn, location) { internal_set(signal, value); + // All prior async derived runs are now stale + for (const [b, d] of deferreds) { + deferreds.delete(b); + if (b === batch) break; + d.reject(STALE_REACTION); + } + if (DEV && location !== undefined) { recent_async_deriveds.add(signal); diff --git a/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js index bccf12562a..50bb414afc 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js @@ -20,7 +20,6 @@ export default test({ input.value = '12'; input.dispatchEvent(new Event('input', { bubbles: true })); await macrotask(6); - // TODO this is wrong (separate bug), this should be 3 | 12 - assert.htmlEqual(target.innerHTML, ' 5 | 12'); + assert.htmlEqual(target.innerHTML, ' 3 | 12'); } }); From 99711d582263ce3dc0103baecf579e995363db86 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 14 Oct 2025 18:25:52 +0200 Subject: [PATCH 12/15] fix: ensure map iteration order is correct (#16947) quick follow-up to #16944 Resetting a map entry does not change its position in the map when iterating. We need to make sure that reset makes that batch jump "to the front" for the "reject all stale batches" logic below. Edge case for which I can't come up with a test case but it _is_ a possibility. --- packages/svelte/src/internal/client/reactivity/deriveds.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index fa780013e1..6aa9a1d9d9 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -144,6 +144,7 @@ export function async_derived(fn, location) { batch.increment(); deferreds.get(batch)?.reject(STALE_REACTION); + deferreds.delete(batch); // delete to ensure correct order in Map iteration below deferreds.set(batch, d); } } From f3c55e8e6c26bc8c006c84a656c845584abf3eb4 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 14 Oct 2025 15:48:29 -0400 Subject: [PATCH 13/15] feat: add `createContext` utility for type-safe context (#16948) * feat: add `createContext` utility for type-safe context * regenerate --- .changeset/neat-melons-cheer.md | 5 +++++ .../98-reference/.generated/shared-errors.md | 8 ++++++++ .../svelte/messages/shared-errors/errors.md | 6 ++++++ packages/svelte/src/index-client.js | 8 +++++++- packages/svelte/src/index-server.js | 8 +++++++- .../svelte/src/internal/client/context.js | 20 +++++++++++++++++++ .../svelte/src/internal/server/context.js | 9 +++++++++ packages/svelte/src/internal/shared/errors.js | 16 +++++++++++++++ .../samples/create-context/Child.svelte | 7 +++++++ .../samples/create-context/_config.js | 5 +++++ .../samples/create-context/main.svelte | 16 +++++++++++++++ packages/svelte/types/index.d.ts | 4 ++++ 12 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 .changeset/neat-melons-cheer.md create mode 100644 packages/svelte/tests/runtime-runes/samples/create-context/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/create-context/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/create-context/main.svelte diff --git a/.changeset/neat-melons-cheer.md b/.changeset/neat-melons-cheer.md new file mode 100644 index 0000000000..1107d7ef20 --- /dev/null +++ b/.changeset/neat-melons-cheer.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: add `createContext` utility for type-safe context diff --git a/documentation/docs/98-reference/.generated/shared-errors.md b/documentation/docs/98-reference/.generated/shared-errors.md index 6c31aaafd0..07e13dea45 100644 --- a/documentation/docs/98-reference/.generated/shared-errors.md +++ b/documentation/docs/98-reference/.generated/shared-errors.md @@ -60,6 +60,14 @@ Certain lifecycle methods can only be used during component initialisation. To f ``` +### missing_context + +``` +Context was not set in a parent component +``` + +The [`createContext()`](svelte#createContext) utility returns a `[get, set]` pair of functions. `get` will throw an error if `set` was not used to set the context in a parent component. + ### snippet_without_render_tag ``` diff --git a/packages/svelte/messages/shared-errors/errors.md b/packages/svelte/messages/shared-errors/errors.md index 4b4d332202..e3959034a3 100644 --- a/packages/svelte/messages/shared-errors/errors.md +++ b/packages/svelte/messages/shared-errors/errors.md @@ -52,6 +52,12 @@ Certain lifecycle methods can only be used during component initialisation. To f ``` +## missing_context + +> Context was not set in a parent component + +The [`createContext()`](svelte#createContext) utility returns a `[get, set]` pair of functions. `get` will throw an error if `set` was not used to set the context in a parent component. + ## snippet_without_render_tag > Attempted to render a snippet without a `{@render}` block. This would cause the snippet code to be stringified instead of its content being rendered to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`. diff --git a/packages/svelte/src/index-client.js b/packages/svelte/src/index-client.js index 85eeab7de9..337cbb500b 100644 --- a/packages/svelte/src/index-client.js +++ b/packages/svelte/src/index-client.js @@ -242,7 +242,13 @@ function init_update_callbacks(context) { } export { flushSync } from './internal/client/reactivity/batch.js'; -export { getContext, getAllContexts, hasContext, setContext } from './internal/client/context.js'; +export { + createContext, + getContext, + getAllContexts, + hasContext, + setContext +} from './internal/client/context.js'; export { hydrate, mount, unmount } from './internal/client/render.js'; export { tick, untrack, settled } from './internal/client/runtime.js'; export { createRawSnippet } from './internal/client/dom/blocks/snippet.js'; diff --git a/packages/svelte/src/index-server.js b/packages/svelte/src/index-server.js index f193c46894..223ce6a4cd 100644 --- a/packages/svelte/src/index-server.js +++ b/packages/svelte/src/index-server.js @@ -39,6 +39,12 @@ export async function settled() {} export { getAbortSignal } from './internal/server/abort-signal.js'; -export { getAllContexts, getContext, hasContext, setContext } from './internal/server/context.js'; +export { + createContext, + getAllContexts, + getContext, + hasContext, + setContext +} from './internal/server/context.js'; export { createRawSnippet } from './internal/server/blocks/snippet.js'; diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index cad75546d4..ea63072a37 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -69,6 +69,26 @@ export function set_dev_current_component_function(fn) { dev_current_component_function = fn; } +/** + * Returns a `[get, set]` pair of functions for working with context in a type-safe way. + * @template T + * @returns {[() => T, (context: T) => T]} + */ +export function createContext() { + const key = {}; + + return [ + () => { + if (!hasContext(key)) { + e.missing_context(); + } + + return getContext(key); + }, + (context) => setContext(key, context) + ]; +} + /** * Retrieves the context that belongs to the closest parent component with the specified `key`. * Must be called during component initialisation. diff --git a/packages/svelte/src/internal/server/context.js b/packages/svelte/src/internal/server/context.js index c59b2d260a..1813bfbf78 100644 --- a/packages/svelte/src/internal/server/context.js +++ b/packages/svelte/src/internal/server/context.js @@ -10,6 +10,15 @@ export function set_ssr_context(v) { ssr_context = v; } +/** + * @template T + * @returns {[() => T, (context: T) => T]} + */ +export function createContext() { + const key = {}; + return [() => getContext(key), (context) => setContext(key, context)]; +} + /** * @template T * @param {any} key diff --git a/packages/svelte/src/internal/shared/errors.js b/packages/svelte/src/internal/shared/errors.js index 6bcc35016a..669cdd96a7 100644 --- a/packages/svelte/src/internal/shared/errors.js +++ b/packages/svelte/src/internal/shared/errors.js @@ -51,6 +51,22 @@ export function lifecycle_outside_component(name) { } } +/** + * Context was not set in a parent component + * @returns {never} + */ +export function missing_context() { + if (DEV) { + const error = new Error(`missing_context\nContext was not set in a parent component\nhttps://svelte.dev/e/missing_context`); + + error.name = 'Svelte error'; + + throw error; + } else { + throw new Error(`https://svelte.dev/e/missing_context`); + } +} + /** * Attempted to render a snippet without a `{@render}` block. This would cause the snippet code to be stringified instead of its content being rendered to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`. * @returns {never} diff --git a/packages/svelte/tests/runtime-runes/samples/create-context/Child.svelte b/packages/svelte/tests/runtime-runes/samples/create-context/Child.svelte new file mode 100644 index 0000000000..3e39d5043e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/create-context/Child.svelte @@ -0,0 +1,7 @@ + + +

{message}

diff --git a/packages/svelte/tests/runtime-runes/samples/create-context/_config.js b/packages/svelte/tests/runtime-runes/samples/create-context/_config.js new file mode 100644 index 0000000000..4ae28e68bd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/create-context/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `

hello

` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/create-context/main.svelte b/packages/svelte/tests/runtime-runes/samples/create-context/main.svelte new file mode 100644 index 0000000000..8d3c50ba55 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/create-context/main.svelte @@ -0,0 +1,16 @@ + + + + + diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 6dc6629faa..58e3285e4a 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -448,6 +448,10 @@ declare module 'svelte' { }): Snippet; /** Anything except a function */ type NotFunction = T extends Function ? never : T; + /** + * Returns a `[get, set]` pair of functions for working with context in a type-safe way. + * */ + export function createContext(): [() => T, (context: T) => T]; /** * Retrieves the context that belongs to the closest parent component with the specified `key`. * Must be called during component initialisation. From 005895d9940eea9006b1d36f2b0d7260a15a91a9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 15:58:42 -0400 Subject: [PATCH 14/15] Version Packages (#16946) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/neat-melons-cheer.md | 5 ----- .changeset/pretty-llamas-explode.md | 5 ----- .changeset/wild-mirrors-take.md | 5 ----- packages/svelte/CHANGELOG.md | 12 ++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 6 files changed, 14 insertions(+), 17 deletions(-) delete mode 100644 .changeset/neat-melons-cheer.md delete mode 100644 .changeset/pretty-llamas-explode.md delete mode 100644 .changeset/wild-mirrors-take.md diff --git a/.changeset/neat-melons-cheer.md b/.changeset/neat-melons-cheer.md deleted file mode 100644 index 1107d7ef20..0000000000 --- a/.changeset/neat-melons-cheer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': minor ---- - -feat: add `createContext` utility for type-safe context diff --git a/.changeset/pretty-llamas-explode.md b/.changeset/pretty-llamas-explode.md deleted file mode 100644 index 00109112de..0000000000 --- a/.changeset/pretty-llamas-explode.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -chore: simplify `batch.apply()` diff --git a/.changeset/wild-mirrors-take.md b/.changeset/wild-mirrors-take.md deleted file mode 100644 index faf28e7695..0000000000 --- a/.changeset/wild-mirrors-take.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: don't rerun async effects unnecessarily diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index b3af39eb4c..5d23dddd15 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # svelte +## 5.40.0 + +### Minor Changes + +- feat: add `createContext` utility for type-safe context ([#16948](https://github.com/sveltejs/svelte/pull/16948)) + +### Patch Changes + +- chore: simplify `batch.apply()` ([#16945](https://github.com/sveltejs/svelte/pull/16945)) + +- fix: don't rerun async effects unnecessarily ([#16944](https://github.com/sveltejs/svelte/pull/16944)) + ## 5.39.13 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 55b44fb2b6..8c049e0f85 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.39.13", + "version": "5.40.0", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 536a2260c9..2fcca0bd8d 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.39.13'; +export const VERSION = '5.40.0'; export const PUBLIC_VERSION = '5'; From 9cdd76e3a3666b6c041fc2eb2c4498db31c795e5 Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Tue, 14 Oct 2025 14:56:49 -0600 Subject: [PATCH 15/15] chore: Remove annoying sync-async warning (#16949) --- .changeset/eager-cups-argue.md | 5 +++++ .../docs/98-reference/.generated/server-warnings.md | 9 --------- .../svelte/messages/server-warnings/warnings.md | 5 ----- packages/svelte/src/internal/server/renderer.js | 2 -- packages/svelte/src/internal/server/warnings.js | 13 +------------ packages/svelte/src/legacy/legacy-server.js | 1 - 6 files changed, 6 insertions(+), 29 deletions(-) create mode 100644 .changeset/eager-cups-argue.md delete mode 100644 documentation/docs/98-reference/.generated/server-warnings.md delete mode 100644 packages/svelte/messages/server-warnings/warnings.md diff --git a/.changeset/eager-cups-argue.md b/.changeset/eager-cups-argue.md new file mode 100644 index 0000000000..74f03bc1da --- /dev/null +++ b/.changeset/eager-cups-argue.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: Remove sync-in-async warning for server rendering diff --git a/documentation/docs/98-reference/.generated/server-warnings.md b/documentation/docs/98-reference/.generated/server-warnings.md deleted file mode 100644 index 26b3628be9..0000000000 --- a/documentation/docs/98-reference/.generated/server-warnings.md +++ /dev/null @@ -1,9 +0,0 @@ - - -### experimental_async_ssr - -``` -Attempted to use asynchronous rendering without `experimental.async` enabled -``` - -Set `experimental.async: true` in your compiler options (usually in `svelte.config.js`) to use async server rendering. This render ran synchronously. diff --git a/packages/svelte/messages/server-warnings/warnings.md b/packages/svelte/messages/server-warnings/warnings.md deleted file mode 100644 index 4df89d0176..0000000000 --- a/packages/svelte/messages/server-warnings/warnings.md +++ /dev/null @@ -1,5 +0,0 @@ -## experimental_async_ssr - -> Attempted to use asynchronous rendering without `experimental.async` enabled - -Set `experimental.async: true` in your compiler options (usually in `svelte.config.js`) to use async server rendering. This render ran synchronously. diff --git a/packages/svelte/src/internal/server/renderer.js b/packages/svelte/src/internal/server/renderer.js index bbb43a6f3b..602c680c08 100644 --- a/packages/svelte/src/internal/server/renderer.js +++ b/packages/svelte/src/internal/server/renderer.js @@ -4,7 +4,6 @@ import { async_mode_flag } from '../flags/index.js'; import { abort } from './abort-signal.js'; import { pop, push, set_ssr_context, ssr_context } from './context.js'; import * as e from './errors.js'; -import * as w from './warnings.js'; import { BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js'; import { attributes } from './index.js'; @@ -361,7 +360,6 @@ export class Renderer { */ (onfulfilled, onrejected) => { if (!async_mode_flag) { - w.experimental_async_ssr(); const result = (sync ??= Renderer.#render(component, options)); const user_result = onfulfilled({ head: result.head, diff --git a/packages/svelte/src/internal/server/warnings.js b/packages/svelte/src/internal/server/warnings.js index d8d9cd6d43..d4ee7a86c2 100644 --- a/packages/svelte/src/internal/server/warnings.js +++ b/packages/svelte/src/internal/server/warnings.js @@ -3,15 +3,4 @@ import { DEV } from 'esm-env'; var bold = 'font-weight: bold'; -var normal = 'font-weight: normal'; - -/** - * Attempted to use asynchronous rendering without `experimental.async` enabled - */ -export function experimental_async_ssr() { - if (DEV) { - console.warn(`%c[svelte] experimental_async_ssr\n%cAttempted to use asynchronous rendering without \`experimental.async\` enabled\nhttps://svelte.dev/e/experimental_async_ssr`, bold, normal); - } else { - console.warn(`https://svelte.dev/e/experimental_async_ssr`); - } -} \ No newline at end of file +var normal = 'font-weight: normal'; \ No newline at end of file diff --git a/packages/svelte/src/legacy/legacy-server.js b/packages/svelte/src/legacy/legacy-server.js index b7d3e673bc..a50d961751 100644 --- a/packages/svelte/src/legacy/legacy-server.js +++ b/packages/svelte/src/legacy/legacy-server.js @@ -53,7 +53,6 @@ export function asClassComponent(component) { */ value: (onfulfilled, onrejected) => { if (!async_mode_flag) { - w.experimental_async_ssr(); const user_result = onfulfilled({ css: munged.css, head: munged.head,