Skip to content
Open
Show file tree
Hide file tree
Changes from all 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/legal-mangos-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: change title only after any pending work has completed
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types' */
import * as b from '#compiler/builders';
import { build_template_chunk } from './shared/utils.js';
import { build_template_chunk, Memoizer } from './shared/utils.js';

/**
* @param {AST.TitleElement} node
* @param {ComponentContext} context
*/
export function TitleElement(node, context) {
const memoizer = new Memoizer();
const { has_state, value } = build_template_chunk(
/** @type {any} */ (node.fragment.nodes),
context
context,
context.state,
(value, metadata) => memoizer.add(value, metadata)
);
const evaluated = context.state.scope.evaluate(value);

Expand All @@ -26,9 +29,21 @@ export function TitleElement(node, context) {
)
);

// Always in an $effect so it only changes the title once async work is done
if (has_state) {
context.state.update.push(statement);
context.state.after_update.push(
b.stmt(
b.call(
'$.template_effect',
b.arrow(memoizer.apply(), b.block([statement])),
memoizer.sync_values(),
memoizer.async_values(),
memoizer.blockers(),
b.true
)
)
);
} else {
context.state.init.push(statement);
context.state.after_update.push(b.stmt(b.call('$.effect', b.thunk(b.block([statement])))));
}
}
21 changes: 9 additions & 12 deletions packages/svelte/src/internal/client/reactivity/batch.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,25 @@ import {
MAYBE_DIRTY,
DERIVED,
BOUNDARY_EFFECT,
EAGER_EFFECT
EAGER_EFFECT,
HEAD_EFFECT
} from '#client/constants';
import { async_mode_flag } from '../../flags/index.js';
import { deferred, define_property } from '../../shared/utils.js';
import {
active_effect,
get,
increment_write_version,
is_dirty,
is_updating_effect,
set_is_updating_effect,
set_signal_status,
tick,
update_effect
} from '../runtime.js';
import * as e from '../errors.js';
import { flush_tasks, queue_micro_task } from '../dom/task.js';
import { DEV } from 'esm-env';
import { invoke_error_boundary } from '../error-handling.js';
import {
flush_eager_effects,
eager_effects,
old_values,
set_eager_effects,
source,
update
} from './sources.js';
import { flush_eager_effects, old_values, set_eager_effects, source, update } from './sources.js';
import { eager_effect, unlink_effect } from './effects.js';

/**
Expand Down Expand Up @@ -800,7 +792,12 @@ export function schedule_effect(signal) {

// if the effect is being scheduled because a parent (each/await/etc) block
// updated an internal source, bail out or we'll cause a second flush
if (is_flushing && effect === active_effect && (flags & BLOCK_EFFECT) !== 0) {
if (
is_flushing &&
effect === active_effect &&
(flags & BLOCK_EFFECT) !== 0 &&
(flags & HEAD_EFFECT) === 0
) {
return;
}

Expand Down
5 changes: 3 additions & 2 deletions packages/svelte/src/internal/client/reactivity/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,10 +366,11 @@ export function render_effect(fn, flags = 0) {
* @param {Array<() => any>} sync
* @param {Array<() => Promise<any>>} async
* @param {Array<Promise<void>>} blockers
* @param {boolean} defer
*/
export function template_effect(fn, sync = [], async = [], blockers = []) {
export function template_effect(fn, sync = [], async = [], blockers = [], defer = false) {
flatten(blockers, sync, async, (values) => {
create_effect(RENDER_EFFECT, () => fn(...values.map(get)), true);
create_effect(defer ? EFFECT : RENDER_EFFECT, () => fn(...values.map(get)), true);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't love this, but it felt even weirder to have a entirely different method that is 90% the same, and I couldn't come up with a good name for that method

});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script>
let { deferred } = $props();

function push() {
const d = Promise.withResolvers();
deferred.push(() => d.resolve());
return d.promise;
}
</script>

<svelte:head>
<title>title</title>
</svelte:head>

<p>{await push()}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { tick } from 'svelte';
import { test } from '../../test';

export default test({
async test({ assert, target }) {
const [toggle, resolve] = target.querySelectorAll('button');
toggle.click();
await tick();
assert.equal(window.document.title, '');

toggle.click();
await tick();
assert.equal(window.document.title, '');

toggle.click();
await tick();
assert.equal(window.document.title, '');

resolve.click();
await tick();
await tick();
assert.equal(window.document.title, 'title');
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script>
import Inner from './Inner.svelte';
let deferred = [];
let show = $state(false);
</script>

<button onclick={() => show = !show}>toggle</button>
<button onclick={() => deferred.pop()()}>resolve</button>
{#if show}
<Inner {deferred} />
{/if}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script>
let { deferred } = $props();

function push() {
const d = Promise.withResolvers();
deferred.push(() => d.resolve('title'));
return d.promise;
}
</script>

<svelte:head>
<title>{await push()}</title>
</svelte:head>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { tick } from 'svelte';
import { test } from '../../test';

export default test({
async test({ assert, target }) {
const [toggle, resolve] = target.querySelectorAll('button');
toggle.click();
await tick();
assert.equal(window.document.title, '');

toggle.click();
await tick();
assert.equal(window.document.title, '');

toggle.click();
await tick();
assert.equal(window.document.title, '');

resolve.click();
await tick();
assert.equal(window.document.title, 'title');
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script>
import Inner from './Inner.svelte';
let deferred = [];
let show = $state(false);
</script>

<button onclick={() => show = !show}>toggle</button>
<button onclick={() => deferred.pop()()}>resolve</button>
{#if show}
<Inner {deferred} />
{/if}
Loading