From e19a7530daa6c0c5675678633eeae03be5f564ff Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 25 Sep 2026 09:46:43 +0200 Subject: [PATCH] revert fork changes --- .../svelte/src/internal/client/constants.js | 2 - .../svelte/src/internal/client/dev/tracing.js | 2 +- .../internal/client/dom/blocks/branches.js | 21 +- .../src/internal/client/dom/operations.js | 11 +- .../src/internal/client/reactivity/batch.js | 260 ++---------------- .../internal/client/reactivity/deriveds.js | 28 +- .../src/internal/client/reactivity/effects.js | 7 +- .../src/internal/client/reactivity/sources.js | 5 +- .../src/internal/client/reactivity/types.d.ts | 5 +- .../svelte/src/internal/client/runtime.js | 7 +- .../async-fork-async-effect-replay/_config.js | 41 --- .../main.svelte | 42 --- .../async-fork-branch-merge/_config.js | 35 --- .../async-fork-branch-merge/main.svelte | 25 -- .../async-fork-branch-update/_config.js | 52 ---- .../async-fork-branch-update/main.svelte | 22 -- .../_config.js | 21 -- .../main.svelte | 17 -- .../async-fork-commit-empty/_config.js | 22 -- .../async-fork-commit-empty/main.svelte | 14 - .../async-fork-commit-overtaken/_config.js | 42 --- .../async-fork-commit-overtaken/main.svelte | 20 -- .../_config.js | 43 --- .../main.svelte | 20 -- .../_config.js | 38 --- .../main.svelte | 48 ---- .../async-fork-effect-overlap-1/_config.js | 38 --- .../async-fork-effect-overlap-1/main.svelte | 23 -- .../async-fork-effect-overlap-2/_config.js | 34 --- .../async-fork-effect-overlap-2/main.svelte | 23 -- .../_config.js | 48 ---- .../main.svelte | 29 -- .../_config.js | 41 --- .../main.svelte | 27 -- .../_config.js | 32 --- .../main.svelte | 22 -- .../async-fork-nested-branch/Child.svelte | 8 - .../async-fork-nested-branch/_config.js | 79 ------ .../async-fork-nested-branch/main.svelte | 36 --- .../samples/async-fork-no-rerun/_config.js | 18 -- .../samples/async-fork-no-rerun/main.svelte | 12 - .../_config.js | 42 --- .../main.svelte | 43 --- .../_config.js | 42 --- .../main.svelte | 20 -- .../Child.svelte | 8 - .../async-fork-uninitialized-debug/_config.js | 50 ---- .../main.svelte | 20 -- .../samples/async-fork-write-back/_config.js | 17 -- .../samples/async-fork-write-back/main.svelte | 20 -- .../async-overlap-multiple-fork-3/_config.js | 1 + .../_config.js | 1 + .../async-state-new-branch-fork-1/_config.js | 12 +- .../async-state-new-branch-fork-4/_config.js | 1 + .../async-state-new-branch-fork-5/_config.js | 7 + 55 files changed, 80 insertions(+), 1524 deletions(-) delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-async-effect-replay/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-async-effect-replay/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-branch-merge/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-branch-merge/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-branch-update/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-branch-update/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-commit-derived-new-reader/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-commit-derived-new-reader/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-commit-empty/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-commit-empty/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-commit-overtaken/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-commit-overtaken/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-commit-superseded-effect/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-commit-superseded-effect/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-discard-revalidation/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-discard-revalidation/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-1/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-1/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-2/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-2/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-chain/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-chain/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-effect-write-version/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-effect-write-version/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch-static/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch-static/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch/Child.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-no-rerun/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-no-rerun/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-obsolete-multiple-sources/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-obsolete-multiple-sources/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-revalidation-no-refetch/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-revalidation-no-refetch/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-uninitialized-debug/Child.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-uninitialized-debug/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-uninitialized-debug/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-write-back/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-write-back/main.svelte diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 586dd30b93..b086bfedff 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -54,8 +54,6 @@ export const EFFECT_OFFSCREEN = 1 << 25; // Flags used for async export const REACTION_IS_UPDATING = 1 << 21; export const ASYNC = 1 << 22; -/** Set on branch effects that only exist for fork batches */ -export const FORK_ONLY_BRANCH = 1 << 23; export const ERROR_VALUE = 1 << 23; diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index 7ad4ca40af..9fea8175bd 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -1,4 +1,4 @@ -/** @import { Derived, Reaction, Source, Value } from '#client' */ +/** @import { Derived, Reaction, Value } from '#client' */ import { UNINITIALIZED } from '../../../constants.js'; import { snapshot } from '../../shared/clone.js'; import { DERIVED, ASYNC, PROXY_PATH_SYMBOL, STATE_SYMBOL } from '#client/constants'; diff --git a/packages/svelte/src/internal/client/dom/blocks/branches.js b/packages/svelte/src/internal/client/dom/blocks/branches.js index ee4303b7ae..272068a253 100644 --- a/packages/svelte/src/internal/client/dom/blocks/branches.js +++ b/packages/svelte/src/internal/client/dom/blocks/branches.js @@ -7,7 +7,7 @@ import { pause_effect, resume_effect } from '../../reactivity/effects.js'; -import { FORK_ONLY_BRANCH, HMR_ANCHOR } from '../../constants.js'; +import { HMR_ANCHOR } from '../../constants.js'; import { hydrate_node, hydrating } from '../hydration.js'; import { create_text, should_defer_append } from '../operations.js'; import { DEV } from 'esm-env'; @@ -188,7 +188,6 @@ export class BranchManager { ensure(key, fn) { var batch = /** @type {Batch} */ (current_batch); var defer = should_defer_append(); - var first = false; // Re-evaluating in the surviving batch supersedes selections made before a merge, // even though those batches originally had newer IDs and still have commit callbacks. @@ -202,23 +201,16 @@ export class BranchManager { } if (fn && !this.#onscreen.has(key) && !this.#offscreen.has(key)) { - first = true; - if (defer) { var fragment = document.createDocumentFragment(); var target = create_text(); fragment.append(target); - const b = branch(() => fn(target)); this.#offscreen.set(key, { - effect: b, + effect: branch(() => fn(target)), fragment }); - - if (batch.is_fork) { - b.f ^= FORK_ONLY_BRANCH; - } } else { this.#onscreen.set( key, @@ -229,15 +221,6 @@ export class BranchManager { this.#batches.set(batch, key); - const offscreen = this.#offscreen.get(key); - if (offscreen && offscreen.effect.f & FORK_ONLY_BRANCH) { - if (batch.is_fork) { - batch.unskip_effect(offscreen.effect, !first); - } else { - offscreen.effect.f ^= FORK_ONLY_BRANCH; - } - } - if (defer) { for (const [k, effect] of this.#onscreen) { if (k === key) { diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js index 05b3fa4f8a..21b5ee4ecb 100644 --- a/packages/svelte/src/internal/client/dom/operations.js +++ b/packages/svelte/src/internal/client/dom/operations.js @@ -3,7 +3,7 @@ import { hydrate_node, hydrating, reset, set_hydrate_node } from './hydration.js import { DEV } from 'esm-env'; import { init_array_prototype_warnings } from '../dev/equality.js'; import { get_descriptor, is_extensible } from '../../shared/utils.js'; -import { active_effect, new_deps, skipped_deps } from '../runtime.js'; +import { active_effect } from '../runtime.js'; import { async_mode_flag } from '../../flags/index.js'; import { ATTRIBUTES_CACHE, @@ -13,7 +13,7 @@ import { TEXT_CACHE, TEXT_NODE } from '#client/constants'; -import { current_batch, eager_block_effects } from '../reactivity/batch.js'; +import { eager_block_effects } from '../reactivity/batch.js'; import { NAMESPACE_HTML } from '../../../constants.js'; // export these for reference in the compiled code, making global name deduplication unnecessary @@ -249,12 +249,7 @@ export function should_defer_append() { if (eager_block_effects !== null) return false; var flags = /** @type {Effect} */ (active_effect).f; - var ran = (flags & REACTION_RAN) !== 0; - if (ran || !current_batch?.is_fork) return ran; - // In a fork we generally want to defer the append, unless this is the first run - // and that run is terminal, i.e. there are no deps so the e.g. if block can never - // rerun, which means it can never end up in commit callbacks for other batches. - return new_deps !== null || skipped_deps !== 0; + return (flags & REACTION_RAN) !== 0; } /** diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index c9b7777da6..c60bf8bbda 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -17,8 +17,7 @@ import { ERROR_VALUE, MANAGED_EFFECT, REACTION_RAN, - DESTROYING, - FORK_ONLY_BRANCH + DESTROYING } from '#client/constants'; import { async_mode_flag } from '../../flags/index.js'; import { deferred, define_property } from '../../shared/utils.js'; @@ -27,8 +26,7 @@ import { get, increment_write_version, is_dirty, - update_effect, - write_version + update_effect } from '../runtime.js'; import * as e from '../errors.js'; import { flush_tasks, queue_micro_task } from '../dom/task.js'; @@ -160,9 +158,6 @@ export class Batch { return (this.#stale_readers ??= new Set()); } - /** @type {Set} */ - seen_effects = new Set(); - /** @type {Batch | null} */ prev = null; @@ -280,18 +275,17 @@ export class Batch { #skipped_branches = new Map(); /** - * @type {Map | null} + * @type {Set | null} */ #unskipped_branches = null; /** * Inverse of #skipped_branches which we need to tell prior batches to unskip them when committing. - * `true` indicates that this branch is new to the eyes of this fork but was already created before. * Lazily initialized for performance reasons. - * @type {Map} + * @type {Set} */ get unskipped_branches() { - return (this.#unskipped_branches ??= new Map()); + return (this.#unskipped_branches ??= new Set()); } is_fork = false; @@ -301,17 +295,15 @@ export class Batch { #decrement_queued = false; constructor() { - // Put the new batch before the first forked batch - let batch = first_batch; - while (batch && !batch.is_fork) { - batch = batch.next; + if (last_batch === null) { + first_batch = last_batch = this; + } else { + last_batch.next = this; + this.prev = last_batch; } - this.insert_before(batch); - while (batch) { - batch.id = uid++; - batch = batch.next; - } + last_batch = this; + this.linked = true; } #is_deferred() { @@ -354,9 +346,8 @@ export class Batch { * Remove an effect from the #skipped_branches map and reschedule * any tracked dirty/maybe_dirty child effects * @param {Effect} effect - * @param {boolean} is_fork_init */ - unskip_effect(effect, is_fork_init = false) { + unskip_effect(effect) { var tracked = this.#skipped_branches.get(effect); if (tracked) { this.#skipped_branches.delete(effect); @@ -371,7 +362,7 @@ export class Batch { this.schedule(e); } } - if (!this.unskipped_branches.has(effect)) this.unskipped_branches.set(effect, is_fork_init); + this.unskipped_branches.add(effect); } /** @@ -578,42 +569,14 @@ export class Batch { root.f ^= CLEAN; var effect = root.first; - var all_dirty = null; while (effect !== null) { - if (all_dirty) { - if (effect.f & CLEAN) effect.f ^= CLEAN; - if ((effect.f & DIRTY) === 0) effect.f |= MAYBE_DIRTY; - } - var flags = effect.f; var is_branch = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) !== 0; var is_skippable_branch = is_branch && (flags & CLEAN) !== 0; var skip = is_skippable_branch || (flags & INERT) !== 0 || this.#skipped_branches.has(effect); - if ((flags & FORK_ONLY_BRANCH) !== 0) { - var first_time = this.unskipped_branches.get(effect); - - if (first_time === undefined) { - skip = true; - this.skip_effect(effect); - reset_branch( - effect, - /** @type {{d: Effect[], m: Effect[]}} */ (this.#skipped_branches.get(effect)) - ); - } else if (first_time) { - // We're seeing a fork-only branch for the first time in another fork. We need to traverse - // all effects inside it (they're all marked MAYBE_DIRTY). This is necessary because - // dependencies of the effects inside could've updated in the real world since the last time this branch ran. - // TODO this can overfire, maybe there's a way to detect which sources actually changed. - this.unskipped_branches.set(effect, false); - all_dirty ??= effect; - if (effect.f & CLEAN) effect.f ^= CLEAN; - skip = false; - } - } - if (!skip && effect.fn !== null) { if (is_branch) { effect.f ^= CLEAN; @@ -621,11 +584,8 @@ export class Batch { effects.push(effect); } else if (async_mode_flag && (flags & (RENDER_EFFECT | MANAGED_EFFECT)) !== 0) { render_effects.push(effect); - } else { - this.seen_effects.add(effect); - if (is_dirty(effect)) { - update_effect(effect); - } + } else if (is_dirty(effect)) { + update_effect(effect); } var child = effect.first; @@ -645,8 +605,6 @@ export class Batch { } effect = effect.parent; - - if (effect === all_dirty) all_dirty = null; } } } @@ -663,51 +621,6 @@ export class Batch { return null; } - /** - * Mark all reactive trees leading to block/async effects that (indirectly) depend on `value` - * @param {Value} value - * @param {number} status - * @param {boolean} not_yet - whether to mark effects that have not yet run, as opposed to those that have already run - */ - mark(value, status, not_yet = false) { - var reactions = value.reactions; - if (reactions === null) return false; - - let marked = false; - - for (const reaction of reactions) { - var flags = reaction.f; - - if ((flags & DERIVED) !== 0) { - var derived = /** @type {Derived} */ (reaction); - - // deriveds are traversed regardless of their status and only marked - // if something downstream was marked, so that we don't dirty deriveds needlessly - if (this.mark(derived, MAYBE_DIRTY, not_yet)) { - set_signal_status(derived, status); - marked = true; - } - } else { - var effect = /** @type {Effect} */ (reaction); - - if ( - not_yet - ? !this.seen_effects.has(effect) && !this.#dirty_reactions.has(effect) - : (flags & (ASYNC | BLOCK_EFFECT)) === 0 || this.seen_effects.has(effect) - ) { - if (this.#dirty_reactions.get(effect) === MAYBE_DIRTY) { - this.#dirty_reactions.delete(effect); - } - set_signal_status(effect, status); - this.schedule(effect); - marked = true; - } - } - } - - return marked; - } - /** * @param {Batch} batch */ @@ -883,60 +796,12 @@ export class Batch { } for (let batch = first_batch; batch !== null && !this.is_eager; batch = batch.next) { - if (batch.id < this.id && batch.current.has(source)) { + if (!batch.is_fork && batch.id < this.id && batch.current.has(source)) { this.dependent.add(batch); } - - if (batch.is_fork && is_latest_value) { - this.notify_fork(batch, source, is_derived, value); - } } } - /** - * Tell a fork batch that a source has been updated. Will delete that source from the fork, - * discarding it if it has no other sources left, and rerunning it else with the new value. - * @param {Batch} batch A fork - * @param {Value} source - * @param {boolean} is_derived - * @param {any} value - */ - notify_fork(batch, source, is_derived, value) { - const current = batch.current.get(source); - batch.current.delete(source); - - if ([...batch.current.values()].every((value) => value.is_derived)) { - // The real world has overtaken every write of this fork, so it is obsolete. Discard it - // right away (its speculative branches must not be adopted by anyone), and empty - // `current` so that `commit()` can tell this apart from a user-initiated discard - batch.current.clear(); - batch.discard(); - } else { - if (current && current.v !== value) batch.current.set(source, current); - if ( - !is_derived && - (!current || current.v !== value) && - ((source.f & ASYNC) === 0 || - // If the fork ran an async effect, its pending/resolved result belongs to the - // fork. Revalidate it when its inputs change, not when another batch resolves - // the same expression with a different view of those inputs. - !batch.#stale_effects?.has(/** @type {Effect} */ (/** @type {Source} */ (source).e))) - ) { - batch.current.delete(source); - batch.queue_revalidation(source); - } - } - } - - /** @param {Value} source */ - queue_revalidation(source) { - queue_micro_task(() => { - if (this.linked && this.mark(source, DIRTY)) { - this.flush(); - } - }); - } - /** * Activate batch - could be merged into another batch in the meantime, * in which case that other batch becomes the active batch. @@ -1186,21 +1051,6 @@ export class Batch { this.#scheduled.push(effect); } - /** @param {Batch | null} next `null` appends to the end of the list */ - insert_before(next) { - this.#unlink(); - this.prev = next === null ? last_batch : next.prev; - this.next = next; - - if (this.prev === null) first_batch = this; - else this.prev.next = this; - - if (next === null) last_batch = this; - else next.prev = this; - - this.linked = true; - } - #unlink() { // #merge calls #unlink, discard later on does it again - prevent // running it multiple times to not corrupt the linked list @@ -1454,7 +1304,7 @@ export function eager(fn) { get(version); eager_effect(() => { - if (initial && !current_batch?.is_fork) { + if (initial) { // the first time this runs, we create an eager effect // that will run eagerly whenever the expression changes var previous_batch_values = batch_values; @@ -1575,15 +1425,6 @@ export function fork(fn) { return; } - if (batch.current.size === 0) { - // Nothing to commit: either the fork never wrote anything (e.g. it assigned a value - // that was already current), or the real world has since written to every source - // it did write to and the fork was discarded as obsolete (see `notify_fork`) - committed = true; - batch.discard(); - return; - } - if (!batch.linked) { e.fork_discarded(); } @@ -1592,44 +1433,11 @@ export function fork(fn) { batch.is_fork = false; - // Keep IDs in order, then move the batch before all remaining forks - let before = batch; - while (before.prev?.is_fork) { - const prev = before.prev; - const id = batch.id; - batch.id = prev.id; - prev.id = id; - before = prev; - } - if (before !== batch) batch.insert_before(before); - - // Apply changes and update write versions so deriveds see the change. Everything still - // in `batch.current` at this point is the latest value: sources that the real world has - // written to in the meantime were removed from the fork via `notify_fork`, while - // async results are kept up to date by revalidating their producers when inputs change. - // We use fresh versions rather than the fork-time `content.wv`, because the real world - // may have run reactions since then whose versions would otherwise outrank them. + // apply changes and update write versions so deriveds see the change for (var [source, content] of batch.current) { - var changed = source.v !== content.v; source.v = content.v; - - if (!content.is_derived) { - content.wv = source.wv = increment_write_version(); - // dirty those effects the fork did not see yet, e.g. because a later batch created new branches - batch.mark(source, MAYBE_DIRTY, true); - } else if (changed) { - // A derived that was evaluated inside the fork: bump its version too, so that reactions - // which read the (then still old) real value _after_ the fork evaluated it — and which are - // therefore not in `stale_effects` — see a newer dependency version and re-run. - content.wv = source.wv = increment_write_version(); - } - } - - // All the block/async effects the fork executed are now guaranteed to be up to date - for (const effect of batch.stale_effects.keys()) { - effect.wv = write_version; + content.wv = source.wv = increment_write_version(); } - batch.stale_effects.clear(); // trigger any `$state.eager(...)` expressions with the new state. // eager effects don't get scheduled like other effects, so we @@ -1648,31 +1456,17 @@ export function fork(fn) { flush_eager_effects(); }); - // Promote fork-only branches to the real world - for (const e of batch.unskipped_branches.keys()) { - if (e.f & FORK_ONLY_BRANCH) { - e.f ^= FORK_ONLY_BRANCH; - } - } - batch.flush(); - - // Other forks might need to rerun now with the updated state. - let next_batch = batch.next; - while (next_batch) { - for (const [source, current] of batch.current) { - if (next_batch.current.has(source)) { - batch.notify_fork(next_batch, source, current.is_derived, current.v); - } else if (!current.is_derived) { - next_batch.queue_revalidation(source); - } - } - next_batch = next_batch.next; - } - await settled; }, discard: () => { + // cause any MAYBE_DIRTY deriveds to update + // if they depend on things that changed + // inside the discarded fork + for (var source of batch.current.keys()) { + source.wv = increment_write_version(); + } + if (!committed && batch.linked) { batch.discard(); } diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 144e883a77..edf0474cc8 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -121,10 +121,6 @@ export function async_derived(fn, label, location) { var promise = /** @type {Promise} */ (/** @type {unknown} */ (undefined)); var signal = source(/** @type {V} */ (UNINITIALIZED)); - // Besides prod-logic this also helps in DEV to let this be printed - // as a derived when using `$inspect.trace()` - signal.f |= ASYNC; - if (DEV) signal.label = label ?? fn.toString(); // only suspend in async deriveds created on initialisation @@ -133,7 +129,7 @@ export function async_derived(fn, label, location) { /** @type {Set>>} */ var deferreds = new Set(); - signal.e = async_effect(() => { + async_effect(() => { var effect = /** @type {Effect} */ (active_effect); if (DEV) { @@ -273,6 +269,12 @@ export function async_derived(fn, label, location) { } }); + if (DEV) { + // add a flag that lets this be printed as a derived + // when using `$inspect.trace()` + signal.f |= ASYNC; + } + return new Promise((fulfil) => { /** @param {Promise} p */ function next(p) { @@ -402,9 +404,18 @@ export function update_derived(derived) { var value = execute_derived(derived); if (!derived.equals(value)) { - if (current_batch !== null || previous_batch !== null) { - // `capture` decides whether the underlying value is updated (it isn't in a fork, - // or if a later batch holds a newer value) and records it in the batch either way. + // in a fork, we don't update the underlying value, just `batch_values`. + // the underlying value will be updated when the fork is committed. + // otherwise, the next time we get here after a 'real world' state + // change, `derived.equals` may incorrectly return `true` + if (current_batch?.is_fork && derived.deps !== null) { + // bump the write version so that reactions reading the (then still old) + // real value re-run once the fork is discarded — its sources are bumped + // on discard, which must outrank this version to trigger recomputation + derived.wv = increment_write_version(); + } else if (current_batch !== null || previous_batch !== null) { + // `capture` decides whether the underlying value is updated (it isn't + // if a later batch holds a newer value) and records it in the batch either way. // We also write to previous_batch because if it exists, it is a sign that we're // currently in the process of flushing effects. These updates to deriveds may belong // to the previous batch, not the new one (which can already exist if an earlier @@ -447,7 +458,6 @@ export function update_derived(derived) { batch_values?.set(derived, value); } var is_latest_value = - !current_batch?.is_fork && value === derived.v && !derived.deps?.some((d) => batch_values?.has(d) && batch_values.get(d) !== d.v); diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index f0ee2cfbf9..7f52ce3b8d 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -43,7 +43,7 @@ import { DEV } from 'esm-env'; import { define_property } from '../../shared/utils.js'; import { get_next_sibling } from '../dom/operations.js'; import { component_context, dev_current_component_function, dev_stack } from '../context.js'; -import { Batch, collected_effects, current_batch } from './batch.js'; +import { Batch, collected_effects } from './batch.js'; import { flatten } from './async.js'; import { without_reactive_context } from '../dom/elements/bindings/shared.js'; import { set_signal_status } from './status.js'; @@ -368,9 +368,7 @@ export function legacy_pre_effect_reset() { * @returns {Effect} */ export function async_effect(fn) { - const effect = create_effect(ASYNC | EFFECT_PRESERVED, fn); - current_batch?.seen_effects.add(effect); - return effect; + return create_effect(ASYNC | EFFECT_PRESERVED, fn); } /** @@ -417,7 +415,6 @@ export function block(fn, flags = 0) { if (DEV) { effect.dev_stack = dev_stack; } - current_batch?.seen_effects.add(effect); return effect; } diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index b001702093..d3f2b51058 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -71,15 +71,14 @@ export function set_eager_effects_deferred() { */ // TODO rename this to `state` throughout the codebase export function source(v, stack) { - /** @type {Source} */ + /** @type {Source} */ var signal = { f: 0, v, reactions: null, equals, rv: 0, - wv: 0, - e: null + wv: 0 }; if (DEV && tracing_mode_flag) { diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index 8e9b4e7624..0ee8570c3d 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -100,10 +100,7 @@ export interface Effect extends Reaction { dev_stack?: DevStackEntry | null; } -export interface Source extends Value { - /** Only set for ASYNC signals - the corresponding effect that writes to this source */ - e: Effect | null; -} +export type Source = Value; export type MaybeSource = T | Source; diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index fefc70d785..14643b3224 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -502,10 +502,9 @@ export function update_effect(effect) { var own_batch = previous_batch ?? current_batch; // can be null inside flush_eager_effects var is_latest_value = own_batch === null || - (!own_batch.is_fork && - (batch_values === null || - effect.deps === null || - !effect.deps.some((d) => batch_values?.has(d) && batch_values.get(d) !== d.v))); + batch_values === null || + effect.deps === null || + !effect.deps.some((d) => batch_values?.has(d) && batch_values.get(d) !== d.v); if (is_latest_value) { effect.wv = write_version; diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-async-effect-replay/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-async-effect-replay/_config.js deleted file mode 100644 index 165ac7e2c8..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-async-effect-replay/_config.js +++ /dev/null @@ -1,41 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -const buttons = ` - - - - -`; - -export default test({ - async test({ assert, target, instance }) { - const [fork_button, update, resolve, discard] = target.querySelectorAll('button'); - - fork_button.click(); - await tick(); - assert.equal(instance.get_calls(), 1); - assert.htmlEqual(target.innerHTML, buttons); - - // Transfer an invalidation into the fork while its async work is pending. - update.click(); - await tick(); - assert.equal(instance.get_calls(), 1); // can also be 2 at this point, would also be ok - - try { - resolve.click(); - await tick(); - assert.equal(instance.get_calls(), 1); - - // Completing the replacement must not replay the same invalidation. - resolve.click(); - await tick(); - assert.equal(instance.get_calls(), 1); - } finally { - discard.click(); - await tick(); - } - - assert.htmlEqual(target.innerHTML, buttons); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-async-effect-replay/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-async-effect-replay/main.svelte deleted file mode 100644 index 814980d99b..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-async-effect-replay/main.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - -{#if show} - - {#snippet pending()}loading{/snippet} -

