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 <rich.harris@vercel.com>

* tweak

* tweak

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/12594/head
Dominic Gannaway 5 months ago committed by GitHub
parent 6037b961c0
commit 20b879717a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
breaking: avoid flushing queued updates on mount/hydrate

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

@ -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?.();

@ -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<HydrationTest>(async (config, cwd) => {
if (!override) {
const expected = read(`${cwd}/_expected.html`) ?? rendered.html;
flushSync();
assert.equal(target.innerHTML.trim(), expected.trim());
}

@ -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);
}

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

@ -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, '<input type="text">\nfoo');
}

Loading…
Cancel
Save