From 20b879717a6385ec3f00df64f1e39526b142e0e0 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 24 Jul 2024 20:32:26 +0100 Subject: [PATCH] breaking: avoid flushing queued updates on mount/hydrate (#12587) * breaking: avoid flushing queued updates on mount/hydrat * Fix tests * Update packages/svelte/src/internal/client/render.js Co-authored-by: Rich Harris * tweak * tweak --------- Co-authored-by: Rich Harris --- .changeset/slow-gorillas-yawn.md | 5 ++ packages/svelte/src/internal/client/render.js | 54 +++++++++---------- .../svelte/src/internal/client/runtime.js | 7 +-- packages/svelte/tests/hydration/test.ts | 2 + .../svelte/tests/runtime-browser/driver.js | 3 ++ .../hydrate-modified-input-group/_config.js | 2 + .../samples/hydrate-modified-input/_config.js | 2 + 7 files changed, 40 insertions(+), 35 deletions(-) create mode 100644 .changeset/slow-gorillas-yawn.md diff --git a/.changeset/slow-gorillas-yawn.md b/.changeset/slow-gorillas-yawn.md new file mode 100644 index 0000000000..376b4d041c --- /dev/null +++ b/.changeset/slow-gorillas-yawn.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +breaking: avoid flushing queued updates on mount/hydrate diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 21cc096325..58bf3a3342 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -79,8 +79,7 @@ export function set_text(text, value) { */ export function mount(component, options) { const anchor = options.anchor ?? options.target.appendChild(empty()); - // Don't flush previous effects to ensure order of outer effects stays consistent - return flush_sync(() => _mount(component, { ...options, anchor }), false); + return _mount(component, { ...options, anchor }); } /** @@ -113,40 +112,35 @@ export function hydrate(component, options) { const previous_hydrate_node = hydrate_node; try { - // Don't flush previous effects to ensure order of outer effects stays consistent - return flush_sync(() => { - var anchor = /** @type {import('#client').TemplateNode} */ (target.firstChild); - while ( - anchor && - (anchor.nodeType !== 8 || /** @type {Comment} */ (anchor).data !== HYDRATION_START) - ) { - anchor = /** @type {import('#client').TemplateNode} */ (anchor.nextSibling); - } + var anchor = /** @type {import('#client').TemplateNode} */ (target.firstChild); + while ( + anchor && + (anchor.nodeType !== 8 || /** @type {Comment} */ (anchor).data !== HYDRATION_START) + ) { + anchor = /** @type {import('#client').TemplateNode} */ (anchor.nextSibling); + } - if (!anchor) { - throw HYDRATION_ERROR; - } + if (!anchor) { + throw HYDRATION_ERROR; + } - set_hydrating(true); - set_hydrate_node(/** @type {Comment} */ (anchor)); - hydrate_next(); + set_hydrating(true); + set_hydrate_node(/** @type {Comment} */ (anchor)); + hydrate_next(); - const instance = _mount(component, { ...options, anchor }); + const instance = _mount(component, { ...options, anchor }); - if ( - hydrate_node.nodeType !== 8 || - /** @type {Comment} */ (hydrate_node).data !== HYDRATION_END - ) { - w.hydration_mismatch(); - throw HYDRATION_ERROR; - } + if ( + hydrate_node.nodeType !== 8 || + /** @type {Comment} */ (hydrate_node).data !== HYDRATION_END + ) { + w.hydration_mismatch(); + throw HYDRATION_ERROR; + } - // flush_sync will run this callback and then synchronously run any pending effects, - // which don't belong to the hydration phase anymore - therefore reset it here - set_hydrating(false); + set_hydrating(false); - return instance; - }, false); + return /** @type {Exports} */ (instance); } catch (error) { if (error === HYDRATION_ERROR) { // TODO it's possible for event listeners to have been added and diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 922193f10e..9749002bf7 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -670,10 +670,9 @@ function process_effects(effect, collected_effects) { * Internal version of `flushSync` with the option to not flush previous effects. * Returns the result of the passed function, if given. * @param {() => any} [fn] - * @param {boolean} [flush_previous] * @returns {any} */ -export function flush_sync(fn, flush_previous = true) { +export function flush_sync(fn) { var previous_scheduler_mode = current_scheduler_mode; var previous_queued_root_effects = current_queued_root_effects; @@ -687,9 +686,7 @@ export function flush_sync(fn, flush_previous = true) { current_queued_root_effects = root_effects; is_micro_task_queued = false; - if (flush_previous) { - flush_queued_root_effects(previous_queued_root_effects); - } + flush_queued_root_effects(previous_queued_root_effects); var result = fn?.(); diff --git a/packages/svelte/tests/hydration/test.ts b/packages/svelte/tests/hydration/test.ts index fab0e5b308..d592a65de3 100644 --- a/packages/svelte/tests/hydration/test.ts +++ b/packages/svelte/tests/hydration/test.ts @@ -8,6 +8,7 @@ import { suite, assert_ok, type BaseTest } from '../suite.js'; import { createClassComponent } from 'svelte/legacy'; import { render } from 'svelte/server'; import type { CompileOptions } from '#compiler'; +import { flushSync } from 'svelte'; interface HydrationTest extends BaseTest { load_compiled?: boolean; @@ -114,6 +115,7 @@ const { test, run } = suite(async (config, cwd) => { if (!override) { const expected = read(`${cwd}/_expected.html`) ?? rendered.html; + flushSync(); assert.equal(target.innerHTML.trim(), expected.trim()); } diff --git a/packages/svelte/tests/runtime-browser/driver.js b/packages/svelte/tests/runtime-browser/driver.js index 7a5603e9b8..ef6acd08f6 100644 --- a/packages/svelte/tests/runtime-browser/driver.js +++ b/packages/svelte/tests/runtime-browser/driver.js @@ -5,6 +5,7 @@ import config from '__CONFIG__'; // @ts-expect-error import * as assert from 'assert.js'; import { createClassComponent } from 'svelte/legacy'; +import { flushSync } from 'svelte'; /** @param {HTMLElement} target */ export default async function (target) { @@ -45,6 +46,8 @@ export default async function (target) { } while (new Date().getTime() <= start + ms); }; + flushSync(); + if (config.html) { assert.htmlEqual(target.innerHTML, config.html); } diff --git a/packages/svelte/tests/runtime-runes/samples/hydrate-modified-input-group/_config.js b/packages/svelte/tests/runtime-runes/samples/hydrate-modified-input-group/_config.js index 7f3bfac707..7596fd97be 100644 --- a/packages/svelte/tests/runtime-runes/samples/hydrate-modified-input-group/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/hydrate-modified-input-group/_config.js @@ -1,3 +1,4 @@ +import { flushSync } from 'svelte'; import { test } from '../../test'; export default test({ @@ -9,6 +10,7 @@ export default test({ inputs[1].dispatchEvent(new window.Event('change')); // Hydration shouldn't reset the value to 1 hydrate(); + flushSync(); assert.htmlEqual( target.innerHTML, diff --git a/packages/svelte/tests/runtime-runes/samples/hydrate-modified-input/_config.js b/packages/svelte/tests/runtime-runes/samples/hydrate-modified-input/_config.js index e5bbe5b0fe..10dac59fae 100644 --- a/packages/svelte/tests/runtime-runes/samples/hydrate-modified-input/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/hydrate-modified-input/_config.js @@ -1,3 +1,4 @@ +import { flushSync } from 'svelte'; import { test } from '../../test'; export default test({ @@ -9,6 +10,7 @@ export default test({ input.dispatchEvent(new window.Event('input')); // Hydration shouldn't reset the value to empty hydrate(); + flushSync(); assert.htmlEqual(target.innerHTML, '\nfoo'); }