|
|
@ -1,21 +1,18 @@
|
|
|
|
/** @import { Derived, Effect, Reaction, Signal, Source, Value } from '#client' */
|
|
|
|
/** @import { Derived, Effect, Reaction, Signal, Source, Value } from '#client' */
|
|
|
|
import { DEV } from 'esm-env';
|
|
|
|
import { DEV } from 'esm-env';
|
|
|
|
import { define_property, get_descriptors, get_prototype_of, index_of } from '../shared/utils.js';
|
|
|
|
import { get_descriptors, get_prototype_of, index_of } from '../shared/utils.js';
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
destroy_block_effect_children,
|
|
|
|
destroy_block_effect_children,
|
|
|
|
destroy_effect_children,
|
|
|
|
destroy_effect_children,
|
|
|
|
execute_effect_teardown,
|
|
|
|
execute_effect_teardown
|
|
|
|
unlink_effect
|
|
|
|
|
|
|
|
} from './reactivity/effects.js';
|
|
|
|
} from './reactivity/effects.js';
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
EFFECT,
|
|
|
|
|
|
|
|
DIRTY,
|
|
|
|
DIRTY,
|
|
|
|
MAYBE_DIRTY,
|
|
|
|
MAYBE_DIRTY,
|
|
|
|
CLEAN,
|
|
|
|
CLEAN,
|
|
|
|
DERIVED,
|
|
|
|
DERIVED,
|
|
|
|
UNOWNED,
|
|
|
|
UNOWNED,
|
|
|
|
DESTROYED,
|
|
|
|
DESTROYED,
|
|
|
|
INERT,
|
|
|
|
|
|
|
|
BRANCH_EFFECT,
|
|
|
|
BRANCH_EFFECT,
|
|
|
|
STATE_SYMBOL,
|
|
|
|
STATE_SYMBOL,
|
|
|
|
BLOCK_EFFECT,
|
|
|
|
BLOCK_EFFECT,
|
|
|
@ -23,12 +20,9 @@ import {
|
|
|
|
DISCONNECTED,
|
|
|
|
DISCONNECTED,
|
|
|
|
REACTION_IS_UPDATING,
|
|
|
|
REACTION_IS_UPDATING,
|
|
|
|
EFFECT_IS_UPDATING,
|
|
|
|
EFFECT_IS_UPDATING,
|
|
|
|
EFFECT_ASYNC,
|
|
|
|
|
|
|
|
RENDER_EFFECT,
|
|
|
|
|
|
|
|
STALE_REACTION,
|
|
|
|
STALE_REACTION,
|
|
|
|
ERROR_VALUE
|
|
|
|
ERROR_VALUE
|
|
|
|
} from './constants.js';
|
|
|
|
} from './constants.js';
|
|
|
|
import { flush_tasks } from './dom/task.js';
|
|
|
|
|
|
|
|
import { internal_set, old_values } from './reactivity/sources.js';
|
|
|
|
import { internal_set, old_values } from './reactivity/sources.js';
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
destroy_derived_effects,
|
|
|
|
destroy_derived_effects,
|
|
|
@ -37,7 +31,6 @@ import {
|
|
|
|
recent_async_deriveds,
|
|
|
|
recent_async_deriveds,
|
|
|
|
update_derived
|
|
|
|
update_derived
|
|
|
|
} from './reactivity/deriveds.js';
|
|
|
|
} from './reactivity/deriveds.js';
|
|
|
|
import * as e from './errors.js';
|
|
|
|
|
|
|
|
import { async_mode_flag, tracing_mode_flag } from '../flags/index.js';
|
|
|
|
import { async_mode_flag, tracing_mode_flag } from '../flags/index.js';
|
|
|
|
import { tracing_expressions, get_stack } from './dev/tracing.js';
|
|
|
|
import { tracing_expressions, get_stack } from './dev/tracing.js';
|
|
|
|
import {
|
|
|
|
import {
|
|
|
@ -50,13 +43,15 @@ import {
|
|
|
|
set_dev_stack
|
|
|
|
set_dev_stack
|
|
|
|
} from './context.js';
|
|
|
|
} from './context.js';
|
|
|
|
import * as w from './warnings.js';
|
|
|
|
import * as w from './warnings.js';
|
|
|
|
import { current_batch, Batch, batch_deriveds } from './reactivity/batch.js';
|
|
|
|
import { Batch, batch_deriveds, flushSync, schedule_effect } from './reactivity/batch.js';
|
|
|
|
import { handle_error, invoke_error_boundary } from './error-handling.js';
|
|
|
|
import { handle_error } from './error-handling.js';
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Effect | null} */
|
|
|
|
export let is_updating_effect = false;
|
|
|
|
let last_scheduled_effect = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let is_updating_effect = false;
|
|
|
|
/** @param {boolean} value */
|
|
|
|
|
|
|
|
export function set_is_updating_effect(value) {
|
|
|
|
|
|
|
|
is_updating_effect = value;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export let is_destroying_effect = false;
|
|
|
|
export let is_destroying_effect = false;
|
|
|
|
|
|
|
|
|
|
|
@ -65,8 +60,6 @@ export function set_is_destroying_effect(value) {
|
|
|
|
is_destroying_effect = value;
|
|
|
|
is_destroying_effect = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle effect queues
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Effect[]} */
|
|
|
|
/** @type {Effect[]} */
|
|
|
|
export let queued_root_effects = [];
|
|
|
|
export let queued_root_effects = [];
|
|
|
|
|
|
|
|
|
|
|
@ -76,8 +69,7 @@ export function set_queued_root_effects(v) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Effect[]} Stack of effects, dev only */
|
|
|
|
/** @type {Effect[]} Stack of effects, dev only */
|
|
|
|
let dev_effect_stack = [];
|
|
|
|
export let dev_effect_stack = [];
|
|
|
|
// Handle signal reactivity tree dependencies and reactions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {null | Reaction} */
|
|
|
|
/** @type {null | Reaction} */
|
|
|
|
export let active_reaction = null;
|
|
|
|
export let active_reaction = null;
|
|
|
@ -522,242 +514,6 @@ export function update_effect(effect) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function log_effect_stack() {
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
|
|
|
console.error(
|
|
|
|
|
|
|
|
'Last ten effects were: ',
|
|
|
|
|
|
|
|
dev_effect_stack.slice(-10).map((d) => d.fn)
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
dev_effect_stack = [];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function infinite_loop_guard() {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
e.effect_update_depth_exceeded();
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
if (DEV) {
|
|
|
|
|
|
|
|
// stack is garbage, ignore. Instead add a console.error message.
|
|
|
|
|
|
|
|
define_property(error, 'stack', {
|
|
|
|
|
|
|
|
value: ''
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try and handle the error so it can be caught at a boundary, that's
|
|
|
|
|
|
|
|
// if there's an effect available from when it was last scheduled
|
|
|
|
|
|
|
|
if (last_scheduled_effect !== null) {
|
|
|
|
|
|
|
|
if (DEV) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
invoke_error_boundary(error, last_scheduled_effect);
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
// Only log the effect stack if the error is re-thrown
|
|
|
|
|
|
|
|
log_effect_stack();
|
|
|
|
|
|
|
|
throw e;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
invoke_error_boundary(error, last_scheduled_effect);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (DEV) {
|
|
|
|
|
|
|
|
log_effect_stack();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function flush_queued_root_effects() {
|
|
|
|
|
|
|
|
var was_updating_effect = is_updating_effect;
|
|
|
|
|
|
|
|
var batch = /** @type {Batch} */ (current_batch);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
var flush_count = 0;
|
|
|
|
|
|
|
|
is_updating_effect = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (queued_root_effects.length > 0) {
|
|
|
|
|
|
|
|
if (flush_count++ > 1000) {
|
|
|
|
|
|
|
|
infinite_loop_guard();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
batch.process(queued_root_effects);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
old_values.clear();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
is_updating_effect = was_updating_effect;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
last_scheduled_effect = null;
|
|
|
|
|
|
|
|
if (DEV) {
|
|
|
|
|
|
|
|
dev_effect_stack = [];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* @param {Array<Effect>} effects
|
|
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
export function flush_queued_effects(effects) {
|
|
|
|
|
|
|
|
var length = effects.length;
|
|
|
|
|
|
|
|
if (length === 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
|
|
|
|
|
|
var effect = effects[i];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((effect.f & (DESTROYED | INERT)) === 0) {
|
|
|
|
|
|
|
|
if (check_dirtiness(effect)) {
|
|
|
|
|
|
|
|
update_effect(effect);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Effects with no dependencies or teardown do not get added to the effect tree.
|
|
|
|
|
|
|
|
// Deferred effects (e.g. `$effect(...)`) _are_ added to the tree because we
|
|
|
|
|
|
|
|
// don't know if we need to keep them until they are executed. Doing the check
|
|
|
|
|
|
|
|
// here (rather than in `update_effect`) allows us to skip the work for
|
|
|
|
|
|
|
|
// immediate effects.
|
|
|
|
|
|
|
|
if (effect.deps === null && effect.first === null && effect.nodes_start === null) {
|
|
|
|
|
|
|
|
if (effect.teardown === null) {
|
|
|
|
|
|
|
|
// remove this effect from the graph
|
|
|
|
|
|
|
|
unlink_effect(effect);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// keep the effect in the graph, but free up some memory
|
|
|
|
|
|
|
|
effect.fn = null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* @param {Effect} signal
|
|
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
export function schedule_effect(signal) {
|
|
|
|
|
|
|
|
var effect = (last_scheduled_effect = signal);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (effect.parent !== null) {
|
|
|
|
|
|
|
|
effect = effect.parent;
|
|
|
|
|
|
|
|
var flags = effect.f;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((flags & (ROOT_EFFECT | BRANCH_EFFECT)) !== 0) {
|
|
|
|
|
|
|
|
if ((flags & CLEAN) === 0) return;
|
|
|
|
|
|
|
|
effect.f ^= CLEAN;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
queued_root_effects.push(effect);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* This function both runs render effects and collects user effects in topological order
|
|
|
|
|
|
|
|
* from the starting effect passed in. Effects will be collected when they match the filtered
|
|
|
|
|
|
|
|
* bitwise flag passed in only. The collected effects array will be populated with all the user
|
|
|
|
|
|
|
|
* effects to be flushed.
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @param {Batch} batch
|
|
|
|
|
|
|
|
* @param {Effect} root
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
export function process_effects(batch, root) {
|
|
|
|
|
|
|
|
root.f ^= CLEAN;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var effect = root.first;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (effect !== null) {
|
|
|
|
|
|
|
|
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 || batch.skipped_effects.has(effect);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!skip && effect.fn !== null) {
|
|
|
|
|
|
|
|
if ((flags & EFFECT_ASYNC) !== 0) {
|
|
|
|
|
|
|
|
const boundary = effect.b;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (check_dirtiness(effect)) {
|
|
|
|
|
|
|
|
var effects = boundary?.pending ? batch.boundary_async_effects : batch.async_effects;
|
|
|
|
|
|
|
|
effects.push(effect);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if ((flags & BLOCK_EFFECT) !== 0) {
|
|
|
|
|
|
|
|
if (check_dirtiness(effect)) {
|
|
|
|
|
|
|
|
update_effect(effect);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (is_branch) {
|
|
|
|
|
|
|
|
effect.f ^= CLEAN;
|
|
|
|
|
|
|
|
} else if ((flags & RENDER_EFFECT) !== 0) {
|
|
|
|
|
|
|
|
// we need to branch here because in legacy mode we run render effects
|
|
|
|
|
|
|
|
// before running block effects
|
|
|
|
|
|
|
|
if (async_mode_flag) {
|
|
|
|
|
|
|
|
batch.render_effects.push(effect);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (check_dirtiness(effect)) {
|
|
|
|
|
|
|
|
update_effect(effect);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if ((flags & EFFECT) !== 0) {
|
|
|
|
|
|
|
|
batch.effects.push(effect);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var child = effect.first;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (child !== null) {
|
|
|
|
|
|
|
|
effect = child;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var parent = effect.parent;
|
|
|
|
|
|
|
|
effect = effect.next;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (effect === null && parent !== null) {
|
|
|
|
|
|
|
|
effect = parent.next;
|
|
|
|
|
|
|
|
parent = parent.parent;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Synchronously flush any pending updates.
|
|
|
|
|
|
|
|
* Returns void if no callback is provided, otherwise returns the result of calling the callback.
|
|
|
|
|
|
|
|
* @template [T=void]
|
|
|
|
|
|
|
|
* @param {(() => T) | undefined} [fn]
|
|
|
|
|
|
|
|
* @returns {T}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
export function flushSync(fn) {
|
|
|
|
|
|
|
|
if (async_mode_flag && active_effect !== null) {
|
|
|
|
|
|
|
|
e.flush_sync_in_effect();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var result;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const batch = Batch.ensure();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (fn) {
|
|
|
|
|
|
|
|
flush_queued_root_effects();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result = fn();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
|
|
flush_tasks();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (queued_root_effects.length === 0) {
|
|
|
|
|
|
|
|
if (batch === current_batch) {
|
|
|
|
|
|
|
|
batch.flush();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// this would be reset in `flush_queued_root_effects` but since we are early returning here,
|
|
|
|
|
|
|
|
// we need to reset it here as well in case the first time there's 0 queued root effects
|
|
|
|
|
|
|
|
last_scheduled_effect = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (DEV) {
|
|
|
|
|
|
|
|
dev_effect_stack = [];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return /** @type {T} */ (result);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
flush_queued_root_effects();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Returns a promise that resolves once any pending state changes have been applied.
|
|
|
|
* Returns a promise that resolves once any pending state changes have been applied.
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
* @returns {Promise<void>}
|
|
|
@ -768,6 +524,7 @@ export async function tick() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await Promise.resolve();
|
|
|
|
await Promise.resolve();
|
|
|
|
|
|
|
|
|
|
|
|
// By calling flushSync we guarantee that any pending state changes are applied after one tick.
|
|
|
|
// By calling flushSync we guarantee that any pending state changes are applied after one tick.
|
|
|
|
// TODO look into whether we can make flushing subsequent updates synchronously in the future.
|
|
|
|
// TODO look into whether we can make flushing subsequent updates synchronously in the future.
|
|
|
|
flushSync();
|
|
|
|
flushSync();
|
|
|
|