Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
f31e715
bring tests over from async-blocking-and-merging
Rich-Harris Mar 23, 2026
f48e4e2
WIP
Rich-Harris Mar 24, 2026
8f2613d
WIP
Rich-Harris Mar 24, 2026
b5be33b
WIP
Rich-Harris Mar 24, 2026
da2ab64
WIP
Rich-Harris Mar 24, 2026
55e722d
WIP
Rich-Harris Mar 24, 2026
153ced7
WIP
Rich-Harris Mar 24, 2026
75e333f
all legacy tests passing
Rich-Harris Mar 24, 2026
fa580d8
tweak log_effect_tree
Rich-Harris Mar 24, 2026
5d5ce8b
tidy up
Rich-Harris Mar 24, 2026
7a70030
WIP
Rich-Harris Mar 24, 2026
de94223
always create batch_values, for now
Rich-Harris Mar 24, 2026
12235f0
WIP
Rich-Harris Mar 24, 2026
b40b63a
WIP
Rich-Harris Mar 24, 2026
7f18152
WIP
Rich-Harris Mar 24, 2026
37b9a96
WIP
Rich-Harris Mar 25, 2026
f86bf0d
WIP
Rich-Harris Mar 25, 2026
144611a
WIP
Rich-Harris Mar 25, 2026
afa5af0
WIP
Rich-Harris Mar 25, 2026
d0b8903
WIP
Rich-Harris Mar 25, 2026
4bf3ad7
WIP
Rich-Harris Mar 25, 2026
92ae2c3
WIP
Rich-Harris Mar 25, 2026
4ed6e06
WIP
Rich-Harris Mar 25, 2026
fe2fc3f
note to self
Rich-Harris Mar 25, 2026
b6ccd14
merge main
Rich-Harris Mar 26, 2026
45b1232
fix
Rich-Harris Mar 26, 2026
46e1d08
more debug logging
Rich-Harris Mar 26, 2026
7cd62ff
WIP
Rich-Harris Mar 27, 2026
4d1436b
fix
Rich-Harris Mar 27, 2026
eae5ccb
delete these tests for now
Rich-Harris Mar 27, 2026
930076d
match main
Rich-Harris Mar 27, 2026
2660c7f
fix
Rich-Harris Mar 27, 2026
b8cb7dc
Merge branch 'main' into incremental-batches
Rich-Harris Mar 27, 2026
41d2de1
WIP
Rich-Harris Mar 27, 2026
a22611a
fix
Rich-Harris Mar 29, 2026
66507b8
fix
Rich-Harris Mar 29, 2026
315b404
apparently we need to get rid of this now?
Rich-Harris Mar 30, 2026
59f0fa1
fix
Rich-Harris Mar 30, 2026
92c5b81
WIP
Rich-Harris Mar 30, 2026
ce6cbd1
WIP
Rich-Harris Mar 30, 2026
e6b3759
tidy
Rich-Harris Mar 30, 2026
5f21efe
WIP
Rich-Harris Mar 30, 2026
25adfd1
note to self
Rich-Harris Mar 30, 2026
b02c7f7
fix
Rich-Harris Mar 30, 2026
e118718
simplify
Rich-Harris Mar 30, 2026
cfc16ca
WIP
Rich-Harris Mar 30, 2026
ae3ed1b
WIP
Rich-Harris Mar 30, 2026
ef44fbe
more commented logs
Rich-Harris Mar 30, 2026
494f06a
WIP
Rich-Harris Mar 30, 2026
4826a27
fix
Rich-Harris Mar 30, 2026
a2c39c1
shrug
Rich-Harris Mar 30, 2026
6a96f64
more shrug
Rich-Harris Mar 30, 2026
0d95919
all async tests passing
Rich-Harris Mar 30, 2026
db7cce9
WIP
Rich-Harris Mar 31, 2026
38a4738
fix
Rich-Harris Mar 31, 2026
e0f3dae
fix
Rich-Harris Mar 31, 2026
a0fe497
fix
Rich-Harris Mar 31, 2026
edd3989
this test was just wrong?
Rich-Harris Mar 31, 2026
2beb9ea
slightly hacky fix. all tests passing
Rich-Harris Mar 31, 2026
9d48444
tidy up
Rich-Harris Mar 31, 2026
033573d
no longer need whatever this nonsense was
Rich-Harris Mar 31, 2026
ac238fb
remove maybe_dirty_effects
Rich-Harris Mar 31, 2026
b27f9ed
Merge branch 'main' into incremental-batches
Rich-Harris Mar 31, 2026
d697d50
remove some logging
Rich-Harris Mar 31, 2026
e9a418f
unused
Rich-Harris Mar 31, 2026
3413d3d
tidy up
Rich-Harris Mar 31, 2026
f44e5c5
Merge branch 'main' into incremental-batches
Rich-Harris Apr 1, 2026
7034256
Merge branch 'main' into incremental-batches
Rich-Harris Apr 1, 2026
c5377d3
apparently unnecessary?
Rich-Harris Apr 1, 2026
ce6e3e3
chore: generate markdown files from CPU profiles for agent-driven inv…
Rich-Harris Apr 1, 2026
d00287c
Merge branch 'benchmark-markdown' into incremental-batches
Rich-Harris Apr 1, 2026
9cc5a79
Merge branch 'main' into incremental-batches
Rich-Harris Apr 2, 2026
e7fc7f1
merge main
Rich-Harris Apr 2, 2026
71e4901
avoid allocation
Rich-Harris Apr 2, 2026
e6206ce
bail out of mark_reactions if wv > cv
Rich-Harris Apr 2, 2026
0c5083c
note to self
Rich-Harris Apr 2, 2026
e12ac55
i don't think we need this
Rich-Harris Apr 3, 2026
04f773e
WIP
Rich-Harris Apr 3, 2026
ef744be
combine previous and previous_wvs
Rich-Harris Apr 3, 2026
eca510c
unused
Rich-Harris Apr 3, 2026
6beb6c6
redundant
Rich-Harris Apr 3, 2026
1847921
create new active_batch concept
Rich-Harris Apr 3, 2026
ac3afef
get rid of batch_wvs
Rich-Harris Apr 3, 2026
8308cb8
WIP
Rich-Harris Apr 3, 2026
05688df
get rid of batch_cvs
Rich-Harris Apr 3, 2026
e95b77c
WIP
Rich-Harris Apr 3, 2026
9702a30
WIP
Rich-Harris Apr 3, 2026
92642f0
WIP
Rich-Harris Apr 3, 2026
314ad26
WIP
Rich-Harris Apr 3, 2026
84a8996
WIP
Rich-Harris Apr 3, 2026
bc32eb3
no more batch_values
Rich-Harris Apr 3, 2026
18da3a3
prevent double apply
Rich-Harris Apr 3, 2026
6bec420
tweak
Rich-Harris Apr 3, 2026
cf730f6
tweak
Rich-Harris Apr 3, 2026
3ede5c4
tweak
Rich-Harris Apr 3, 2026
0a0352d
tweak
Rich-Harris Apr 3, 2026
253a20e
get rid of batch.wvs
Rich-Harris Apr 3, 2026
679690b
DRY
Rich-Harris Apr 3, 2026
e39f561
unused
Rich-Harris Apr 3, 2026
9e09508
WIP
Rich-Harris Apr 3, 2026
e4219d5
WIP
Rich-Harris Apr 3, 2026
3dd1d67
print cv/wv when logging tree
Rich-Harris Apr 3, 2026
1747c7b
lint
Rich-Harris Apr 3, 2026
9b34d8f
tidy up
Rich-Harris Apr 3, 2026
6914710
WIP
Rich-Harris Apr 3, 2026
0ac1a34
reinstate WAS_MARKED optimization
Rich-Harris Apr 3, 2026
fe79b76
WIP
Rich-Harris Apr 3, 2026
e080f39
break out report generation
Rich-Harris Apr 3, 2026
f900fcf
lint
Rich-Harris Apr 3, 2026
d197e83
fix
Rich-Harris Apr 3, 2026
1fd260f
fix
Rich-Harris Apr 3, 2026
3bb8f3d
lint
Rich-Harris Apr 3, 2026
b15015b
merge main
Rich-Harris Apr 3, 2026
261dc41
this change appears to be no longer necessary
Rich-Harris Apr 3, 2026
6fb4705
enable async flag
Rich-Harris Apr 3, 2026
03b1f22
add a benchmark
Rich-Harris Apr 3, 2026
1e5ea94
Merge branch 'main' into incremental-batches
Rich-Harris Apr 3, 2026
ec39a11
merge main
Rich-Harris Apr 6, 2026
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
4 changes: 2 additions & 2 deletions packages/svelte/src/internal/client/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@ export const BOUNDARY_EFFECT = 1 << 7;
*/
export const CONNECTED = 1 << 9;
export const CLEAN = 1 << 10;
export const DIRTY = 1 << 11;
export const MAYBE_DIRTY = 1 << 12;
export const INERT = 1 << 13;
export const DESTROYED = 1 << 14;
/** Set once a reaction has run for the first time */
export const REACTION_RAN = 1 << 15;
/** Effect is in the process of getting destroyed. Can be observed in child teardown functions */
export const DESTROYING = 1 << 25;
export const EFFECT_LEGACY = 1 << 26;