{await load(searchParams.value)}

-
-{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-branch-merge/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-branch-merge/_config.js deleted file mode 100644 index caf93e9489..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-branch-merge/_config.js +++ /dev/null @@ -1,35 +0,0 @@ -import { flushSync, tick } from 'svelte'; -import { test } from '../../test'; - -const buttons = ` - - - - - -`; - -export default test({ - async test({ assert, target, logs }) { - const [preload, increment, commit, merge, resolve] = target.querySelectorAll('button'); - - preload.click(); - flushSync(() => increment.click()); - assert.deepEqual(logs, [0, 1]); // [0] would also be ok; the 1 must be done in the context of the fork (impossible to assert so you gotta check manually) - - commit.click(); - assert.deepEqual(logs, [0, 1]); - - flushSync(() => merge.click()); - await tick(); - assert.deepEqual(logs, [0, 1]); - assert.htmlEqual(target.innerHTML, buttons); - - // Resolve the obsolete request for 0, then the existing request for 1. - resolve.click(); - resolve.click(); - await tick(); - assert.deepEqual(logs, [0, 1]); - assert.htmlEqual(target.innerHTML, `${buttons}

1

`); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-branch-merge/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-branch-merge/main.svelte deleted file mode 100644 index 1166cc0a03..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-branch-merge/main.svelte +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - -{#if show && gate > 0} -

{await load(count)}

-{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-branch-update/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-branch-update/_config.js deleted file mode 100644 index 6ab63f7693..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-branch-update/_config.js +++ /dev/null @@ -1,52 +0,0 @@ -import { flushSync, tick } from 'svelte'; -import { test } from '../../test'; - -const buttons = ` - - - - - - - - -`; - -export default test({ - async test({ assert, target }) { - const [preload, increment, commit, reveal, discard, preload_second, commit_second, reset] = - target.querySelectorAll('button'); - - for (const mode of ['commit', 'reveal', 'second-fork']) { - try { - preload.click(); - await tick(); - increment.click(); - await tick(); - assert.htmlEqual(target.innerHTML, buttons); - - if (mode === 'commit') { - commit.click(); - await tick(); - } else if (mode === 'reveal') { - flushSync(() => reveal.click()); - discard.click(); - } else { - preload_second.click(); - discard.click(); - commit_second.click(); - await tick(); - } - - assert.htmlEqual(target.innerHTML, `${buttons}

1 2

`); - flushSync(() => increment.click()); - assert.htmlEqual(target.innerHTML, `${buttons}

2 4

`); - flushSync(() => reset.click()); - assert.htmlEqual(target.innerHTML, buttons); - } catch (e) { - /** @type {Error} */ (e).message = `${mode}: ${/** @type {Error} */ (e).message}`; - throw e; - } - } - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-branch-update/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-branch-update/main.svelte deleted file mode 100644 index a0e98c1c4b..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-branch-update/main.svelte +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - -{#if show} -

{count} {doubled}

-{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-derived-new-reader/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-commit-derived-new-reader/_config.js deleted file mode 100644 index 7fe22437f6..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-derived-new-reader/_config.js +++ /dev/null @@ -1,21 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -const buttons = ``; - -export default test({ - async test({ assert, target }) { - const [fork, show, commit] = target.querySelectorAll('button'); - - fork.click(); - await tick(); - - show.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}

0

`); - - commit.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}two

2

`); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-derived-new-reader/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-commit-derived-new-reader/main.svelte deleted file mode 100644 index 9b425a216d..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-derived-new-reader/main.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - -{#if d2 === 2}two{/if} -{#if show} -

{d2}

-{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-empty/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-commit-empty/_config.js deleted file mode 100644 index 9e17498d26..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-empty/_config.js +++ /dev/null @@ -1,22 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -const buttons = ``; - -// If a fork is auto-discarded it should not throw on user-commit. -export default test({ - mode: ['client'], - async test({ assert, target }) { - const [preload, other, commit] = target.querySelectorAll('button'); - - preload.click(); - await tick(); - other.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}

true 1

`); - - commit.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}

