fix: coordinate mount of snippets with await expressions (#17021)

* fix: coordinate mount of snippets with await expressions

* try this

* deduplicate

---------

Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
pull/17019/head
Rich Harris 4 weeks ago committed by GitHub
parent 523c85bd3d
commit 2e1dd489f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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,10 @@ export class Boundary {
this.#hydrate_resolved_content();
}
} else {
var anchor = this.#get_anchor();
try {
this.#main_effect = branch(() => children(this.#anchor));
this.#main_effect = branch(() => children(anchor));
} catch (error) {
this.error(error);
}
@ -167,6 +173,10 @@ export class Boundary {
this.#pending = false;
}
}
return () => {
this.#pending_anchor?.remove();
};
}, flags);
if (hydrating) {
@ -194,9 +204,11 @@ export class Boundary {
this.#pending_effect = branch(() => pending(this.#anchor));
Batch.enqueue(() => {
var anchor = this.#get_anchor();
this.#main_effect = this.#run(() => {
Batch.ensure();
return branch(() => this.#children(this.#anchor));
return branch(() => this.#children(anchor));
});
if (this.#pending_count > 0) {
@ -211,6 +223,19 @@ export class Boundary {
});
}
#get_anchor() {
var anchor = this.#anchor;
if (this.#pending) {
this.#pending_anchor = create_text();
this.#anchor.before(this.#pending_anchor);
anchor = this.#pending_anchor;
}
return anchor;
}
/**
* Returns `true` if the effect exists inside a boundary whose pending snippet is shown
* @returns {boolean}
@ -252,6 +277,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);
}

@ -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