// Flags exclusive to effects
/**
Expand All @@ -43,6 +42,7 @@ export const HEAD_EFFECT = 1 << 18;
export const EFFECT_PRESERVED = 1 << 19;
export const USER_EFFECT = 1 << 20;
export const EFFECT_OFFSCREEN = 1 << 25;
export const STATE_EAGER_EFFECT = 1 << 27;

// Flags exclusive to deriveds
/**
Expand Down
10 changes: 9 additions & 1 deletion packages/svelte/src/internal/client/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,15 @@ export function pop(component) {

/** @returns {boolean} */
export function is_runes() {
return !legacy_mode_flag || (component_context !== null && component_context.l === null);
if (!legacy_mode_flag) {
return true;
}

// TODO feels like we could probably simplify this a bit. no tests fail without
// the first part, though would like to better understand usage before deleting
const context = component_context ?? active_reaction?.ctx ?? active_effect?.ctx;

return context?.l === null;
}

/**
Expand Down
29 changes: 16 additions & 13 deletions packages/svelte/src/internal/client/dev/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,18 @@ import {
CLEAN,
CONNECTED,
DERIVED,
DIRTY,
EFFECT,
ASYNC,
DESTROYED,
INERT,
MAYBE_DIRTY,
RENDER_EFFECT,
ROOT_EFFECT,
WAS_MARKED,
MANAGED_EFFECT
} from '#client/constants';
import { snapshot } from '../../shared/clone.js';
import { untrack } from '../runtime.js';
import { get_cv, get_wv } from '../reactivity/batch.js';
import { is_dirty, untrack } from '../runtime.js';

/**
*
Expand Down Expand Up @@ -77,8 +76,15 @@ export function log_effect_tree(effect, highlighted = [], depth = 0, is_reachabl
const flags = effect.f;
let label = effect_label(effect);

let status =
(flags & CLEAN) !== 0 ? 'clean' : (flags & MAYBE_DIRTY) !== 0 ? 'maybe dirty' : 'dirty';
let status = 'clean';

if ((flags & (BRANCH_EFFECT | ROOT_EFFECT)) !== 0) {
if ((flags & CLEAN) === 0) status = 'dirty';
} else {
if (is_dirty(effect)) {
status = 'dirty';
}
}

let styles = [`font-weight: ${status === 'clean' ? 'normal' : 'bold'}`];

Expand All @@ -96,7 +102,7 @@ export function log_effect_tree(effect, highlighted = [], depth = 0, is_reachabl
}

// eslint-disable-next-line no-console
console.group(`%c${label} (${status})`, styles.join('; '));
console.group(`%c${label} (${status}) cv=${get_cv(effect)}`, styles.join('; '));

if (depth === 0) {
const callsite = new Error().stack
Expand Down Expand Up @@ -158,7 +164,7 @@ function log_dep(dep) {

// eslint-disable-next-line no-console
console.groupCollapsed(
`%c$derived %c${dep.label ?? '<unknown>'}`,
`%c$derived %c${dep.label ?? '<unknown>'} wv=${get_wv(derived)} cv=${get_cv(derived)}`,
'font-weight: bold; color: CornflowerBlue',
'font-weight: normal',
untrack(() => snapshot(derived.v))
Expand All @@ -175,7 +181,7 @@ function log_dep(dep) {
} else {
// eslint-disable-next-line no-console
console.log(
`%c$state %c${dep.label ?? '<unknown>'}`,
`%c$state %c${dep.label ?? '<unknown>'} wv=${get_wv(dep)}`,
'font-weight: bold; color: CornflowerBlue',
'font-weight: normal',
untrack(() => snapshot(dep.v))
Expand All @@ -201,8 +207,6 @@ export function log_reactions(signal) {
const names = [];

if ((flags & CLEAN) !== 0) names.push('CLEAN');
if ((flags & DIRTY) !== 0) names.push('DIRTY');
if ((flags & MAYBE_DIRTY) !== 0) names.push('MAYBE_DIRTY');
if ((flags & CONNECTED) !== 0) names.push('CONNECTED');
if ((flags & WAS_MARKED) !== 0) names.push('WAS_MARKED');
if ((flags & INERT) !== 0) names.push('INERT');
Expand Down Expand Up @@ -259,7 +263,7 @@ export function log_reactions(signal) {
} else {
// It's an effect
const label = effect_label(/** @type {Effect} */ (reaction), true);
const status = (flags & MAYBE_DIRTY) !== 0 ? 'maybe dirty' : 'dirty';
const status = is_dirty(reaction) ? 'dirty' : 'clean';

