fix: coordinate mount of snippets with await expressions

pull/17020/head
Rich Harris 7 days ago
parent 86aedaecf9
commit 0b477871e8

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: coordinate mount of snippets with await expressions

@ -30,7 +30,7 @@ import {
skip_nodes, skip_nodes,
set_hydrate_node set_hydrate_node
} from '../hydration.js'; } from '../hydration.js';
import { get_next_sibling } from '../operations.js'; import { create_text, get_next_sibling } from '../operations.js';
import { queue_micro_task } from '../task.js'; import { queue_micro_task } from '../task.js';
import * as e from '../../errors.js'; import * as e from '../../errors.js';
import * as w from '../../warnings.js'; import * as w from '../../warnings.js';
@ -93,6 +93,9 @@ export class Boundary {
/** @type {DocumentFragment | null} */ /** @type {DocumentFragment | null} */
#offscreen_fragment = null; #offscreen_fragment = null;
/** @type {TemplateNode | null} */
#pending_anchor = null;
#local_pending_count = 0; #local_pending_count = 0;
#pending_count = 0; #pending_count = 0;
@ -156,8 +159,17 @@ export class Boundary {
this.#hydrate_resolved_content(); this.#hydrate_resolved_content();
} }
} else { } else {
var anchor = this.#anchor;
if (this.#pending) {
this.#pending_anchor = create_text();
this.#anchor.before(this.#pending_anchor);
anchor = this.#pending_anchor;
}
try { try {
this.#main_effect = branch(() => children(this.#anchor)); this.#main_effect = branch(() => children(anchor));
} catch (error) { } catch (error) {
this.error(error); this.error(error);
} }
@ -166,6 +178,7 @@ export class Boundary {
this.#show_pending_snippet(); this.#show_pending_snippet();
} else { } else {
this.#pending = false; this.#pending = false;
this.#pending_anchor?.remove();
} }
} }
}, flags); }, flags);
@ -195,9 +208,18 @@ export class Boundary {
this.#pending_effect = branch(() => pending(this.#anchor)); this.#pending_effect = branch(() => pending(this.#anchor));
Batch.enqueue(() => { Batch.enqueue(() => {
var anchor = this.#anchor;
if (this.#pending) {
this.#pending_anchor = create_text();
this.#anchor.before(this.#pending_anchor);
anchor = this.#pending_anchor;
}
this.#main_effect = this.#run(() => { this.#main_effect = this.#run(() => {
Batch.ensure(); Batch.ensure();
return branch(() => this.#children(this.#anchor)); return branch(() => this.#children(anchor));
}); });
if (this.#pending_count > 0) { if (this.#pending_count > 0) {
@ -208,6 +230,7 @@ export class Boundary {
}); });
this.#pending = false; this.#pending = false;
this.#pending_anchor?.remove();
} }
}); });
} }
@ -253,6 +276,7 @@ export class Boundary {
if (this.#main_effect !== null) { if (this.#main_effect !== null) {
this.#offscreen_fragment = document.createDocumentFragment(); this.#offscreen_fragment = document.createDocumentFragment();
this.#offscreen_fragment.append(/** @type {TemplateNode} */ (this.#pending_anchor));
move_effect(this.#main_effect, this.#offscreen_fragment); move_effect(this.#main_effect, this.#offscreen_fragment);
} }
@ -288,6 +312,7 @@ export class Boundary {
} }
if (this.#offscreen_fragment) { if (this.#offscreen_fragment) {
this.#pending_anchor?.remove();
this.#anchor.before(this.#offscreen_fragment); this.#anchor.before(this.#offscreen_fragment);
this.#offscreen_fragment = null; this.#offscreen_fragment = null;
} }

@ -0,0 +1,8 @@
<script>
let { children, push } = $props();
let message = await push('hello from child');
</script>
<p>message: {message}</p>
{@render children()}

@ -0,0 +1,25 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target }) {
const [shift] = target.querySelectorAll('button');
shift.click();
await tick();
assert.htmlEqual(target.innerHTML, `<button>shift</button><p>loading...</p>`);
shift.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>shift</button>
<p>message: hello from child</p>
<p>hello from parent</p>
`
);
}
});

@ -0,0 +1,23 @@
<script>
import Child from './Child.svelte';
const resolvers = [];
function push(value) {
const { promise, resolve } = Promise.withResolvers();
resolvers.push(() => resolve(value));
return promise;
}
</script>
<button onclick={() => resolvers.shift()?.()}>shift</button>
<svelte:boundary>
<Child {push}>
<p>{await push('hello from parent')}</p>
</Child>
{#snippet pending()}
<p>loading...</p>
{/snippet}
</svelte:boundary>
Loading…
Cancel
Save