true 1

`); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-empty/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-commit-empty/main.svelte deleted file mode 100644 index b4e35f29a7..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-empty/main.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - -

{open} {other} {error}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-overtaken/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-commit-overtaken/_config.js deleted file mode 100644 index 0ccca0c573..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-overtaken/_config.js +++ /dev/null @@ -1,42 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -const buttons = ` - - - - -`; - -// A fork writes `x`, then the real world writes `x` as well (with async work still pending). -// The fork's write is overtaken and it has nothing left to commit — `commit()` must resolve -// rather than throw `fork_discarded`, and the real world's value wins -export default test({ - mode: ['client'], - async test({ assert, target }) { - await tick(); - const [fork, x5, commit, shift] = target.querySelectorAll('button'); - - fork.click(); // speculative: delay(10) - await tick(); - x5.click(); // real: delay(5); the fork adopts x = 5 and re-runs: delay(5) - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}

0

`); - - commit.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}

0

`); - - shift.click(); // the fork's obsolete delay(10) - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}

0

`); - - shift.click(); // the real delay(5) - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}

5

`); - - shift.click(); // the fork's delay(5), rejected when the fork was cleaned up - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}

5

`); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-overtaken/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-commit-overtaken/main.svelte deleted file mode 100644 index c6c6ba9c1b..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-overtaken/main.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -

{await delay(x)} {error}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-superseded-effect/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-commit-superseded-effect/_config.js deleted file mode 100644 index b7ace65f23..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-superseded-effect/_config.js +++ /dev/null @@ -1,43 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -const buttons = - ''; - -export default test({ - async test({ assert, target }) { - await tick(); - const [forkButton, y, shift, commit] = target.querySelectorAll('button'); - - assert.htmlEqual(target.innerHTML, `

0

${buttons}`); - - // speculative world: x becomes 1, async expression runs with x + y = 1 - forkButton.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `

0

${buttons}`); - - // real world: y becomes 1, async expression runs with x + y = 1 - // (computed with the pre-fork x = 0) — this run supersedes the - // fork's validation of the effect - y.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `

0

${buttons}`); - - // commit the fork while the real batch is still pending — x = 1 is - // written, and the async expression must eventually re-run with the - // committed value, because its in-flight run used x = 0 - commit.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `

0

${buttons}`); - - // resolve all in-flight runs (superseded ones are no-ops) - for (let i = 0; i < 4; i += 1) { - shift.click(); - await tick(); - } - - // x = 1, y = 1 — anything else means the effect resolved with a value - // computed from stale inputs and was never re-run - assert.htmlEqual(target.innerHTML, `

2

${buttons}`); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-superseded-effect/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-commit-superseded-effect/main.svelte deleted file mode 100644 index 3dba3d1004..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-superseded-effect/main.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - -

{await delay(x + y)}

- - - - diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-discard-revalidation/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-discard-revalidation/_config.js deleted file mode 100644 index 45f2a13f23..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-discard-revalidation/_config.js +++ /dev/null @@ -1,38 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -const buttons = ` - - - - - -`; - -export default test({ - mode: ['client'], - async test({ assert, target, logs }) { - await tick(); - const [speculate, update, commit, resolve, reset] = target.querySelectorAll('button'); - - for (const action of [update, commit]) { - speculate.click(); - await tick(); - logs.length = 0; - - // Discard before the queued fork revalidation runs. It must not restart - // the async effect and abort the real world's request, whether the write - // came from a normal update or another fork's commit. - action.click(); - await tick(); - resolve.click(); - await tick(); - - assert.htmlEqual(target.innerHTML, `${buttons}

0/1

`); - assert.deepEqual(logs, ['0/1']); - - reset.click(); - await tick(); - } - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-discard-revalidation/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-discard-revalidation/main.svelte deleted file mode 100644 index 197764c910..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-discard-revalidation/main.svelte +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - -

{await load(x, y)}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-1/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-1/_config.js deleted file mode 100644 index 1849a9b5ee..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-1/_config.js +++ /dev/null @@ -1,38 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -export default test({ - async test({ assert, target, logs }) { - await tick(); - const [x, y, shift, pop, commit] = target.querySelectorAll('button'); - const [p] = target.querySelectorAll('p'); - logs.length = 0; - - x.click(); - await tick(); - assert.deepEqual(logs, ['called with 1,0']); - logs.length = 0; - - y.click(); - await tick(); - assert.deepEqual(logs, ['called with 0,1', 'called with 1,1']); // if 'called with 1,1' happens a few button clicks later that would also be ok - assert.htmlEqual(p.innerHTML, '0'); - logs.length = 0; - - pop.click(); // the rerunning fork - await tick(); - pop.click(); // the first real world run - await tick(); - assert.deepEqual(logs, []); - assert.htmlEqual(p.innerHTML, '1'); - logs.length = 0; - - commit.click(); - await tick(); - assert.deepEqual(logs, []); - assert.htmlEqual(p.innerHTML, '2'); - - pop.click(); - await tick(); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-1/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-1/main.svelte deleted file mode 100644 index 2e461cd884..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-1/main.svelte +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - -

{await delay(console.log('called with ' + x + ',' + y), x + y)}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-2/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-2/_config.js deleted file mode 100644 index 4aeeff78b2..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-2/_config.js +++ /dev/null @@ -1,34 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -export default test({ - async test({ assert, target, logs }) { - await tick(); - const [x, y, shift, pop, commit] = target.querySelectorAll('button'); - const [p] = target.querySelectorAll('p'); - logs.length = 0; - - y.click(); - await tick(); - assert.deepEqual(logs, ['called with 0,1']); - logs.length = 0; - - x.click(); - await tick(); - assert.deepEqual(logs, ['called with 1,1']); - assert.htmlEqual(p.innerHTML, '0'); - logs.length = 0; - - shift.click(); - await tick(); - assert.deepEqual(logs, []); - assert.htmlEqual(p.innerHTML, '1'); - - commit.click(); - await tick(); - pop.click(); - await tick(); - assert.deepEqual(logs, []); - assert.htmlEqual(p.innerHTML, '2'); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-2/main.svelte deleted file mode 100644 index 2e461cd884..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-2/main.svelte +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - -

{await delay(console.log('called with ' + x + ',' + y), x + y)}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-chain/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-chain/_config.js deleted file mode 100644 index 7dc50351cd..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-chain/_config.js +++ /dev/null @@ -1,48 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -export default test({ - async test({ assert, target, logs }) { - await tick(); - const [fork, update, pop, commit] = target.querySelectorAll('button'); - const [sum, doubled] = target.querySelectorAll('p'); - logs.length = 0; - - fork.click(); - await tick(); - assert.deepEqual(logs, ['sum 1,0']); - logs.length = 0; - - // Revalidate twice to also check that retaining async results does not - // prevent the fork from responding to genuine changes to its inputs. - for (const y of [1, 2]) { - update.click(); - await tick(); - assert.deepEqual(logs, [`sum 0,${y}`, `sum 1,${y}`]); - logs.length = 0; - - pop.click(); // resolve the fork before the real world - await tick(); - assert.deepEqual(logs, [`double ${y + 1}`]); - logs.length = 0; - - pop.click(); - await tick(); - assert.deepEqual(logs, [`double ${y}`]); - assert.htmlEqual(sum.innerHTML, String(y)); - assert.htmlEqual(doubled.innerHTML, String(y * 2)); - logs.length = 0; - } - - commit.click(); - await tick(); - assert.deepEqual(logs, []); - assert.htmlEqual(sum.innerHTML, '3'); - assert.htmlEqual(doubled.innerHTML, '6'); - - pop.click(); // the superseded first fork run - await tick(); - assert.htmlEqual(sum.innerHTML, '3'); - assert.htmlEqual(doubled.innerHTML, '6'); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-chain/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-chain/main.svelte deleted file mode 100644 index a682f9e170..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-overlap-chain/main.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - -

{sum}

-

{await double(sum)}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-write-version/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-effect-write-version/_config.js deleted file mode 100644 index 5d164fcc82..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-write-version/_config.js +++ /dev/null @@ -1,41 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -const buttons = ` - - - -`; - -export default test({ - async test({ assert, target, logs }) { - await tick(); - const [fork_button, resolve, commit] = target.querySelectorAll('button'); - - assert.deepEqual(logs, [[0, true]]); - assert.htmlEqual(target.innerHTML, `${buttons}

0

`); - - fork_button.click(); - - await tick(); - assert.deepEqual(logs, [ - [0, true], - [2, true] - ]); - assert.htmlEqual(target.innerHTML, `${buttons}

0

`); - - // `delayed` changes, but `nonnegative` stays true. The async expression has - // already consumed the fork's `doubled`, so it should not run again. - resolve.click(); - await tick(); - assert.deepEqual(logs, [ - [0, true], - [2, true] - ]); - assert.htmlEqual(target.innerHTML, `${buttons}

0

`); - - commit.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}

2

`); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-write-version/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-effect-write-version/main.svelte deleted file mode 100644 index 49083cc97b..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-effect-write-version/main.svelte +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - -

{await load(doubled, nonnegative)}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch-static/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch-static/_config.js deleted file mode 100644 index 1c16112872..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch-static/_config.js +++ /dev/null @@ -1,32 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -const buttons = ` - - - - -`; - -export default test({ - async test({ assert, target }) { - const [preload, reveal, hide, commit] = target.querySelectorAll('button'); - - preload.click(); - reveal.click(); - await tick(); - assert.htmlEqual( - target.innerHTML, - `${buttons}0

constant

keyed

boundary

` - ); - - hide.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}0`); - - // The remaining fork write must not resurrect its obsolete branch selection. - commit.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}1`); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch-static/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch-static/main.svelte deleted file mode 100644 index 13247dda7a..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch-static/main.svelte +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - -{other} - -{#if show} - {#if true}

constant

{/if} - {#key 1}

keyed

{/key} - -

boundary

-
-{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch/Child.svelte deleted file mode 100644 index b131b926f8..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch/Child.svelte +++ /dev/null @@ -1,8 +0,0 @@ - - -{#if pending}pending{/if} -

{pages}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch/_config.js deleted file mode 100644 index 61eb25eeb8..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch/_config.js +++ /dev/null @@ -1,79 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -const buttons = ` - - - - - - - - -`; - -export default test({ - async test({ assert, target, logs }) { - const [preload, reveal, reveal_and_navigate, navigate, resolve, commit, discard, reset] = - target.querySelectorAll('button'); - - for (const mode of ['separate', 'together', 'pending']) { - for (const finish of [commit, discard]) { - try { - preload.click(); - - if (mode !== 'pending') { - resolve.click(); - await tick(); - } - - assert.htmlEqual(target.innerHTML, buttons); - assert.deepEqual(logs, ['load']); - - if (mode === 'together') { - reveal_and_navigate.click(); - } else { - reveal.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}
`); - navigate.click(); - } - - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}
`); - assert.deepEqual(logs, ['load']); - - if (mode === 'pending') { - resolve.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}
`); - } - - finish.click(); - await tick(); - assert.htmlEqual( - target.innerHTML, - `${buttons}
${finish === commit ? 'pending

2

' : ''}
` - ); - assert.deepEqual(logs, ['load']); - - if (finish === commit) { - navigate.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}

2

`); - assert.deepEqual(logs, ['load']); - } - - reset.click(); - await tick(); - assert.htmlEqual(target.innerHTML, buttons); - logs.length = 0; - } catch (e) { - /** @type {Error} */ (e).message = - `${mode}/${finish === commit ? 'commit' : 'discard'}: ${/** @type {Error} */ (e).message}`; - throw e; - } - } - } - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch/main.svelte deleted file mode 100644 index 6d24027f05..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-nested-branch/main.svelte +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - -{#if outer} -
- {#if inner} - console.log(error.message)}> - {#snippet pending()}loading{/snippet} - - - {/if} -
-{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-no-rerun/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-no-rerun/_config.js deleted file mode 100644 index 7fb39c8a84..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-no-rerun/_config.js +++ /dev/null @@ -1,18 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -export default test({ - async test({ assert, target, logs }) { - const [change, commit] = target.querySelectorAll('button'); - - assert.deepEqual(logs, ['hi']); - - change.click(); - await tick(); - assert.deepEqual(logs, ['hi', 'hi']); - - commit.click(); - await tick(); - assert.deepEqual(logs, ['hi', 'hi']); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-no-rerun/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-no-rerun/main.svelte deleted file mode 100644 index 810e09097b..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-no-rerun/main.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - - - -{#if y}hi{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-obsolete-multiple-sources/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-obsolete-multiple-sources/_config.js deleted file mode 100644 index b2f791a40a..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-obsolete-multiple-sources/_config.js +++ /dev/null @@ -1,42 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -const buttons = ` - - - - - -`; - -export default test({ - mode: ['client'], - async test({ assert, target, logs }) { - await tick(); - const [speculate, catch_up, update, commit, discard] = target.querySelectorAll('button'); - - speculate.click(); - await tick(); - catch_up.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}

2

`); - logs.length = 0; - - // All speculative writes are obsolete. Only the real world should react. - update.click(); - await tick(); - try { - assert.htmlEqual(target.innerHTML, `${buttons}

3

`); - assert.deepEqual(logs, ['1/1/1']); - - // Committing an automatically discarded fork should be a no-op and not throw, - // as the user cannot really know that something got automatically discarded. - commit.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}

3

`); - assert.deepEqual(logs, ['1/1/1', 'committed']); - } finally { - discard.click(); - } - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-obsolete-multiple-sources/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-obsolete-multiple-sources/main.svelte deleted file mode 100644 index eeacc2c8d1..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-obsolete-multiple-sources/main.svelte +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - -

{await load(x, y, z)}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-revalidation-no-refetch/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-revalidation-no-refetch/_config.js deleted file mode 100644 index 31474640ef..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-revalidation-no-refetch/_config.js +++ /dev/null @@ -1,42 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -export default test({ - async test({ assert, target, logs }) { - await tick(); - const [forkButton, real, shift, discard] = target.querySelectorAll('button'); - - assert.deepEqual(logs, ['b 0']); - logs.length = 0; - - // speculative world: fork writes b and c, runs the async expression - forkButton.click(); - await tick(); - assert.deepEqual(logs, ['b 1']); - - // real world: b++ re-runs the async expression for real - real.click(); - await tick(); - assert.deepEqual(logs, ['b 1', 'b 1']); - - // resolve the fork's in-flight run — the fork is still speculative, - // nothing should be committed or re-run - shift.click(); - await tick(); - assert.deepEqual(logs, ['b 1', 'b 1']); - - // resolve the real run — the real batch commits b = 1. The fork's world - // value of `b` is also 1 (its own write, now also committed), so the - // fork's async expression sees unchanged inputs and should not re-run - shift.click(); - await tick(); - assert.htmlEqual( - target.innerHTML, - '

1 0

' - ); - assert.deepEqual(logs, ['b 1', 'b 1']); - - discard.click(); - await tick(); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-revalidation-no-refetch/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-revalidation-no-refetch/main.svelte deleted file mode 100644 index f2a6df36d7..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-revalidation-no-refetch/main.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - -

{await delay(console.log(`b ${b}`), b)} {c}

- - - - diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-uninitialized-debug/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-uninitialized-debug/Child.svelte deleted file mode 100644 index b131b926f8..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-uninitialized-debug/Child.svelte +++ /dev/null @@ -1,8 +0,0 @@ - - -{#if pending}pending{/if} -

{pages}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-uninitialized-debug/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-uninitialized-debug/_config.js deleted file mode 100644 index 40a9c5e63d..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-uninitialized-debug/_config.js +++ /dev/null @@ -1,50 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -const buttons = ` - - - - -`; - -export default test({ - async test({ assert, target, logs }) { - const [preload, navigate, commit, discard] = target.querySelectorAll('button'); - - preload.click(); - // Let the async child resolve, without committing its fork. - await new Promise((resolve) => setTimeout(resolve, 0)); - assert.htmlEqual(target.innerHTML, buttons); - assert.deepEqual(logs, []); - - // A real-world update must not evaluate the speculative child in the real world. - navigate.click(); - await tick(); - assert.htmlEqual(target.innerHTML, buttons); - assert.deepEqual(logs, []); - discard.click(); - await tick(); - assert.htmlEqual(target.innerHTML, buttons); - assert.deepEqual(logs, []); - - navigate.click(); - await tick(); - preload.click(); - await new Promise((resolve) => setTimeout(resolve, 0)); - navigate.click(); - await tick(); - assert.htmlEqual(target.innerHTML, buttons); - assert.deepEqual(logs, []); - - commit.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}pending

2

`); - assert.deepEqual(logs, []); - - navigate.click(); - await tick(); - assert.htmlEqual(target.innerHTML, `${buttons}

2

`); - assert.deepEqual(logs, []); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-uninitialized-debug/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-uninitialized-debug/main.svelte deleted file mode 100644 index 43e9600df9..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-uninitialized-debug/main.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -{#if show} - console.log(error.message)}> - {#snippet pending()}loading{/snippet} - - -{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-write-back/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-write-back/_config.js deleted file mode 100644 index 3d96cb58e6..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-write-back/_config.js +++ /dev/null @@ -1,17 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -export default test({ - async test({ assert, target }) { - const [create, commit] = target.querySelectorAll('button'); - const [p] = target.querySelectorAll('p'); - - create.click(); - await tick(); - assert.htmlEqual(p.innerHTML, '0:0'); - - commit.click(); - await tick(); - assert.htmlEqual(p.innerHTML, '0:0'); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-write-back/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-write-back/main.svelte deleted file mode 100644 index 2d424c95aa..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-fork-write-back/main.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - -

{source}:{writable}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-3/_config.js b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-3/_config.js index fd7bc40eed..61360d8dc9 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-3/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-3/_config.js @@ -2,6 +2,7 @@ import { tick } from 'svelte'; import { test } from '../../test'; export default test({ + skip: true, async test({ assert, target }) { await tick(); const [a_b_fork, a_c, b_d, shift, pop, commit] = target.querySelectorAll('button'); diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-all-combinations/_config.js b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-all-combinations/_config.js index 708d90655d..8b7e7784de 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-all-combinations/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-all-combinations/_config.js @@ -2,6 +2,7 @@ import { tick } from 'svelte'; import { test } from '../../test'; export default test({ + skip: true, // TODO more combinations pass on https://github.com/sveltejs/svelte/pull/17971 timeout: 20_000, async test({ assert, target }) { const [x, fork_x, y, fork_y, shift, pop, commit_x, commit_y, reset] = diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-1/_config.js b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-1/_config.js index 24d6c12205..737169cb91 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-1/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-1/_config.js @@ -2,6 +2,7 @@ import { tick } from 'svelte'; import { test } from '../../test'; export default test({ + skip: true, // TODO works on https://github.com/sveltejs/svelte/pull/17971 async test({ assert, target }) { const [x, y, shift, pop, commit] = target.querySelectorAll('button'); @@ -22,7 +23,7 @@ export default test({ ` ); - commit.click(); // puts fork (x) behind y so it has to wait on y first + commit.click(); await tick(); assert.htmlEqual( target.innerHTML, @@ -36,7 +37,7 @@ export default test({ ` ); - shift.click(); // ... which is why nothing happens yet on first shift + shift.click(); await tick(); assert.htmlEqual( target.innerHTML, @@ -46,6 +47,13 @@ export default test({ + universe + universe + "universe" + universe + universe + universe + "universe"
` ); diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-4/_config.js b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-4/_config.js index a20f8c0ba5..6a6399a19e 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-4/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-4/_config.js @@ -2,6 +2,7 @@ import { tick } from 'svelte'; import { test } from '../../test'; export default test({ + skip: true, // TODO works on https://github.com/sveltejs/svelte/pull/17971 async test({ assert, target }) { const [x, y, resolve, commit] = target.querySelectorAll('button'); diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-5/_config.js b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-5/_config.js index 4881907ade..977fb281b3 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-5/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-5/_config.js @@ -2,6 +2,7 @@ import { tick } from 'svelte'; import { test } from '../../test'; export default test({ + skip: true, // TODO works with fork reconciliation async test({ assert, target }) { const [x, y, resolve, commit] = target.querySelectorAll('button'); @@ -44,6 +45,12 @@ export default test({
+ world + "world" + world + world + world + "world" ` );