// Collect parent statuses
/** @type {string[]} */
Expand Down Expand Up @@ -380,8 +384,7 @@ export function log_inconsistent_branches(effect) {
const is_branch = (flags & BRANCH_EFFECT) !== 0;

if (is_branch) {
const status =
(flags & CLEAN) !== 0 ? 'clean' : (flags & MAYBE_DIRTY) !== 0 ? 'maybe dirty' : 'dirty';
const status = (flags & CLEAN) !== 0 ? 'clean' : 'dirty';

/** @type {BranchInfo[]} */
const child_branches = [];
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/src/internal/client/dev/tracing.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { snapshot } from '../../shared/clone.js';
import { DERIVED, ASYNC, PROXY_PATH_SYMBOL, STATE_SYMBOL } from '#client/constants';
import { effect_tracking } from '../reactivity/effects.js';
import { active_reaction, untrack } from '../runtime.js';
import { get_cv, get_wv } from '../reactivity/batch.js';

/**
* @typedef {{
Expand All @@ -27,7 +28,7 @@ function log_entry(signal, entry) {

const type = get_type(signal);
const current_reaction = /** @type {Reaction} */ (active_reaction);
const dirty = signal.wv > current_reaction.wv || current_reaction.wv === 0;
const dirty = get_wv(signal) > get_cv(current_reaction);
const style = dirty
? 'color: CornflowerBlue; font-weight: bold'
: 'color: grey; font-weight: normal';
Expand Down
16 changes: 3 additions & 13 deletions packages/svelte/src/internal/client/dom/blocks/boundary.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
/** @import { Effect, Source, TemplateNode, } from '#client' */
import {
BOUNDARY_EFFECT,
DIRTY,
EFFECT_PRESERVED,
EFFECT_TRANSPARENT,
MAYBE_DIRTY
} from '#client/constants';
import { BOUNDARY_EFFECT, EFFECT_PRESERVED, EFFECT_TRANSPARENT } from '#client/constants';
import { HYDRATION_START_ELSE, HYDRATION_START_FAILED } from '../../../../constants.js';
import { component_context, set_component_context } from '../../context.js';
import { handle_error, invoke_error_boundary } from '../../error-handling.js';
Expand Down Expand Up @@ -41,7 +35,6 @@ import { tag } from '../../dev/tracing.js';
import { createSubscriber } from '../../../../reactivity/create-subscriber.js';
import { create_text } from '../operations.js';
import { defer_effect } from '../../reactivity/utils.js';
import { set_signal_status } from '../../reactivity/status.js';

/**
* @typedef {{
Expand Down Expand Up @@ -111,9 +104,6 @@ export class Boundary {
/** @type {Set<Effect>} */
#dirty_effects = new Set();

/** @type {Set<Effect>} */
#maybe_dirty_effects = new Set();

/**
* A source containing the number of pending async deriveds/expressions.
* Only created if `$effect.pending()` is used inside the boundary,
Expand Down Expand Up @@ -273,15 +263,15 @@ export class Boundary {

// any effects that were previously deferred should be transferred
// to the batch, which will flush in the next microtask
batch.transfer_effects(this.#dirty_effects, this.#maybe_dirty_effects);
batch.transfer_effects(this.#dirty_effects);
}

/**
* Defer an effect inside a pending boundary until the boundary resolves
* @param {Effect} effect
*/
defer_effect(effect) {
defer_effect(effect, this.#dirty_effects, this.#maybe_dirty_effects);
defer_effect(effect, this.#dirty_effects);
}

/**
Expand Down
16 changes: 6 additions & 10 deletions packages/svelte/src/internal/client/dom/blocks/each.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
var each_array = derived_safe_equal(() => {
var collection = get_collection();

return is_array(collection) ? collection : collection == null ? [] : array_from(collection);
return /** @type {V[]} */ (
is_array(collection) ? collection : collection == null ? [] : array_from(collection)
);
});

if (DEV) {
Expand All @@ -226,6 +228,8 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
return;
}

var array = get(each_array);

state.pending.delete(batch);

state.fallback = fallback;
Expand Down Expand Up @@ -258,7 +262,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
}

var effect = block(() => {
array = /** @type {V[]} */ (get(each_array));
array = get(each_array);
var length = array.length;

/** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
Expand Down Expand Up @@ -381,14 +385,6 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
// continue in hydration mode
set_hydrating(true);
}

// When we mount the each block for the first time, the collection won't be
// connected to this effect as the effect hasn't finished running yet and its deps
// won't be assigned. However, it's possible that when reconciling the each block
// that a mutation occurred and it's made the collection MAYBE_DIRTY, so reading the
// collection again can provide consistency to the reactive graph again as the deriveds
// will now be `CLEAN`.
get(each_array);
});

/** @type {EachState} */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { is } from '../../../proxy.js';
import { queue_micro_task } from '../../task.js';
import { hydrating } from '../../hydration.js';
import { tick, untrack } from '../../../runtime.js';
import { is_runes } from '../../../context.js';
import { current_batch, previous_batch } from '../../../reactivity/batch.js';
import { async_mode_flag } from '../../../../flags/index.js';

Expand Down
30 changes: 23 additions & 7 deletions packages/svelte/src/internal/client/reactivity/async.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @import { Blocker, Effect, Value } from '#client' */
/** @import { Blocker, Effect, Source, Value } from '#client' */
import { DESTROYED, STALE_REACTION } from '#client/constants';
import { DEV } from 'esm-env';
import {
Expand Down Expand Up @@ -38,8 +38,22 @@ export function flatten(blockers, sync, async, fn) {
// Filter out already-settled blockers - no need to wait for them
var pending = blockers.filter((b) => !b.settled);

var deriveds = sync.map(d);

if (DEV) {
deriveds.forEach((d, i) => {
// TODO this is kinda useful for debugging but a lousy implementation —
// maybe the compiler could pass through the template string
d.label = sync[i]
.toString()
.replace('() => ', '')
.replaceAll('$.eager(() => ', '$state.eager(')
.replace(/\$\.get\((.+?)\)/g, (_, id) => id);
});
}

if (async.length === 0 && pending.length === 0) {
fn(sync.map(d));
fn(deriveds);
return;
}

Expand All @@ -53,12 +67,14 @@ export function flatten(blockers, sync, async, fn) {
? Promise.all(pending.map((b) => b.promise))
: null;

/** @param {Value[]} values */
function finish(values) {
/**
* @param {Source[]} async
*/
function finish(async) {
restore();

try {
fn(values);
fn([...deriveds, ...async]);
} catch (error) {
if ((parent.f & DESTROYED) === 0) {
invoke_error_boundary(error, parent);
Expand All @@ -70,7 +86,7 @@ export function flatten(blockers, sync, async, fn) {

// Fast path: blockers but no async expressions
if (async.length === 0) {
/** @type {Promise<any>} */ (blocker_promise).then(() => finish(sync.map(d)));
/** @type {Promise<any>} */ (blocker_promise).then(() => finish([]));
return;
}

Expand All @@ -79,7 +95,7 @@ export function flatten(blockers, sync, async, fn) {
// Full path: has async expressions
function run() {
Promise.all(async.map((expression) => async_derived(expression)))
.then((result) => finish([...sync.map(d), ...result]))
.then(finish)
.catch((error) => invoke_error_boundary(error, parent))
.finally(() => decrement_pending());
}
Expand Down
Loading
Loading