fix: coordinate mount of snippets with await expressions

pull/17021/head
Rich Harris 3 days ago
parent 523c85bd3d
commit 6a7e033be1

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

@ -38,6 +38,7 @@ import { Batch, effect_pending_updates } from '../../reactivity/batch.js';
import { internal_set, source } from '../../reactivity/sources.js';
import { tag } from '../../dev/tracing.js';
import { createSubscriber } from '../../../../reactivity/create-subscriber.js';
import { create_text } from '../operations.js';
/**
* @typedef {{
@ -92,6 +93,9 @@ export class Boundary {
/** @type {DocumentFragment | null} */
#offscreen_fragment = null;
/** @type {TemplateNode | null} */
#pending_anchor = null;
#local_pending_count = 0;
#pending_count = 0;
@ -155,8 +159,17 @@ export class Boundary {
this.#hydrate_resolved_content();
}
} else {
var anchor = this.#anchor;
if (this.#pending) {
this.#pending_anchor = create_text();
this.#anchor.before(this.#pending_anchor);
anchor = this.#pending_anchor;
}
try {
this.#main_effect = branch(() => children(this.#anchor));
this.#main_effect = branch(() => children(anchor));
} catch (error) {
this.error(error);
}
@ -165,6 +178,7 @@ export class Boundary {
this.#show_pending_snippet();
} else {
this.#pending = false;
this.#pending_anchor?.remove();
}
}
}, flags);
@ -194,9 +208,18 @@ export class Boundary {
this.#pending_effect = branch(() => pending(this.#anchor));
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(() => {
Batch.ensure();
return branch(() => this.#children(this.#anchor));
return branch(() => this.#children(anchor));
});
if (this.#pending_count > 0) {
@ -207,6 +230,7 @@ export class Boundary {
});
this.#pending = false;
this.#pending_anchor?.remove();
}
});
}
@ -252,6 +276,7 @@ export class Boundary {
if (this.#main_effect !== null) {
this.#offscreen_fragment = document.createDocumentFragment();
this.#offscreen_fragment.append(/** @type {TemplateNode} */ (this.#pending_anchor));
move_effect(this.#main_effect, this.#offscreen_fragment);
}
@ -287,6 +312,7 @@ export class Boundary {
}
if (this.#offscreen_fragment) {
this.#pending_anchor?.remove();
this.#anchor.before(this.#offscreen_fragment);
this.#offscreen_fragment = null;
}

@ -0,0 +1,7 @@
<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,21 @@
<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