diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 01d5a8ef0a..007b4eb721 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -275,10 +275,10 @@ export class Boundary { if (!this.has_pending_snippet()) { if (this.parent) { this.parent.#update_pending_count(d); - return; } - e.await_outside_boundary(); + // if there's no parent, we're in a scope with no pending snippet + return; } this.#pending_count += d; diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index c5015875a8..289f8001c9 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -31,6 +31,7 @@ import * as e from './errors.js'; import { assign_nodes } from './dom/template.js'; import { is_passive_event } from '../../utils.js'; import { COMMENT_NODE } from './constants.js'; +import { boundary } from './dom/blocks/boundary.js'; /** * This is normally true — block effects should run their intro transitions — @@ -218,35 +219,37 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro var unmount = component_root(() => { var anchor_node = anchor ?? target.appendChild(create_text()); - branch(() => { - if (context) { - push({}); - var ctx = /** @type {ComponentContext} */ (component_context); - ctx.c = context; - } + boundary(anchor_node, {}, (anchor_node) => + branch(() => { + if (context) { + push({}); + var ctx = /** @type {ComponentContext} */ (component_context); + ctx.c = context; + } - if (events) { - // We can't spread the object or else we'd lose the state proxy stuff, if it is one - /** @type {any} */ (props).$$events = events; - } + if (events) { + // We can't spread the object or else we'd lose the state proxy stuff, if it is one + /** @type {any} */ (props).$$events = events; + } - if (hydrating) { - assign_nodes(/** @type {TemplateNode} */ (anchor_node), null); - } + if (hydrating) { + assign_nodes(/** @type {TemplateNode} */ (anchor_node), null); + } - should_intro = intro; - // @ts-expect-error the public typings are not what the actual function looks like - component = Component(anchor_node, props) || {}; - should_intro = true; + should_intro = intro; + // @ts-expect-error the public typings are not what the actual function looks like + component = Component(anchor_node, props) || {}; + should_intro = true; - if (hydrating) { - /** @type {Effect} */ (active_effect).nodes_end = hydrate_node; - } + if (hydrating) { + /** @type {Effect} */ (active_effect).nodes_end = hydrate_node; + } - if (context) { - pop(); - } - }); + if (context) { + pop(); + } + }) + ); return () => { for (var event_name of registered_events) { diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 3b8726fbf6..6bd4383710 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -104,6 +104,8 @@ export function render(component, options = {}) { let { head, body } = payload.collect(); head += payload.global.head.title.value; + body = BLOCK_OPEN + body + BLOCK_CLOSE; // this inserts a fake boundary so hydration matches + for (const { hash, code } of payload.global.css) { head += ``; } @@ -171,6 +173,8 @@ export async function render_async(component, options = {}) { let { head, body } = await payload; head += payload.global.head.title.value; + body = BLOCK_OPEN + body + BLOCK_CLOSE; // this inserts a fake boundary so hydration matches + for (const { hash, code } of payload.global.css) { head += ``; } diff --git a/packages/svelte/tests/runtime-legacy/shared.ts b/packages/svelte/tests/runtime-legacy/shared.ts index 630117abd3..235f37bc52 100644 --- a/packages/svelte/tests/runtime-legacy/shared.ts +++ b/packages/svelte/tests/runtime-legacy/shared.ts @@ -398,12 +398,17 @@ async function run_test_variant( }; } else { const render = variant === 'hydrate' ? hydrate : mount; - instance = render(mod.default, { - target, - props, - intro: config.intro, - recover: config.recover ?? false - }); + // catch and log error from this function + try { + instance = render(mod.default, { + target, + props, + intro: config.intro, + recover: config.recover ?? false + }); + } catch (error) { + console.log(error); + } } } else { instance = createClassComponent({ diff --git a/packages/svelte/tests/runtime-runes/samples/async-hydrate/_config.js b/packages/svelte/tests/runtime-runes/samples/async-hydrate/_config.js index 1a96c20d7e..10c366fd75 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-hydrate/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-hydrate/_config.js @@ -1,14 +1,23 @@ +import { flushSync, settled } from 'svelte'; import { ok, test } from '../../test'; export default test({ - skip: true, + skip_mode: ['hydrate', 'server'], + html: `
hello
`, - async test({ assert, target }) { + async test({ assert, target, variant }) { + if (variant === 'dom') { + await settled(); + } const p = target.querySelector('p'); ok(p); + assert.htmlEqual(p.outerHTML, 'Loading...
'); + + await settled(); + flushSync(); assert.htmlEqual(p.outerHTML, 'hello
'); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/async-hydrate/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-hydrate/main.svelte index f34ef41752..0f609f7ace 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-hydrate/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/async-hydrate/main.svelte @@ -1,2 +1,7 @@ +{await Promise.resolve('hello')}
-{await Promise.resolve('hello')}
+ {#snippet pending()} +Loading...
+ {/snippet} +