Skip to content

Commit 0b47787

Browse files
committed
fix: coordinate mount of snippets with await expressions
1 parent 86aedae commit 0b47787

File tree

5 files changed

+89
-3
lines changed

5 files changed

+89
-3
lines changed

.changeset/huge-poets-tickle.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: coordinate mount of snippets with await expressions

packages/svelte/src/internal/client/dom/blocks/boundary.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
skip_nodes,
3131
set_hydrate_node
3232
} from '../hydration.js';
33-
import { get_next_sibling } from '../operations.js';
33+
import { create_text, get_next_sibling } from '../operations.js';
3434
import { queue_micro_task } from '../task.js';
3535
import * as e from '../../errors.js';
3636
import * as w from '../../warnings.js';
@@ -93,6 +93,9 @@ export class Boundary {
9393
/** @type {DocumentFragment | null} */
9494
#offscreen_fragment = null;
9595

96+
/** @type {TemplateNode | null} */
97+
#pending_anchor = null;
98+
9699
#local_pending_count = 0;
97100
#pending_count = 0;
98101

@@ -156,8 +159,17 @@ export class Boundary {
156159
this.#hydrate_resolved_content();
157160
}
158161
} else {
162+
var anchor = this.#anchor;
163+
164+
if (this.#pending) {
165+
this.#pending_anchor = create_text();
166+
this.#anchor.before(this.#pending_anchor);
167+
168+
anchor = this.#pending_anchor;
169+
}
170+
159171
try {
160-
this.#main_effect = branch(() => children(this.#anchor));
172+
this.#main_effect = branch(() => children(anchor));
161173
} catch (error) {
162174
this.error(error);
163175
}
@@ -166,6 +178,7 @@ export class Boundary {
166178
this.#show_pending_snippet();
167179
} else {
168180
this.#pending = false;
181+
this.#pending_anchor?.remove();
169182
}
170183
}
171184
}, flags);
@@ -195,9 +208,18 @@ export class Boundary {
195208
this.#pending_effect = branch(() => pending(this.#anchor));
196209

197210
Batch.enqueue(() => {
211+
var anchor = this.#anchor;
212+
213+
if (this.#pending) {
214+
this.#pending_anchor = create_text();
215+
this.#anchor.before(this.#pending_anchor);
216+
217+
anchor = this.#pending_anchor;
218+
}
219+
198220
this.#main_effect = this.#run(() => {
199221
Batch.ensure();
200-
return branch(() => this.#children(this.#anchor));
222+
return branch(() => this.#children(anchor));
201223
});
202224

203225
if (this.#pending_count > 0) {
@@ -208,6 +230,7 @@ export class Boundary {
208230
});
209231

210232
this.#pending = false;
233+
this.#pending_anchor?.remove();
211234
}
212235
});
213236
}
@@ -253,6 +276,7 @@ export class Boundary {
253276

254277
if (this.#main_effect !== null) {
255278
this.#offscreen_fragment = document.createDocumentFragment();
279+
this.#offscreen_fragment.append(/** @type {TemplateNode} */ (this.#pending_anchor));
256280
move_effect(this.#main_effect, this.#offscreen_fragment);
257281
}
258282

@@ -288,6 +312,7 @@ export class Boundary {
288312
}
289313

290314
if (this.#offscreen_fragment) {
315+
this.#pending_anchor?.remove();
291316
this.#anchor.before(this.#offscreen_fragment);
292317
this.#offscreen_fragment = null;
293318
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
let { children, push } = $props();
3+
4+
let message = await push('hello from child');
5+
</script>
6+
7+
<p>message: {message}</p>
8+
{@render children()}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { tick } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target }) {
6+
const [shift] = target.querySelectorAll('button');
7+
8+
shift.click();
9+
await tick();
10+
11+
assert.htmlEqual(target.innerHTML, `<button>shift</button><p>loading...</p>`);
12+
13+
shift.click();
14+
await tick();
15+
16+
assert.htmlEqual(
17+
target.innerHTML,
18+
`
19+
<button>shift</button>
20+
<p>message: hello from child</p>
21+
<p>hello from parent</p>
22+
`
23+
);
24+
}
25+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script>
2+
import Child from './Child.svelte';
3+
4+
const resolvers = [];
5+
6+
function push(value) {
7+
const { promise, resolve } = Promise.withResolvers();
8+
resolvers.push(() => resolve(value));
9+
return promise;
10+
}
11+
</script>
12+
13+
<button onclick={() => resolvers.shift()?.()}>shift</button>
14+
15+
<svelte:boundary>
16+
<Child {push}>
17+
<p>{await push('hello from parent')}</p>
18+
</Child>
19+
20+
{#snippet pending()}
21+
<p>loading...</p>
22+
{/snippet}
23+
</svelte:boundary>

0 commit comments

Comments
 (0)