Skip to content

Commit bc44975

Browse files
fix: properly hydrate already-resolved async blocks (alternative) (#17641)
This is basically #17611, minus #17640, plus #17639. We need to add the $.next() call after render tags as well as components; rather than duplicating the logic, we can use is_standalone to determine when this is necessary (since this is what prevents $.append(...) from being used). Fixes #17261 Fixes #17608 --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
1 parent f5304ec commit bc44975

15 files changed

Lines changed: 109 additions & 11 deletions

File tree

.changeset/poor-students-nail.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: properly hydrate already-resolved async blocks

packages/svelte/src/compiler/phases/3-transform/client/transform-client.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ export function client_component(analysis, options) {
166166
in_constructor: false,
167167
instance_level_snippets: [],
168168
module_level_snippets: [],
169+
is_standalone: false,
169170

170171
// these are set inside the `Fragment` visitor, and cannot be used until then
171172
init: /** @type {any} */ (null),

packages/svelte/src/compiler/phases/3-transform/client/types.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ export interface ComponentClientTransformState extends ClientTransformState {
8383
readonly instance_level_snippets: VariableDeclaration[];
8484
/** Snippets hoisted to the module */
8585
readonly module_level_snippets: VariableDeclaration[];
86+
87+
/** True if the current node is a) a component or render tag and b) the sole child of a block */
88+
readonly is_standalone: boolean;
8689
}
8790

8891
export type Context = import('zimmerframe').Context<AST.SvelteNode, ClientTransformState>;

packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,10 @@ export function Fragment(node, context) {
122122
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
123123
} else if (is_standalone) {
124124
// no need to create a template, we can just use the existing block's anchor
125-
process_children(trimmed, () => b.id('$$anchor'), false, { ...context, state });
125+
process_children(trimmed, () => b.id('$$anchor'), false, {
126+
...context,
127+
state: { ...state, is_standalone }
128+
});
126129
} else {
127130
/** @type {(is_text: boolean) => Expression} */
128131
const expression = (is_text) => b.call('$.first_child', id, is_text && b.true);

packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ export function RenderTag(node, context) {
8585
)
8686
)
8787
);
88+
89+
if (context.state.is_standalone) {
90+
context.state.init.push(b.stmt(b.call('$.next')));
91+
}
8892
} else {
8993
context.state.init.push(statements.length === 1 ? statements[0] : b.block(statements));
9094
}

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ export function build_component(node, component_name, loc, context) {
461461
memoizer.check_blockers(node.metadata.expression);
462462
}
463463

464-
const statements = [...snippet_declarations, ...memoizer.deriveds(context.state.analysis.runes)];
464+
let statements = [...snippet_declarations, ...memoizer.deriveds(context.state.analysis.runes)];
465465

466466
if (is_component_dynamic) {
467467
const prev = fn;
@@ -515,15 +515,21 @@ export function build_component(node, component_name, loc, context) {
515515
const blockers = memoizer.blockers();
516516

517517
if (async_values || blockers) {
518-
return b.stmt(
519-
b.call(
520-
'$.async',
521-
anchor,
522-
blockers,
523-
async_values,
524-
b.arrow([b.id('$$anchor'), ...memoizer.async_ids()], b.block(statements))
518+
statements = [
519+
b.stmt(
520+
b.call(
521+
'$.async',
522+
anchor,
523+
blockers,
524+
async_values,
525+
b.arrow([b.id('$$anchor'), ...memoizer.async_ids()], b.block(statements))
526+
)
525527
)
526-
);
528+
];
529+
530+
if (context.state.is_standalone) {
531+
statements.push(b.stmt(b.call('$.next')));
532+
}
527533
}
528534

529535
return statements.length > 1 ? b.block(statements) : statements[0];

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,16 @@ export function build_inline_component(node, expression, context) {
101101
}
102102

103103
push_prop(b.prop('init', b.key(attribute.name), value));
104-
} else if (attribute.type === 'BindDirective' && attribute.name !== 'this') {
104+
} else if (attribute.type === 'BindDirective') {
105105
// Bindings are a bit special: we don't want to add them to (async) deriveds but we need to check if they have blockers
106106
optimiser.check_blockers(attribute.metadata.expression);
107107

108+
if (attribute.name === 'this') {
109+
// bind:this is client-only, but we still need to check for blockers to ensure
110+
// the server generates matching hydration markers if the client wraps in $.async
111+
continue;
112+
}
113+
108114
if (attribute.expression.type === 'SequenceExpression') {
109115
const [get, set] = /** @type {SequenceExpression} */ (context.visit(attribute.expression))
110116
.expressions;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<script>
2+
let { children } = $props()
3+
</script>
4+
<div>{@render children?.()}</div>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<script>
2+
let { children } = $props()
3+
</script>
4+
<div>{@render children?.()}</div>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script lang='ts'>
2+
import type { Attachment } from 'svelte/attachments'
3+
4+
export function action(): Attachment<HTMLElement> {
5+
return ()=>{}
6+
}
7+
</script>

0 commit comments

Comments
 (0)