From d8e61f66516987b9cd67307036f5dab1a9482f5d Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 15 Oct 2025 00:10:23 +0200 Subject: [PATCH] feat: `$state.eager(value)` (#16926) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * runtime-first approach * revert these * type safety, lint * 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 * Version Packages (#16920) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * docs: await no longer need pending (#16900) * docs: link to custom renderer issue in Svelte Native discussion (#16896) * fix code block (#16937) Updated code block syntax from Svelte to JavaScript for clarity. * 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 * fix: svg `radialGradient` `fr` attribute missing in types (#16943) * fix(svg radialGradient): fr attribute missing in types * chore: add changeset * 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 * chore: simplify `batch.apply()` (#16945) * chore: simplify `batch.apply()` * belt and braces * note to self * unused * 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 * 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. * feat: add `createContext` utility for type-safe context (#16948) * feat: add `createContext` utility for type-safe context * regenerate * Version Packages (#16946) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * chore: Remove annoying sync-async warning (#16949) * fix * use `$state.eager(value)` instead of `$effect.pending(value)` --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Hyunbin Seo <47051820+hyunbinseo@users.noreply.github.com> Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> Co-authored-by: Rich Harris Co-authored-by: Hannes Rüger Co-authored-by: Elliott Johnson --- .changeset/eager-cups-argue.md | 5 + .changeset/rude-frogs-train.md | 5 - .changeset/spicy-rabbits-drive.md | 5 - .changeset/wicked-goats-begin.md | 5 - documentation/docs/02-runes/03-$derived.md | 2 +- .../05-special-elements/01-svelte-boundary.md | 2 +- documentation/docs/07-misc/99-faq.md | 2 +- .../.generated/server-warnings.md | 9 -- .../98-reference/.generated/shared-errors.md | 8 ++ packages/svelte/CHANGELOG.md | 32 +++++ packages/svelte/elements.d.ts | 1 + .../messages/server-warnings/warnings.md | 5 - .../svelte/messages/shared-errors/errors.md | 6 + packages/svelte/package.json | 2 +- packages/svelte/src/ambient.d.ts | 18 ++- .../compiler/phases/1-parse/utils/create.js | 3 +- .../2-analyze/visitors/CallExpression.js | 19 +-- .../phases/3-transform/client/types.d.ts | 2 - .../client/visitors/CallExpression.js | 17 +-- .../3-transform/client/visitors/Fragment.js | 5 - .../client/visitors/RegularElement.js | 7 +- .../server/visitors/CallExpression.js | 6 +- .../svelte/src/compiler/types/template.d.ts | 1 - packages/svelte/src/index-client.js | 8 +- packages/svelte/src/index-server.js | 8 +- .../svelte/src/internal/client/context.js | 20 +++ .../internal/client/dom/blocks/boundary.js | 50 +++---- .../client/dom/elements/bindings/input.js | 12 +- .../src/internal/client/reactivity/batch.js | 132 ++++++++++-------- .../internal/client/reactivity/deriveds.js | 22 ++- .../svelte/src/internal/client/runtime.js | 29 +++- .../svelte/src/internal/server/context.js | 9 ++ .../svelte/src/internal/server/renderer.js | 2 - .../svelte/src/internal/server/warnings.js | 13 +- packages/svelte/src/internal/shared/errors.js | 16 +++ packages/svelte/src/legacy/legacy-server.js | 1 - packages/svelte/src/utils.js | 1 + packages/svelte/src/version.js | 2 +- .../samples/async-resolve-stale/_config.js | 25 ++++ .../samples/async-resolve-stale/main.svelte | 34 +++++ .../samples/create-context/Child.svelte | 7 + .../samples/create-context/_config.js | 5 + .../samples/create-context/main.svelte | 16 +++ packages/svelte/types/index.d.ts | 22 ++- 44 files changed, 402 insertions(+), 199 deletions(-) create mode 100644 .changeset/eager-cups-argue.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 delete mode 100644 documentation/docs/98-reference/.generated/server-warnings.md delete mode 100644 packages/svelte/messages/server-warnings/warnings.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 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/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/.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/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); 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 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? 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/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/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 2302acd033..5d23dddd15 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,37 @@ # 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 + +- 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 + +- 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/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; 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/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/package.json b/packages/svelte/package.json index 822f19f001..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.11", + "version": "5.40.0", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/ambient.d.ts b/packages/svelte/src/ambient.d.ts index d655fb648a..64cdcc93b2 100644 --- a/packages/svelte/src/ambient.d.ts +++ b/packages/svelte/src/ambient.d.ts @@ -95,12 +95,24 @@ declare namespace $state { : never : never; + /** + * Returns the latest `value`, even if the rest of the UI is suspending + * while async work (such as data loading) completes. + * + * ```svelte + * + * ``` + */ + export function eager(value: T): T; /** * Declares state that is _not_ made deeply reactive — instead of mutating it, * you must reassign it. * * Example: - * ```ts + * ```svelte * * - * * ``` @@ -124,7 +136,7 @@ declare namespace $state { * To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snapshot`: * * Example: - * ```ts + * ```svelte * + + + +{count} | {x} 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..2bc64d22a3 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. @@ -3181,12 +3185,24 @@ declare namespace $state { : never : never; + /** + * Returns the latest `value`, even if the rest of the UI is suspending + * while async work (such as data loading) completes. + * + * ```svelte + * + * ``` + */ + export function eager(value: T): T; /** * Declares state that is _not_ made deeply reactive — instead of mutating it, * you must reassign it. * * Example: - * ```ts + * ```svelte * * - * * ``` @@ -3210,7 +3226,7 @@ declare namespace $state { * To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snapshot`: * * Example: - * ```ts + * ```svelte *