diff --git a/.changeset/gentle-eagles-walk.md b/.changeset/gentle-eagles-walk.md new file mode 100644 index 0000000000..4ed6c5b3fe --- /dev/null +++ b/.changeset/gentle-eagles-walk.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: wait a microtask for await blocks to reduce UI churn diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index 8956631a6a..3743515cca 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -10,6 +10,7 @@ import { import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js'; import { INERT } from '../../constants.js'; import { DEV } from 'esm-env'; +import { hydrating } from '../hydration.js'; /** * @template V @@ -66,41 +67,61 @@ export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) { return e; } + function pause() { + if (pending_effect) pause_effect(pending_effect); + if (then_effect) pause_effect(then_effect); + if (catch_effect) pause_effect(catch_effect); + } + const effect = block(() => { if (input === (input = get_input())) return; if (is_promise(input)) { - const promise = /** @type {Promise} */ (input); - - if (pending_fn) { - if (pending_effect && (pending_effect.f & INERT) === 0) { - destroy_effect(pending_effect); - } - + if (hydrating && pending_fn) { pending_effect = branch(() => pending_fn(anchor)); + return; } - if (then_effect) pause_effect(then_effect); - if (catch_effect) pause_effect(catch_effect); + var promise = /** @type {Promise} */ (input); + + var settled = false; promise.then( (value) => { + settled = true; if (promise !== input) return; - if (pending_effect) pause_effect(pending_effect); + pause(); if (then_fn) { then_effect = create_effect(then_fn, value); } }, (error) => { + settled = true; if (promise !== input) return; - if (pending_effect) pause_effect(pending_effect); + pause(); if (catch_fn) { catch_effect = create_effect(catch_fn, error); } } ); + + Promise.resolve().then(() => { + // if the promise was already resolved, avoid thrash + if (settled) return; + + if (pending_fn) { + if (pending_effect && (pending_effect.f & INERT) === 0) { + destroy_effect(pending_effect); + } + + pending_effect = branch(() => pending_fn(anchor)); + } + + if (then_effect) pause_effect(then_effect); + if (catch_effect) pause_effect(catch_effect); + }); } else { if (pending_effect) pause_effect(pending_effect); if (catch_effect) pause_effect(catch_effect); diff --git a/packages/svelte/tests/runtime-legacy/shared.ts b/packages/svelte/tests/runtime-legacy/shared.ts index d7b64302b7..8d3ea09e45 100644 --- a/packages/svelte/tests/runtime-legacy/shared.ts +++ b/packages/svelte/tests/runtime-legacy/shared.ts @@ -59,7 +59,6 @@ export interface RuntimeTest = Record void | Promise; test_ssr?: (args: { assert: Assert }) => void | Promise; accessors?: boolean; @@ -104,10 +103,6 @@ export function runtime_suite(runes: boolean) { if (config.skip_mode?.includes('hydrate')) return true; } - if (variant === 'dom' && config.skip_mode?.includes('client')) { - return 'no-test'; - } - if (variant === 'ssr') { if ( (config.mode && !config.mode.includes('server')) || @@ -166,7 +161,6 @@ async function run_test_variant( let logs: string[] = []; let warnings: string[] = []; - let manual_hydrate = false; { // use some crude static analysis to determine if logs/warnings are intercepted. @@ -186,10 +180,6 @@ async function run_test_variant( console.log = (...args) => logs.push(...args); } - if (str.slice(0, i).includes('hydrate')) { - manual_hydrate = true; - } - if (str.slice(0, i).includes('warnings') || config.warnings) { // eslint-disable-next-line no-console console.warn = (...args) => { @@ -307,30 +297,17 @@ async function run_test_variant( let instance: any; let props: any; - let hydrate_fn: Function = () => { - throw new Error('Ensure dom mode is skipped'); - }; if (runes) { props = proxy({ ...(config.props || {}) }); - if (manual_hydrate) { - hydrate_fn = () => { - instance = hydrate(mod.default, { - target, - props, - intro: config.intro, - recover: config.recover ?? false - }); - }; - } else { - const render = variant === 'hydrate' ? hydrate : mount; - instance = render(mod.default, { - target, - props, - intro: config.intro, - recover: config.recover ?? false - }); - } + + const render = variant === 'hydrate' ? hydrate : mount; + instance = render(mod.default, { + target, + props, + intro: config.intro, + recover: config.recover ?? false + }); } else { instance = createClassComponent({ component: mod.default, @@ -380,8 +357,7 @@ async function run_test_variant( raf, compileOptions, logs, - warnings, - hydrate: hydrate_fn + warnings }); } diff --git a/packages/svelte/tests/runtime-runes/samples/await-resolve/Pending.svelte b/packages/svelte/tests/runtime-runes/samples/await-resolve/Pending.svelte new file mode 100644 index 0000000000..b61826a993 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/await-resolve/Pending.svelte @@ -0,0 +1,7 @@ + + +

pending

diff --git a/packages/svelte/tests/runtime-runes/samples/await-resolve/Then.svelte b/packages/svelte/tests/runtime-runes/samples/await-resolve/Then.svelte new file mode 100644 index 0000000000..becf2a13df --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/await-resolve/Then.svelte @@ -0,0 +1,9 @@ + + +

then {value}

diff --git a/packages/svelte/tests/runtime-runes/samples/await-resolve/_config.js b/packages/svelte/tests/runtime-runes/samples/await-resolve/_config.js new file mode 100644 index 0000000000..9c194ac162 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/await-resolve/_config.js @@ -0,0 +1,30 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const [b1, b2] = target.querySelectorAll('button'); + b1.click(); + await Promise.resolve(); + assert.htmlEqual( + target.innerHTML, + `

then a

` + ); + b2.click(); + await Promise.resolve(); + assert.htmlEqual( + target.innerHTML, + `` + ); + await Promise.resolve(); + assert.htmlEqual( + target.innerHTML, + `

then b

` + ); + + assert.deepEqual(logs, [ + 'mounting Pending.svelte', + 'mounting Then.svelte', + 'mounting Then.svelte' + ]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/await-resolve/main.svelte b/packages/svelte/tests/runtime-runes/samples/await-resolve/main.svelte new file mode 100644 index 0000000000..83ad835687 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/await-resolve/main.svelte @@ -0,0 +1,18 @@ + + +{#await current_promise} + +{:then value} + +{/await} + + +