Skip to content

Commit 449868d

Browse files
committed
merge main
2 parents 04ddbfe + 24944e6 commit 449868d

File tree

26 files changed

+200
-34
lines changed

26 files changed

+200
-34
lines changed

.changeset/gold-eels-lay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: depend on reads of deriveds created within reaction (async mode)

.changeset/tasty-snails-dress.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: async `class:` + spread attributes were compiled into sync server-side code

.changeset/wise-bottles-explode.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: ensure tick resolves within a macrotask

documentation/docs/03-template-syntax/19-await-expressions.md

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,6 @@ export default {
2323

2424
The experimental flag will be removed in Svelte 6.
2525

26-
## Boundaries
27-
28-
Currently, you can only use `await` inside a [`<svelte:boundary>`](svelte-boundary) with a `pending` snippet:
29-
30-
```svelte
31-
<svelte:boundary>
32-
<MyApp />
33-
34-
{#snippet pending()}
35-
<p>loading...</p>
36-
{/snippet}
37-
</svelte:boundary>
38-
```
39-
40-
This restriction will be lifted once Svelte supports asynchronous server-side rendering (see [caveats](#Caveats)).
41-
42-
> [!NOTE] In the [playground](/playground), your app is rendered inside a boundary with an empty pending snippet, so that you can use `await` without having to create one.
43-
4426
## Synchronized updates
4527

4628
When an `await` expression depends on a particular piece of state, changes to that state will not be reflected in the UI until the asynchronous work has completed, so that the UI is not left in an inconsistent state. In other words, in an example like [this](/playground/untitled#H4sIAAAAAAAAE42QsWrDQBBEf2VZUkhYRE4gjSwJ0qVMkS6XYk9awcFpJe5Wdoy4fw-ycdykSPt2dpiZFYVGxgrf2PsJTlPwPWTcO-U-xwIH5zli9bminudNtwEsbl-v8_wYj-x1Y5Yi_8W7SZRFI1ZYxy64WVsjRj0rEDTwEJWUs6f8cKP2Tp8vVIxSPEsHwyKdukmA-j6jAmwO63Y1SidyCsIneA_T6CJn2ZBD00Jk_XAjT4tmQwEv-32eH6AsgYK6wXWOPPTs6Xy1CaxLECDYgb3kSUbq8p5aaifzorCt0RiUZbQcDIJ10ldH8gs3K6X2Xzqbro5zu1KCHaw2QQPrtclvwVSXc2sEC1T-Vqw0LJy-ClRy_uSkx2ogHzn9ADZ1CubKAQAA)...
@@ -99,7 +81,9 @@ let b = $derived(await two());
9981
10082
## Indicating loading states
10183

102-
In addition to the nearest boundary's [`pending`](svelte-boundary#Properties-pending) snippet, you can indicate that asynchronous work is ongoing with [`$effect.pending()`]($effect#$effect.pending).
84+
To render placeholder UI, you can wrap content in a `<svelte:boundary>` with a [`pending`](svelte-boundary#Properties-pending) snippet. This will be shown when the boundary is first created, but not for subsequent updates, which are globally coordinated.
85+
86+
After the contents of a boundary have resolved for the first time and have replaced the `pending` snippet, you can detect subsequent async work with [`$effect.pending()`]($effect#$effect.pending). This is what you would use to display a "we're asynchronously validating your input" spinner next to a form field, for example.
10387

10488
You can also use [`settled()`](svelte#settled) to get a promise that resolves when the current update is complete:
10589

@@ -133,6 +117,24 @@ async function onclick() {
133117

134118
Errors in `await` expressions will bubble to the nearest [error boundary](svelte-boundary).
135119

120+
## Server-side rendering
121+
122+
Svelte supports asynchronous server-side rendering (SSR) with the `render(...)` API. To use it, simply await the return value:
123+
124+
```js
125+
/// file: server.js
126+
import { render } from 'svelte/server';
127+
import App from './App.svelte';
128+
129+
const { head, body } = +++await+++ render(App);
130+
```
131+
132+
> [!NOTE] If you're using a framework like SvelteKit, this is done on your behalf.
133+
134+
If a `<svelte:boundary>` with a `pending` snippet is encountered during SSR, that snippet will be rendered while the rest of the content is ignored. All `await` expressions encountered outside boundaries with `pending` snippets will resolve and render their contents prior to `await render(...)` returning.
135+
136+
> [!NOTE] In the future, we plan to add a streaming implementation that renders the content in the background.
137+
136138
## Caveats
137139

138140
As an experimental feature, the details of how `await` is handled (and related APIs like `$effect.pending()`) are subject to breaking changes outside of a semver major release, though we intend to keep such changes to a bare minimum.

packages/svelte/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# svelte
22

3+
## 5.39.5
4+
5+
### Patch Changes
6+
7+
- fix: allow `{@html await ...}` and snippets with async content on the server ([#16817](https://github.com/sveltejs/svelte/pull/16817))
8+
9+
- fix: use nginx SSI-compatible comments for `$props.id()` ([#16820](https://github.com/sveltejs/svelte/pull/16820))
10+
311
## 5.39.4
412

513
### Patch Changes

packages/svelte/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "svelte",
33
"description": "Cybernetically enhanced web apps",
44
"license": "MIT",
5-
"version": "5.39.4",
5+
"version": "5.39.5",
66
"type": "module",
77
"types": "./types/index.d.ts",
88
"engines": {

packages/svelte/src/compiler/phases/3-transform/server/visitors/HtmlTag.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,10 @@ import * as b from '#compiler/builders';
99
*/
1010
export function HtmlTag(node, context) {
1111
const expression = /** @type {Expression} */ (context.visit(node.expression));
12-
context.state.template.push(b.call('$.html', expression));
12+
const call = b.call('$.html', expression);
13+
context.state.template.push(
14+
node.metadata.expression.has_await
15+
? b.stmt(b.call('$$renderer.push', b.thunk(call, true)))
16+
: call
17+
);
1318
}

packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
/** @import { ComponentContext } from '../types.js' */
44
import { dev } from '../../../../state.js';
55
import * as b from '#compiler/builders';
6+
import { create_async_block } from './shared/utils.js';
67

78
/**
89
* @param {AST.SnippetBlock} node
@@ -15,6 +16,10 @@ export function SnippetBlock(node, context) {
1516
/** @type {BlockStatement} */ (context.visit(node.body))
1617
);
1718

19+
if (node.body.metadata.has_await) {
20+
fn.body = b.block([create_async_block(fn.body)]);
21+
}
22+
1823
// @ts-expect-error - TODO remove this hack once $$render_inner for legacy bindings is gone
1924
fn.___snippet = true;
2025

packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,9 +441,13 @@ export function prepare_element_spread(
441441
directive.name,
442442
directive.expression.type === 'Identifier' && directive.expression.name === directive.name
443443
? b.id(directive.name)
444-
: /** @type {Expression} */ (context.visit(directive.expression))
444+
: transform(
445+
/** @type {Expression} */ (context.visit(directive.expression)),
446+
directive.metadata.expression
447+
)
445448
)
446449
);
450+
447451
classes = b.object(properties);
448452
}
449453

packages/svelte/src/internal/client/dom/template.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ export function props_id() {
365365
hydrating &&
366366
hydrate_node &&
367367
hydrate_node.nodeType === COMMENT_NODE &&
368-
hydrate_node.textContent?.startsWith(`#`)
368+
hydrate_node.textContent?.startsWith(`$`)
369369
) {
370370
const id = hydrate_node.textContent.substring(1);
371371
hydrate_next();

0 commit comments

Comments
 (0)