Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/slow-gorillas-yawn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

breaking: avoid flushing queued updates on mount/hydrate
56 changes: 26 additions & 30 deletions packages/svelte/src/internal/client/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}

/**
Expand Down Expand Up @@ -113,40 +112,37 @@ 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);
// 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);

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
Expand Down
7 changes: 2 additions & 5 deletions packages/svelte/src/internal/client/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

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

Expand Down
2 changes: 2 additions & 0 deletions packages/svelte/tests/hydration/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}

Expand Down
2 changes: 2 additions & 0 deletions packages/svelte/tests/runtime-browser/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -46,6 +47,7 @@ export default async function (target) {
};

if (config.html) {
flushSync();
assert.htmlEqual(target.innerHTML, config.html);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { flushSync } from 'svelte';
import { test, ok } from '../../assert';

export default test({
mode: ['client'],
async test({ assert, target }) {
flushSync();
const audio = target.querySelector('audio');
const button = target.querySelector('button');
ok(audio);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { flushSync } from 'svelte';
import { test } from '../../assert';

export default test({
test({ assert, window }) {
flushSync();
document.dispatchEvent(new Event('DOMContentLoaded'));
assert.equal(window.document.querySelector('button')?.textContent, 'Hello world');
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { flushSync } from 'svelte';
import { test } from '../../assert';

export default test({
// Test that template with sole script tag does execute when instantiated in the client.
// Needs to be in this test suite because JSDOM does not quite get this right.
mode: ['client'],
test({ window, assert }) {
flushSync();
// In here to give effects etc time to execute
assert.htmlEqual(window.document.body.innerHTML, 'this should be executed');
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { flushSync } from 'svelte';
import { test } from '../../test';

export default test({
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { flushSync } from 'svelte';
import { test } from '../../test';

export default test({
Expand All @@ -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');
}
Expand Down