Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f9d4569
Merge branch 'main' into effect-pending-value-2
dummdidumm Oct 10, 2025
e4ae9e9
runtime-first approach
dummdidumm Oct 10, 2025
3c4dd34
Merge branch 'effect-pending-value' into effect-pending-value-2
dummdidumm Oct 10, 2025
49627a7
revert these
dummdidumm Oct 10, 2025
b92d2b0
type safety, lint
dummdidumm Oct 10, 2025
5d9b4c6
fix: better input cursor restoration for `bind:value` (#16925)
dummdidumm Oct 13, 2025
420468a
Version Packages (#16920)
github-actions[bot] Oct 13, 2025
b91f9de
docs: await no longer need pending (#16900)
hyunbinseo Oct 13, 2025
ba782f9
docs: link to custom renderer issue in Svelte Native discussion (#16896)
benmccann Oct 13, 2025
b05e12f
fix code block (#16937)
Rich-Harris Oct 13, 2025
23e2bb3
fix: unset context on stale promises (#16935)
dummdidumm Oct 13, 2025
e8330ee
fix: svg `radialGradient` `fr` attribute missing in types (#16943)
hrueger Oct 14, 2025
2a95139
Version Packages (#16940)
github-actions[bot] Oct 14, 2025
a7c958a
chore: simplify `batch.apply()` (#16945)
Rich-Harris Oct 14, 2025
d50701d
unused
Rich-Harris Oct 14, 2025
28765f8
fix: don't rerun async effects unnecessarily (#16944)
dummdidumm Oct 14, 2025
99711d5
fix: ensure map iteration order is correct (#16947)
dummdidumm Oct 14, 2025
288b7dd
merge main
Rich-Harris Oct 14, 2025
f3c55e8
feat: add `createContext` utility for type-safe context (#16948)
Rich-Harris Oct 14, 2025
005895d
Version Packages (#16946)
github-actions[bot] Oct 14, 2025
9cdd76e
chore: Remove annoying sync-async warning (#16949)
elliott-with-the-longest-name-on-github Oct 14, 2025
2846922
Merge branch 'main' into pr/16926
Rich-Harris Oct 14, 2025
323a641
fix
Rich-Harris Oct 14, 2025
61adc3d
use `$state.eager(value)` instead of `$effect.pending(value)`
Rich-Harris Oct 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eager-cups-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

chore: Remove sync-in-async warning for server rendering
5 changes: 0 additions & 5 deletions .changeset/rude-frogs-train.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/spicy-rabbits-drive.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/wicked-goats-begin.md

This file was deleted.

2 changes: 1 addition & 1 deletion documentation/docs/02-runes/03-$derived.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
<svelte:boundary>
Expand Down
2 changes: 1 addition & 1 deletion documentation/docs/07-misc/99-faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand Down
9 changes: 0 additions & 9 deletions documentation/docs/98-reference/.generated/server-warnings.md

This file was deleted.

8 changes: 8 additions & 0 deletions documentation/docs/98-reference/.generated/shared-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ Certain lifecycle methods can only be used during component initialisation. To f
<button onclick={handleClick}>click me</button>
```

### 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

```
Expand Down
32 changes: 32 additions & 0 deletions packages/svelte/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/elements.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1658,6 +1658,7 @@ export interface SVGAttributes<T extends EventTarget> 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;
Expand Down
5 changes: 0 additions & 5 deletions packages/svelte/messages/server-warnings/warnings.md

This file was deleted.

6 changes: 6 additions & 0 deletions packages/svelte/messages/shared-errors/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ Certain lifecycle methods can only be used during component initialisation. To f
<button onclick={handleClick}>click me</button>
```

## 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()}`.
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
18 changes: 15 additions & 3 deletions packages/svelte/src/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
* <nav>
* <a href="/" aria-current={$state.eager(href) === '/' ? 'page' : null}>home</a>
* <a href="/about" aria-current={$state.eager(href) === '/about' ? 'page' : null}>about</a>
* </nav>
* ```
*/
export function eager<T>(value: T): T;
/**
* Declares state that is _not_ made deeply reactive — instead of mutating it,
* you must reassign it.
*
* Example:
* ```ts
* ```svelte
* <script>
* let items = $state.raw([0]);
*
Expand All @@ -109,7 +121,7 @@ declare namespace $state {
* };
* </script>
*
* <button on:click={addItem}>
* <button onclick={addItem}>
* {items.join(', ')}
* </button>
* ```
Expand All @@ -124,7 +136,7 @@ declare namespace $state {
* To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snapshot`:
*
* Example:
* ```ts
* ```svelte
* <script>
* let counter = $state({ count: 0 });
*
Expand Down
3 changes: 1 addition & 2 deletions packages/svelte/src/compiler/phases/1-parse/utils/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ export function create_fragment(transparent = false) {
metadata: {
transparent,
dynamic: false,
has_await: false,
effect_pending_expressions: []
has_await: false
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,22 +165,10 @@ export function CallExpression(node, context) {
break;

case '$effect.pending':
if (node.arguments.length > 1) {
e.rune_invalid_arguments_length(node, rune, 'zero or one arguments');
}

if (context.state.expression) {
context.state.expression.has_state = true;
}

if (node.arguments[0]) {
const fragment = /** @type {AST.Fragment} */ (context.state.fragment);

fragment.metadata.effect_pending_expressions.push(
/** @type {Expression} */ (node.arguments[0])
);
}

break;

case '$inspect':
Expand Down Expand Up @@ -238,6 +226,13 @@ export function CallExpression(node, context) {
break;
}

case '$state.eager':
if (node.arguments.length !== 1) {
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
}

break;

case '$state.snapshot':
if (node.arguments.length !== 1) {
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ export interface ClientTransformState extends TransformState {
/** `true` if we're transforming the contents of `<script>` */
readonly is_instance: boolean;

effect_pending: Map<Expression, Identifier>;

readonly transform: Record<
string,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ export function CallExpression(node, context) {
return b.call('$.derived', rune === '$derived' ? b.thunk(fn) : fn);
}

case '$state.eager':
return b.call(
'$.pending',
b.thunk(/** @type {Expression} */ (context.visit(node.arguments[0])))
);

case '$state.snapshot':
return b.call(
'$.snapshot',
Expand All @@ -63,17 +69,6 @@ export function CallExpression(node, context) {
);

case '$effect.pending':
if (node.arguments[0]) {
const id = b.id(`$$pending_${context.state.effect_pending.size}`);

context.state.effect_pending.set(
/** @type {Expression} */ (context.visit(node.arguments[0])),
id
);

return b.call('$.get', id);
}

return b.call('$.pending');

case '$inspect':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ export function Fragment(node, context) {
memoizer: new Memoizer(),
template: new Template(),
transform: { ...context.state.transform },
effect_pending: new Map(),
metadata: {
namespace,
bound_contenteditable: context.state.metadata.bound_contenteditable
Expand Down Expand Up @@ -153,10 +152,6 @@ export function Fragment(node, context) {

body.push(...state.consts);

for (const [expression, id] of state.effect_pending) {
body.push(b.const(id, b.call('$.pending', b.thunk(expression))));
}

if (has_await) {
body.push(b.if(b.call('$.aborted'), b.return()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,7 @@ export function RegularElement(node, context) {
metadata,
scope: /** @type {Scope} */ (context.state.scopes.get(node.fragment)),
preserve_whitespace:
context.state.preserve_whitespace || node.name === 'pre' || node.name === 'textarea',
effect_pending: new Map()
context.state.preserve_whitespace || node.name === 'pre' || node.name === 'textarea'
};

const { hoisted, trimmed } = clean_nodes(
Expand Down Expand Up @@ -379,10 +378,6 @@ export function RegularElement(node, context) {
}
}

for (const [expression, id] of state.effect_pending) {
child_state.init.push(b.const(id, b.call('$.pending', b.thunk(expression))));
}

if (node.fragment.nodes.some((node) => node.type === 'SnippetBlock')) {
// Wrap children in `{...}` to avoid declaration conflicts
context.state.init.push(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function CallExpression(node, context) {
}

if (rune === '$effect.pending') {
return node.arguments[0] ?? b.literal(0);
return b.literal(0);
}

if (rune === '$state' || rune === '$state.raw') {
Expand All @@ -38,6 +38,10 @@ export function CallExpression(node, context) {
return b.call('$.derived', rune === '$derived' ? b.thunk(fn) : fn);
}

if (rune === '$state.eager') {
return node.arguments[0];
}

if (rune === '$state.snapshot') {
return b.call(
'$.snapshot',
Expand Down
1 change: 0 additions & 1 deletion packages/svelte/src/compiler/types/template.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ export namespace AST {
*/
dynamic: boolean;
has_await: boolean;
effect_pending_expressions: Expression[];
};
}

Expand Down
8 changes: 7 additions & 1 deletion packages/svelte/src/index-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
8 changes: 7 additions & 1 deletion packages/svelte/src/index-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Loading
Loading