chore: move signal logic into subdirectory (#10582)

* move effect code

* move source code

* derived

* move more stuff

* simplify

* init is a daft name for something that runs repeatedly

* remove unneeded effect variant

* chore: reshuffle part2 (#10588)

* extract store

* move prop/init to render

* introduce constants file and move all constants there to reduce cyclic dependencies

* move reactive_import to render

* move bubble_event into render, move (raf) task handing into its own file

* expect-error did hide that one

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
pull/10590/head
Rich Harris 2 years ago committed by GitHub
parent 187400b409
commit 29890bb616
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,16 +1,12 @@
import {
ROOT_BLOCK,
HEAD_BLOCK,
DYNAMIC_ELEMENT_BLOCK,
DYNAMIC_COMPONENT_BLOCK,
SNIPPET_BLOCK
} from './constants.js';
import { current_block } from './runtime.js';
export const ROOT_BLOCK = 0;
export const IF_BLOCK = 1;
export const EACH_BLOCK = 2;
export const EACH_ITEM_BLOCK = 3;
export const AWAIT_BLOCK = 4;
export const KEY_BLOCK = 5;
export const HEAD_BLOCK = 6;
export const DYNAMIC_COMPONENT_BLOCK = 7;
export const DYNAMIC_ELEMENT_BLOCK = 8;
export const SNIPPET_BLOCK = 9;
/**
* @param {boolean} intro
* @returns {import('./types.js').RootBlock}

@ -0,0 +1,26 @@
export const SOURCE = 1;
export const DERIVED = 1 << 1;
export const EFFECT = 1 << 2;
export const PRE_EFFECT = 1 << 3;
export const RENDER_EFFECT = 1 << 4;
export const MANAGED = 1 << 6;
export const UNOWNED = 1 << 7;
export const CLEAN = 1 << 8;
export const DIRTY = 1 << 9;
export const MAYBE_DIRTY = 1 << 10;
export const INERT = 1 << 11;
export const DESTROYED = 1 << 12;
export const ROOT_BLOCK = 0;
export const IF_BLOCK = 1;
export const EACH_BLOCK = 2;
export const EACH_ITEM_BLOCK = 3;
export const AWAIT_BLOCK = 4;
export const KEY_BLOCK = 5;
export const HEAD_BLOCK = 6;
export const DYNAMIC_COMPONENT_BLOCK = 7;
export const DYNAMIC_ELEMENT_BLOCK = 8;
export const SNIPPET_BLOCK = 9;
export const UNINITIALIZED = Symbol();
export const STATE_SYMBOL = Symbol('$state');

@ -1,5 +1,6 @@
import { createClassComponent } from '../../legacy/legacy-client.js';
import { render_effect, destroy_signal } from './runtime.js';
import { destroy_signal } from './runtime.js';
import { render_effect } from './reactivity/computations.js';
import { open, close } from './render.js';
import { define_property } from './utils.js';

@ -1,6 +1,6 @@
/** @typedef {{ file: string, line: number, column: number }} Location */
import { STATE_SYMBOL } from '../proxy.js';
import { STATE_SYMBOL } from '../constants.js';
import { untrack } from '../runtime.js';
/** @type {Record<string, Array<{ start: Location, end: Location, component: Function }>>} */

@ -1,17 +1,16 @@
import { is_promise } from '../../../common.js';
import { AWAIT_BLOCK } from '../../block.js';
import { hydrate_block_anchor } from '../../hydration.js';
import { remove } from '../../reconciler.js';
import {
UNINITIALIZED,
current_block,
destroy_signal,
execute_effect,
flushSync,
push_destroy_fn,
render_effect
push_destroy_fn
} from '../../runtime.js';
import { render_effect } from '../../reactivity/computations.js';
import { trigger_transitions } from '../../transitions.js';
import { AWAIT_BLOCK, UNINITIALIZED } from '../../constants.js';
/** @returns {import('../../types.js').AwaitBlock} */
export function create_await_block() {

@ -7,7 +7,6 @@ import {
EACH_KEYED
} from '../../../../constants.js';
import { noop } from '../../../common.js';
import { EACH_BLOCK, EACH_ITEM_BLOCK } from '../../block.js';
import {
current_hydration_fragment,
get_hydration_fragment,
@ -21,14 +20,14 @@ import {
current_block,
destroy_signal,
execute_effect,
mutable_source,
push_destroy_fn,
render_effect,
set_signal_value,
source
set_signal_value
} from '../../runtime.js';
import { render_effect } from '../../reactivity/computations.js';
import { source, mutable_source } from '../../reactivity/sources.js';
import { trigger_transitions } from '../../transitions.js';
import { is_array } from '../../utils.js';
import { EACH_BLOCK, EACH_ITEM_BLOCK } from '../../constants.js';
const NEW_BLOCK = -1;
const MOVED_BLOCK = 99999999;

@ -1,4 +1,4 @@
import { IF_BLOCK } from '../../block.js';
import { IF_BLOCK } from '../../constants.js';
import {
current_hydration_fragment,
hydrate_block_anchor,
@ -6,13 +6,8 @@ import {
set_current_hydration_fragment
} from '../../hydration.js';
import { remove } from '../../reconciler.js';
import {
current_block,
destroy_signal,
execute_effect,
push_destroy_fn,
render_effect
} from '../../runtime.js';
import { current_block, destroy_signal, execute_effect, push_destroy_fn } from '../../runtime.js';
import { render_effect } from '../../reactivity/computations.js';
import { trigger_transitions } from '../../transitions.js';
/** @returns {import('../../types.js').IfBlock} */

@ -1,16 +1,10 @@
import { KEY_BLOCK } from '../../block.js';
import { UNINITIALIZED, KEY_BLOCK } from '../../constants.js';
import { hydrate_block_anchor } from '../../hydration.js';
import { remove } from '../../reconciler.js';
import {
UNINITIALIZED,
current_block,
destroy_signal,
execute_effect,
push_destroy_fn,
render_effect,
safe_not_equal
} from '../../runtime.js';
import { current_block, destroy_signal, execute_effect, push_destroy_fn } from '../../runtime.js';
import { render_effect } from '../../reactivity/computations.js';
import { trigger_transitions } from '../../transitions.js';
import { safe_not_equal } from '../../reactivity/equality.js';
/** @returns {import('../../types.js').KeyBlock} */
function create_key_block() {

@ -0,0 +1,59 @@
import { run_all } from '../../common.js';
let is_task_queued = false;
let is_raf_queued = false;
/** @type {Array<() => void>} */
let current_queued_tasks = [];
/** @type {Array<() => void>} */
let current_raf_tasks = [];
function process_task() {
is_task_queued = false;
const tasks = current_queued_tasks.slice();
current_queued_tasks = [];
run_all(tasks);
}
function process_raf_task() {
is_raf_queued = false;
const tasks = current_raf_tasks.slice();
current_raf_tasks = [];
run_all(tasks);
}
/**
* @param {() => void} fn
* @returns {void}
*/
export function schedule_task(fn) {
if (!is_task_queued) {
is_task_queued = true;
setTimeout(process_task, 0);
}
current_queued_tasks.push(fn);
}
/**
* @param {() => void} fn
* @returns {void}
*/
export function schedule_raf_task(fn) {
if (!is_raf_queued) {
is_raf_queued = true;
requestAnimationFrame(process_raf_task);
}
current_raf_tasks.push(fn);
}
/**
* Synchronously run any queued tasks.
*/
export function flush_tasks() {
if (is_task_queued) {
process_task();
}
if (is_raf_queued) {
process_raf_task();
}
}

@ -1,7 +1,7 @@
// Handle hydration
import { schedule_task } from './dom/task.js';
import { empty } from './operations.js';
import { schedule_task } from './runtime.js';
/**
* Use this variable to guard everything related to hydration code so it can be treeshaken out

@ -1,16 +1,13 @@
import { DEV } from 'esm-env';
import {
effect_active,
get,
set,
update,
source,
updating_derived,
UNINITIALIZED,
mutable_source,
batch_inspect,
current_component_context
} from './runtime.js';
import { effect_active } from './reactivity/computations.js';
import {
array_prototype,
define_property,
@ -22,8 +19,8 @@ import {
object_prototype
} from './utils.js';
import { add_owner, check_ownership, strip_owner } from './dev/ownership.js';
export const STATE_SYMBOL = Symbol('$state');
import { mutable_source, source } from './reactivity/sources.js';
import { STATE_SYMBOL, UNINITIALIZED } from './constants.js';
/**
* @template T

@ -0,0 +1,258 @@
import { DEV } from 'esm-env';
import {
current_block,
current_component_context,
current_consumer,
current_effect,
destroy_signal,
flush_local_render_effects,
schedule_effect
} from '../runtime.js';
import { default_equals, safe_equal } from './equality.js';
import {
DIRTY,
MANAGED,
RENDER_EFFECT,
EFFECT,
PRE_EFFECT,
DERIVED,
UNOWNED,
CLEAN,
UNINITIALIZED
} from '../constants.js';
/**
* @template V
* @param {import('../types.js').SignalFlags} flags
* @param {V} value
* @param {import('../types.js').Block | null} block
*/
function create_computation_signal(flags, value, block) {
/** @type {import('../types.js').ComputationSignal<V>} */
const signal = {
b: block,
c: null,
d: null,
e: null,
f: flags,
l: 0,
i: null,
r: null,
v: value,
w: 0,
x: null,
y: null
};
if (DEV) {
// @ts-expect-error
signal.inspect = new Set();
}
return signal;
}
/**
* @param {import('../types.js').ComputationSignal} target_signal
* @param {import('../types.js').ComputationSignal} ref_signal
* @returns {void}
*/
export function push_reference(target_signal, ref_signal) {
const references = target_signal.r;
if (references === null) {
target_signal.r = [ref_signal];
} else {
references.push(ref_signal);
}
}
/**
* @param {import('../types.js').EffectType} type
* @param {(() => void | (() => void)) | ((b: import('../types.js').Block) => void | (() => void))} fn
* @param {boolean} sync
* @param {null | import('../types.js').Block} block
* @param {boolean} schedule
* @returns {import('../types.js').EffectSignal}
*/
function internal_create_effect(type, fn, sync, block, schedule) {
const signal = create_computation_signal(type | DIRTY, null, block);
signal.i = fn;
signal.x = current_component_context;
if (current_effect !== null) {
signal.l = current_effect.l + 1;
if ((type & MANAGED) === 0) {
push_reference(current_effect, signal);
}
}
if (schedule) {
schedule_effect(signal, sync);
}
return signal;
}
/**
* @returns {boolean}
*/
export function effect_active() {
return current_effect ? (current_effect.f & MANAGED) === 0 : false;
}
/**
* Internal representation of `$effect(...)`
* @param {() => void | (() => void)} fn
* @returns {import('../types.js').EffectSignal}
*/
export function user_effect(fn) {
if (current_effect === null) {
throw new Error(
'ERR_SVELTE_ORPHAN_EFFECT' +
(DEV ? ': The Svelte $effect rune can only be used during component initialisation.' : '')
);
}
const apply_component_effect_heuristics =
current_effect.f & RENDER_EFFECT &&
current_component_context !== null &&
!current_component_context.m;
const effect = internal_create_effect(
EFFECT,
fn,
false,
current_block,
!apply_component_effect_heuristics
);
if (apply_component_effect_heuristics) {
const context = /** @type {import('../types.js').ComponentContext} */ (
current_component_context
);
(context.e ??= []).push(effect);
}
return effect;
}
/**
* Internal representation of `$effect.root(...)`
* @param {() => void | (() => void)} fn
* @returns {() => void}
*/
export function user_root_effect(fn) {
const effect = render_effect(fn, current_block, true);
return () => {
destroy_signal(effect);
};
}
/**
* @param {() => void | (() => void)} fn
* @returns {import('../types.js').EffectSignal}
*/
export function effect(fn) {
return internal_create_effect(EFFECT, fn, false, current_block, true);
}
/**
* @param {() => void | (() => void)} fn
* @returns {import('../types.js').EffectSignal}
*/
export function managed_effect(fn) {
return internal_create_effect(EFFECT | MANAGED, fn, false, current_block, true);
}
/**
* @param {() => void | (() => void)} fn
* @param {boolean} sync
* @returns {import('../types.js').EffectSignal}
*/
export function managed_pre_effect(fn, sync) {
return internal_create_effect(PRE_EFFECT | MANAGED, fn, sync, current_block, true);
}
/**
* Internal representation of `$effect.pre(...)`
* @param {() => void | (() => void)} fn
* @returns {import('../types.js').EffectSignal}
*/
export function pre_effect(fn) {
if (current_effect === null) {
throw new Error(
'ERR_SVELTE_ORPHAN_EFFECT' +
(DEV
? ': The Svelte $effect.pre rune can only be used during component initialisation.'
: '')
);
}
const sync = current_effect !== null && (current_effect.f & RENDER_EFFECT) !== 0;
return internal_create_effect(
PRE_EFFECT,
() => {
const val = fn();
flush_local_render_effects();
return val;
},
sync,
current_block,
true
);
}
/**
* This effect is used to ensure binding are kept in sync. We use a pre effect to ensure we run before the
* bindings which are in later effects. However, we don't use a pre_effect directly as we don't want to flush anything.
*
* @param {() => void | (() => void)} fn
* @returns {import('../types.js').EffectSignal}
*/
export function invalidate_effect(fn) {
return internal_create_effect(PRE_EFFECT, fn, true, current_block, true);
}
/**
* @template {import('../types.js').Block} B
* @param {(block: B) => void | (() => void)} fn
* @param {any} block
* @param {any} managed
* @param {any} sync
* @returns {import('../types.js').EffectSignal}
*/
export function render_effect(fn, block = current_block, managed = false, sync = true) {
let flags = RENDER_EFFECT;
if (managed) {
flags |= MANAGED;
}
return internal_create_effect(flags, /** @type {any} */ (fn), sync, block, true);
}
/**
* @template V
* @param {() => V} fn
* @returns {import('../types.js').ComputationSignal<V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function derived(fn) {
const is_unowned = current_effect === null;
const flags = is_unowned ? DERIVED | UNOWNED : DERIVED;
const signal = /** @type {import('../types.js').ComputationSignal<V>} */ (
create_computation_signal(flags | CLEAN, UNINITIALIZED, current_block)
);
signal.i = fn;
signal.e = default_equals;
if (current_consumer !== null) {
push_reference(current_consumer, signal);
}
return signal;
}
/**
* @template V
* @param {() => V} fn
* @returns {import('../types.js').ComputationSignal<V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function derived_safe_equal(fn) {
const signal = derived(fn);
signal.e = safe_equal;
return signal;
}

@ -0,0 +1,30 @@
/**
* @param {unknown} a
* @param {unknown} b
* @returns {boolean}
*/
export function default_equals(a, b) {
return a === b;
}
/**
* @param {unknown} a
* @param {unknown} b
* @returns {boolean}
*/
export function safe_not_equal(a, b) {
// eslint-disable-next-line eqeqeq
return a != a
? // eslint-disable-next-line eqeqeq
b == b
: a !== b || (a !== null && typeof a === 'object') || typeof a === 'function';
}
/**
* @param {unknown} a
* @param {unknown} b
* @returns {boolean}
*/
export function safe_equal(a, b) {
return !safe_not_equal(a, b);
}

@ -0,0 +1,70 @@
import { DEV } from 'esm-env';
import { current_component_context } from '../runtime.js';
import { default_equals, safe_equal } from './equality.js';
import { CLEAN, SOURCE } from '../constants.js';
/**
* @template V
* @param {V} initial_value
* @returns {import('../types.js').SourceSignal<V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function source(initial_value) {
return create_source_signal(SOURCE | CLEAN, initial_value);
}
/**
* @template V
* @param {V} initial_value
* @returns {import('../types.js').SourceSignal<V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function mutable_source(initial_value) {
const s = source(initial_value);
s.e = safe_equal;
// bind the signal to the component context, in case we need to
// track updates to trigger beforeUpdate/afterUpdate callbacks
if (current_component_context) {
(current_component_context.d ??= []).push(s);
}
return s;
}
/**
* @template V
* @param {import('../types.js').SignalFlags} flags
* @param {V} value
* @returns {import('../types.js').SourceSignal<V> | import('../types.js').SourceSignal<V> & import('../types.js').SourceSignalDebug}
*/
function create_source_signal(flags, value) {
if (DEV) {
return {
// consumers
c: null,
// equals
e: default_equals,
// flags
f: flags,
// value
v: value,
// write version
w: 0,
// this is for DEV only
inspect: new Set()
};
}
return {
// consumers
c: null,
// equals
e: default_equals,
// flags
f: flags,
// value
v: value,
// write version
w: 0
};
}

@ -0,0 +1,153 @@
import { subscribe_to_store } from '../../../store/utils.js';
import { noop } from '../../common.js';
import { UNINITIALIZED } from '../constants.js';
import { get, set, set_ignore_mutation_validation, untrack } from '../runtime.js';
import { user_effect } from './computations.js';
import { mutable_source } from './sources.js';
/**
* Gets the current value of a store. If the store isn't subscribed to yet, it will create a proxy
* signal that will be updated when the store is. The store references container is needed to
* track reassignments to stores and to track the correct component context.
* @template V
* @param {import('../types.js').Store<V> | null | undefined} store
* @param {string} store_name
* @param {import('../types.js').StoreReferencesContainer} stores
* @returns {V}
*/
export function store_get(store, store_name, stores) {
/** @type {import('../types.js').StoreReferencesContainer[''] | undefined} */
let entry = stores[store_name];
const is_new = entry === undefined;
if (is_new) {
entry = {
store: null,
last_value: null,
value: mutable_source(UNINITIALIZED),
unsubscribe: noop
};
// TODO: can we remove this code? it was refactored out when we split up source/comptued signals
// push_destroy_fn(entry.value, () => {
// /** @type {import('../types.js').StoreReferencesContainer['']} */ (entry).last_value =
// /** @type {import('../types.js').StoreReferencesContainer['']} */ (entry).value.value;
// });
stores[store_name] = entry;
}
if (is_new || entry.store !== store) {
entry.unsubscribe();
entry.store = store ?? null;
entry.unsubscribe = connect_store_to_signal(store, entry.value);
}
const value = get(entry.value);
// This could happen if the store was cleaned up because the component was destroyed and there's a leak on the user side.
// In that case we don't want to fail with a cryptic Symbol error, but rather return the last value we got.
return value === UNINITIALIZED ? entry.last_value : value;
}
/**
* @template V
* @param {import('../types.js').Store<V> | null | undefined} store
* @param {import('../types.js').SourceSignal<V>} source
*/
function connect_store_to_signal(store, source) {
if (store == null) {
set(source, undefined);
return noop;
}
/** @param {V} v */
const run = (v) => {
set_ignore_mutation_validation(true);
set(source, v);
set_ignore_mutation_validation(false);
};
return subscribe_to_store(store, run);
}
/**
* Sets the new value of a store and returns that value.
* @template V
* @param {import('../types.js').Store<V>} store
* @param {V} value
* @returns {V}
*/
export function store_set(store, value) {
store.set(value);
return value;
}
/**
* Unsubscribes from all auto-subscribed stores on destroy
* @param {import('../types.js').StoreReferencesContainer} stores
*/
export function unsubscribe_on_destroy(stores) {
on_destroy(() => {
let store_name;
for (store_name in stores) {
const ref = stores[store_name];
ref.unsubscribe();
// TODO: can we remove this code? it was refactored out when we split up source/comptued signals
// destroy_signal(ref.value);
}
});
}
/**
* Updates a store with a new value.
* @param {import('../types.js').Store<V>} store the store to update
* @param {any} expression the expression that mutates the store
* @param {V} new_value the new store value
* @template V
*/
export function mutate_store(store, expression, new_value) {
store.set(new_value);
return expression;
}
/**
* @template V
* @param {unknown} val
* @returns {val is import('../types.js').Store<V>}
*/
export function is_store(val) {
return (
typeof val === 'object' &&
val !== null &&
typeof (/** @type {import('../types.js').Store<V>} */ (val).subscribe) === 'function'
);
}
/**
* @param {import('../types.js').Store<number>} store
* @param {number} store_value
* @param {1 | -1} [d]
* @returns {number}
*/
export function update_store(store, store_value, d = 1) {
store.set(store_value + d);
return store_value;
}
/**
* @param {import('../types.js').Store<number>} store
* @param {number} store_value
* @param {1 | -1} [d]
* @returns {number}
*/
export function update_pre_store(store, store_value, d = 1) {
const value = store_value + d;
store.set(value);
return value;
}
/**
* Schedules a callback to run immediately before the component is unmounted.
* @param {() => any} fn
* @returns {void}
*/
function on_destroy(fn) {
user_effect(() => () => untrack(fn));
}

@ -21,7 +21,11 @@ import {
PassiveDelegatedEvents,
DelegatedEvents,
AttributeAliases,
namespace_svg
namespace_svg,
PROPS_IS_IMMUTABLE,
PROPS_IS_RUNES,
PROPS_IS_UPDATED,
PROPS_IS_LAZY_INITIAL
} from '../../constants.js';
import {
create_fragment_from_html,
@ -31,21 +35,30 @@ import {
remove
} from './reconciler.js';
import {
render_effect,
destroy_signal,
is_signal,
push_destroy_fn,
execute_effect,
untrack,
effect,
flush_sync,
current_block,
managed_effect,
push,
current_component_context,
pop,
deep_read
current_component_context,
deep_read,
get,
set,
is_signals_recorded,
inspect_fn
} from './runtime.js';
import {
render_effect,
effect,
managed_effect,
derived,
pre_effect,
user_effect
} from './reactivity/computations.js';
import {
current_hydration_fragment,
get_hydration_fragment,
@ -62,9 +75,11 @@ import {
is_function,
object_assign
} from './utils.js';
import { is_promise } from '../common.js';
import { run } from '../common.js';
import { bind_transition, trigger_transitions } from './transitions.js';
import { STATE_SYMBOL } from './proxy.js';
import { mutable_source, source } from './reactivity/sources.js';
import { safe_equal, safe_not_equal } from './reactivity/equality.js';
import { STATE_SYMBOL } from './constants.js';
/** @type {Set<string>} */
const all_registerd_events = new Set();
@ -2691,3 +2706,198 @@ function get_root_for_style(node) {
}
return /** @type {Document} */ (node.ownerDocument);
}
/**
* This function is responsible for synchronizing a possibly bound prop with the inner component state.
* It is used whenever the compiler sees that the component writes to the prop, or when it has a default prop_value.
* @template V
* @param {Record<string, unknown>} props
* @param {string} key
* @param {number} flags
* @param {V | (() => V)} [initial]
* @returns {(() => V | ((arg: V) => V) | ((arg: V, mutation: boolean) => V))}
*/
export function prop(props, key, flags, initial) {
var immutable = (flags & PROPS_IS_IMMUTABLE) !== 0;
var runes = (flags & PROPS_IS_RUNES) !== 0;
var prop_value = /** @type {V} */ (props[key]);
var setter = get_descriptor(props, key)?.set;
if (prop_value === undefined && initial !== undefined) {
if (setter && runes) {
// TODO consolidate all these random runtime errors
throw new Error(
'ERR_SVELTE_BINDING_FALLBACK' +
(DEV
? `: Cannot pass undefined to bind:${key} because the property contains a fallback value. Pass a different value than undefined to ${key}.`
: '')
);
}
// @ts-expect-error would need a cumbersome method overload to type this
if ((flags & PROPS_IS_LAZY_INITIAL) !== 0) initial = initial();
prop_value = /** @type {V} */ (initial);
if (setter) setter(prop_value);
}
var getter = () => {
var value = /** @type {V} */ (props[key]);
if (value !== undefined) initial = undefined;
return value === undefined ? /** @type {V} */ (initial) : value;
};
// easy mode — prop is never written to
if ((flags & PROPS_IS_UPDATED) === 0) {
return getter;
}
// intermediate mode — prop is written to, but the parent component had
// `bind:foo` which means we can just call `$$props.foo = value` directly
if (setter) {
return function (/** @type {V} */ value) {
if (arguments.length === 1) {
/** @type {Function} */ (setter)(value);
return value;
} else {
return getter();
}
};
}
// hard mode. this is where it gets ugly — the value in the child should
// synchronize with the parent, but it should also be possible to temporarily
// set the value to something else locally.
var from_child = false;
var was_from_child = false;
// The derived returns the current value. The underlying mutable
// source is written to from various places to persist this value.
var inner_current_value = mutable_source(prop_value);
var current_value = derived(() => {
var parent_value = getter();
var child_value = get(inner_current_value);
if (from_child) {
from_child = false;
was_from_child = true;
return child_value;
}
was_from_child = false;
return (inner_current_value.v = parent_value);
});
if (!immutable) current_value.e = safe_equal;
return function (/** @type {V} */ value, mutation = false) {
var current = get(current_value);
// legacy nonsense — need to ensure the source is invalidated when necessary
// also needed for when handling inspect logic so we can inspect the correct source signal
if (is_signals_recorded || (DEV && inspect_fn)) {
// set this so that we don't reset to the parent value if `d`
// is invalidated because of `invalidate_inner_signals` (rather
// than because the parent or child value changed)
from_child = was_from_child;
// invoke getters so that signals are picked up by `invalidate_inner_signals`
getter();
get(inner_current_value);
}
if (arguments.length > 0) {
if (mutation || (immutable ? value !== current : safe_not_equal(value, current))) {
from_child = true;
set(inner_current_value, mutation ? current : value);
get(current_value); // force a synchronisation immediately
}
return value;
}
return current;
};
}
/**
* Legacy-mode only: Call `onMount` callbacks and set up `beforeUpdate`/`afterUpdate` effects
*/
export function init() {
const context = /** @type {import('./types.js').ComponentContext} */ (current_component_context);
const callbacks = context.u;
if (!callbacks) return;
// beforeUpdate
pre_effect(() => {
observe_all(context);
callbacks.b.forEach(run);
});
// onMount (must run before afterUpdate)
user_effect(() => {
const fns = untrack(() => callbacks.m.map(run));
return () => {
for (const fn of fns) {
if (typeof fn === 'function') {
fn();
}
}
};
});
// afterUpdate
user_effect(() => {
observe_all(context);
callbacks.a.forEach(run);
});
}
/**
* Invoke the getter of all signals associated with a component
* so they can be registered to the effect this function is called in.
* @param {import('./types.js').ComponentContext} context
*/
function observe_all(context) {
if (context.d) {
for (const signal of context.d) get(signal);
}
deep_read(context.s);
}
/**
* Under some circumstances, imports may be reactive in legacy mode. In that case,
* they should be using `reactive_import` as part of the transformation
* @param {() => any} fn
*/
export function reactive_import(fn) {
const s = source(0);
return function () {
if (arguments.length === 1) {
set(s, get(s) + 1);
return arguments[0];
} else {
get(s);
return fn();
}
};
}
/**
* @this {any}
* @param {Record<string, unknown>} $$props
* @param {Event} event
* @returns {void}
*/
export function bubble_event($$props, event) {
var events = /** @type {Record<string, Function[] | Function>} */ ($$props.$$events)?.[
event.type
];
var callbacks = is_array(events) ? events.slice() : events == null ? [] : [events];
for (var fn of callbacks) {
// Preserve "this" context
fn.call(this, event);
}
}

@ -1,9 +1,7 @@
import { DEV } from 'esm-env';
import { subscribe_to_store } from '../../store/utils.js';
import { noop, run, run_all } from '../common.js';
import { run_all } from '../common.js';
import {
array_prototype,
get_descriptor,
get_descriptors,
get_prototype_of,
is_array,
@ -11,41 +9,37 @@ import {
object_freeze,
object_prototype
} from './utils.js';
import { unstate } from './proxy.js';
import { pre_effect } from './reactivity/computations.js';
import {
PROPS_IS_LAZY_INITIAL,
PROPS_IS_IMMUTABLE,
PROPS_IS_RUNES,
PROPS_IS_UPDATED
} from '../../constants.js';
import { STATE_SYMBOL, unstate } from './proxy.js';
import { EACH_BLOCK, IF_BLOCK } from './block.js';
export const SOURCE = 1;
export const DERIVED = 1 << 1;
export const EFFECT = 1 << 2;
export const PRE_EFFECT = 1 << 3;
export const RENDER_EFFECT = 1 << 4;
const MANAGED = 1 << 6;
const UNOWNED = 1 << 7;
export const CLEAN = 1 << 8;
export const DIRTY = 1 << 9;
export const MAYBE_DIRTY = 1 << 10;
export const INERT = 1 << 11;
export const DESTROYED = 1 << 12;
EACH_BLOCK,
IF_BLOCK,
EFFECT,
PRE_EFFECT,
RENDER_EFFECT,
DIRTY,
UNINITIALIZED,
MAYBE_DIRTY,
CLEAN,
DERIVED,
UNOWNED,
DESTROYED,
INERT,
MANAGED,
SOURCE,
STATE_SYMBOL
} from './constants.js';
import { flush_tasks } from './dom/task.js';
const IS_EFFECT = EFFECT | PRE_EFFECT | RENDER_EFFECT;
const FLUSH_MICROTASK = 0;
const FLUSH_SYNC = 1;
export const UNINITIALIZED = Symbol();
// Used for controlling the flush of effects.
let current_scheduler_mode = FLUSH_MICROTASK;
// Used for handling scheduling
let is_micro_task_queued = false;
let is_task_queued = false;
let is_raf_queued = false;
let is_flushing_effect = false;
// Used for $inspect
export let is_batching_effect = false;
@ -58,15 +52,11 @@ let current_queued_pre_and_render_effects = [];
/** @type {import('./types.js').EffectSignal[]} */
let current_queued_effects = [];
/** @type {Array<() => void>} */
let current_queued_tasks = [];
/** @type {Array<() => void>} */
let current_raf_tasks = [];
let flush_count = 0;
// Handle signal reactivity tree dependencies and consumer
/** @type {null | import('./types.js').ComputationSignal} */
let current_consumer = null;
export let current_consumer = null;
/** @type {null | import('./types.js').EffectSignal} */
export let current_effect = null;
@ -86,16 +76,20 @@ let last_inspected_signal = null;
export let current_untracking = false;
/** Exists to opt out of the mutation validation for stores which may be set for the first time during a derivation */
let ignore_mutation_validation = false;
/** @param {boolean} value */
export function set_ignore_mutation_validation(value) {
ignore_mutation_validation = value;
}
// If we are working with a get() chain that has no active container,
// to prevent memory leaks, we skip adding the consumer.
let current_skip_consumer = false;
// Handle collecting all signals which are read during a specific time frame
let is_signals_recorded = false;
export let is_signals_recorded = false;
let captured_signals = new Set();
/** @type {Function | null} */
let inspect_fn = null;
export let inspect_fn = null;
/** @type {Array<import('./types.js').SignalDebug>} */
let inspect_captured_signals = [];
@ -144,133 +138,6 @@ export function batch_inspect(target, prop, receiver) {
};
}
/**
* @param {unknown} a
* @param {unknown} b
* @returns {boolean}
*/
export function default_equals(a, b) {
return a === b;
}
/**
* @template V
* @param {import('./types.js').SignalFlags} flags
* @param {V} value
* @returns {import('./types.js').SourceSignal<V> | import('./types.js').SourceSignal<V> & import('./types.js').SourceSignalDebug}
*/
function create_source_signal(flags, value) {
if (DEV) {
return {
// consumers
c: null,
// equals
e: default_equals,
// flags
f: flags,
// value
v: value,
// write version
w: 0,
// this is for DEV only
inspect: new Set()
};
}
return {
// consumers
c: null,
// equals
e: default_equals,
// flags
f: flags,
// value
v: value,
// write version
w: 0
};
}
/**
* @template V
* @param {import('./types.js').SignalFlags} flags
* @param {V} value
* @param {import('./types.js').Block | null} block
* @returns {import('./types.js').ComputationSignal<V> | import('./types.js').ComputationSignal<V> & import('./types.js').SourceSignalDebug}
*/
function create_computation_signal(flags, value, block) {
if (DEV) {
return {
// block
b: block,
// consumers
c: null,
// destroy
d: null,
// equals
e: null,
// flags
f: flags,
// init
i: null,
// level
l: 0,
// references
r: null,
// value
v: value,
// write version
w: 0,
// context: We can remove this if we get rid of beforeUpdate/afterUpdate
x: null,
// destroy
y: null,
// this is for DEV only
inspect: new Set()
};
}
return {
// block
b: block,
// consumers
c: null,
// destroy
d: null,
// equals
e: null,
// flags
f: flags,
// level
l: 0,
// init
i: null,
// references
r: null,
// value
v: value,
// write version
w: 0,
// context: We can remove this if we get rid of beforeUpdate/afterUpdate
x: null,
// destroy
y: null
};
}
/**
* @param {import('./types.js').ComputationSignal} target_signal
* @param {import('./types.js').ComputationSignal} ref_signal
* @returns {void}
*/
function push_reference(target_signal, ref_signal) {
const references = target_signal.r;
if (references === null) {
target_signal.r = [ref_signal];
} else {
references.push(ref_signal);
}
}
/**
* @template V
* @param {import('./types.js').Signal<V>} signal
@ -694,44 +561,6 @@ export function schedule_effect(signal, sync) {
}
}
function process_task() {
is_task_queued = false;
const tasks = current_queued_tasks.slice();
current_queued_tasks = [];
run_all(tasks);
}
function process_raf_task() {
is_raf_queued = false;
const tasks = current_raf_tasks.slice();
current_raf_tasks = [];
run_all(tasks);
}
/**
* @param {() => void} fn
* @returns {void}
*/
export function schedule_task(fn) {
if (!is_task_queued) {
is_task_queued = true;
setTimeout(process_task, 0);
}
current_queued_tasks.push(fn);
}
/**
* @param {() => void} fn
* @returns {void}
*/
export function schedule_raf_task(fn) {
if (!is_raf_queued) {
is_raf_queued = true;
requestAnimationFrame(process_raf_task);
}
current_raf_tasks.push(fn);
}
/**
* @returns {void}
*/
@ -807,12 +636,7 @@ export function flush_sync(fn, flush_previous = true) {
if (current_queued_pre_and_render_effects.length > 0 || effects.length > 0) {
flushSync();
}
if (is_raf_queued) {
process_raf_task();
}
if (is_task_queued) {
process_task();
}
flush_tasks();
flush_count = 0;
} finally {
current_scheduler_mode = previous_scheduler_mode;
@ -863,96 +687,6 @@ function update_derived(signal, force_schedule) {
}
}
/**
* Gets the current value of a store. If the store isn't subscribed to yet, it will create a proxy
* signal that will be updated when the store is. The store references container is needed to
* track reassignments to stores and to track the correct component context.
* @template V
* @param {import('./types.js').Store<V> | null | undefined} store
* @param {string} store_name
* @param {import('./types.js').StoreReferencesContainer} stores
* @returns {V}
*/
export function store_get(store, store_name, stores) {
/** @type {import('./types.js').StoreReferencesContainer[''] | undefined} */
let entry = stores[store_name];
const is_new = entry === undefined;
if (is_new) {
entry = {
store: null,
last_value: null,
value: mutable_source(UNINITIALIZED),
unsubscribe: noop
};
// TODO: can we remove this code? it was refactored out when we split up source/comptued signals
// push_destroy_fn(entry.value, () => {
// /** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).last_value =
// /** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).value.value;
// });
stores[store_name] = entry;
}
if (is_new || entry.store !== store) {
entry.unsubscribe();
entry.store = store ?? null;
entry.unsubscribe = connect_store_to_signal(store, entry.value);
}
const value = get(entry.value);
// This could happen if the store was cleaned up because the component was destroyed and there's a leak on the user side.
// In that case we don't want to fail with a cryptic Symbol error, but rather return the last value we got.
return value === UNINITIALIZED ? entry.last_value : value;
}
/**
* @template V
* @param {import('./types.js').Store<V> | null | undefined} store
* @param {import('./types.js').SourceSignal<V>} source
*/
function connect_store_to_signal(store, source) {
if (store == null) {
set(source, undefined);
return noop;
}
/** @param {V} v */
const run = (v) => {
ignore_mutation_validation = true;
set(source, v);
ignore_mutation_validation = false;
};
return subscribe_to_store(store, run);
}
/**
* Sets the new value of a store and returns that value.
* @template V
* @param {import('./types.js').Store<V>} store
* @param {V} value
* @returns {V}
*/
export function store_set(store, value) {
store.set(value);
return value;
}
/**
* Unsubscribes from all auto-subscribed stores on destroy
* @param {import('./types.js').StoreReferencesContainer} stores
*/
export function unsubscribe_on_destroy(stores) {
on_destroy(() => {
let store_name;
for (store_name in stores) {
const ref = stores[store_name];
ref.unsubscribe();
// TODO: can we remove this code? it was refactored out when we split up source/comptued signals
// destroy_signal(ref.value);
}
});
}
/**
* @template V
* @param {import('./types.js').Signal<V>} signal
@ -1084,18 +818,6 @@ export function mutate(source, value) {
return value;
}
/**
* Updates a store with a new value.
* @param {import('./types.js').Store<V>} store the store to update
* @param {any} expression the expression that mutates the store
* @param {V} new_value the new store value
* @template V
*/
export function mutate_store(store, expression, new_value) {
store.set(new_value);
return expression;
}
/**
* @param {import('./types.js').ComputationSignal} signal
* @param {boolean} inert
@ -1295,67 +1017,6 @@ export function destroy_signal(signal) {
}
}
/**
* @template V
* @param {() => V} init
* @returns {import('./types.js').ComputationSignal<V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function derived(init) {
const is_unowned = current_effect === null;
const flags = is_unowned ? DERIVED | UNOWNED : DERIVED;
const signal = /** @type {import('./types.js').ComputationSignal<V>} */ (
create_computation_signal(flags | CLEAN, UNINITIALIZED, current_block)
);
signal.i = init;
signal.e = default_equals;
if (current_consumer !== null) {
push_reference(current_consumer, signal);
}
return signal;
}
/**
* @template V
* @param {() => V} init
* @returns {import('./types.js').ComputationSignal<V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function derived_safe_equal(init) {
const signal = derived(init);
signal.e = safe_equal;
return signal;
}
/**
* @template V
* @param {V} initial_value
* @returns {import('./types.js').SourceSignal<V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function source(initial_value) {
return create_source_signal(SOURCE | CLEAN, initial_value);
}
/**
* @template V
* @param {V} initial_value
* @returns {import('./types.js').SourceSignal<V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function mutable_source(initial_value) {
const s = source(initial_value);
s.e = safe_equal;
// bind the signal to the component context, in case we need to
// track updates to trigger beforeUpdate/afterUpdate callbacks
if (current_component_context) {
(current_component_context.d ??= []).push(s);
}
return s;
}
/**
* Use `untrack` to prevent something from being treated as an `$effect`/`$derived` dependency.
*
@ -1374,170 +1035,6 @@ export function untrack(fn) {
}
}
/**
* @param {import('./types.js').EffectType} type
* @param {(() => void | (() => void)) | ((b: import('./types.js').Block) => void | (() => void))} init
* @param {boolean} sync
* @param {null | import('./types.js').Block} block
* @param {boolean} schedule
* @returns {import('./types.js').EffectSignal}
*/
function internal_create_effect(type, init, sync, block, schedule) {
const signal = create_computation_signal(type | DIRTY, null, block);
signal.i = init;
signal.x = current_component_context;
if (current_effect !== null) {
signal.l = current_effect.l + 1;
if ((type & MANAGED) === 0) {
push_reference(current_effect, signal);
}
}
if (schedule) {
schedule_effect(signal, sync);
}
return signal;
}
/**
* @returns {boolean}
*/
export function effect_active() {
return current_effect ? (current_effect.f & MANAGED) === 0 : false;
}
/**
* @param {() => void | (() => void)} init
* @returns {import('./types.js').EffectSignal}
*/
export function user_effect(init) {
if (current_effect === null) {
throw new Error(
'ERR_SVELTE_ORPHAN_EFFECT' +
(DEV ? ': The Svelte $effect rune can only be used during component initialisation.' : '')
);
}
const apply_component_effect_heuristics =
current_effect.f & RENDER_EFFECT &&
current_component_context !== null &&
!current_component_context.m;
const effect = internal_create_effect(
EFFECT,
init,
false,
current_block,
!apply_component_effect_heuristics
);
if (apply_component_effect_heuristics) {
const context = /** @type {import('./types.js').ComponentContext} */ (
current_component_context
);
(context.e ??= []).push(effect);
}
return effect;
}
/**
* @param {() => void | (() => void)} init
* @returns {() => void}
*/
export function user_root_effect(init) {
const effect = managed_render_effect(init);
return () => {
destroy_signal(effect);
};
}
/**
* @param {() => void | (() => void)} init
* @returns {import('./types.js').EffectSignal}
*/
export function effect(init) {
return internal_create_effect(EFFECT, init, false, current_block, true);
}
/**
* @param {() => void | (() => void)} init
* @returns {import('./types.js').EffectSignal}
*/
export function managed_effect(init) {
return internal_create_effect(EFFECT | MANAGED, init, false, current_block, true);
}
/**
* @param {() => void | (() => void)} init
* @param {boolean} sync
* @returns {import('./types.js').EffectSignal}
*/
export function managed_pre_effect(init, sync) {
return internal_create_effect(PRE_EFFECT | MANAGED, init, sync, current_block, true);
}
/**
* @param {() => void | (() => void)} init
* @returns {import('./types.js').EffectSignal}
*/
export function pre_effect(init) {
if (current_effect === null) {
throw new Error(
'ERR_SVELTE_ORPHAN_EFFECT' +
(DEV
? ': The Svelte $effect.pre rune can only be used during component initialisation.'
: '')
);
}
const sync = current_effect !== null && (current_effect.f & RENDER_EFFECT) !== 0;
return internal_create_effect(
PRE_EFFECT,
() => {
const val = init();
flush_local_render_effects();
return val;
},
sync,
current_block,
true
);
}
/**
* This effect is used to ensure binding are kept in sync. We use a pre effect to ensure we run before the
* bindings which are in later effects. However, we don't use a pre_effect directly as we don't want to flush anything.
*
* @param {() => void | (() => void)} init
* @returns {import('./types.js').EffectSignal}
*/
export function invalidate_effect(init) {
return internal_create_effect(PRE_EFFECT, init, true, current_block, true);
}
/**
* @template {import('./types.js').Block} B
* @param {(block: B) => void | (() => void)} init
* @param {any} block
* @param {any} managed
* @param {any} sync
* @returns {import('./types.js').EffectSignal}
*/
export function render_effect(init, block = current_block, managed = false, sync = true) {
let flags = RENDER_EFFECT;
if (managed) {
flags |= MANAGED;
}
return internal_create_effect(flags, /** @type {any} */ (init), sync, block, true);
}
/**
* @template {import('./types.js').Block} B
* @param {(block: B) => void | (() => void)} init
* @param {any} block
* @param {any} sync
* @returns {import('./types.js').EffectSignal}
*/
export function managed_render_effect(init, block = current_block, sync = true) {
const flags = RENDER_EFFECT | MANAGED;
return internal_create_effect(flags, /** @type {any} */ (init), sync, block, true);
}
/**
* @template V
* @param {import('./types.js').ComputationSignal<V>} signal
@ -1562,7 +1059,7 @@ const STATUS_MASK = ~(DIRTY | MAYBE_DIRTY | CLEAN);
* @param {number} status
* @returns {void}
*/
export function set_signal_status(signal, status) {
function set_signal_status(signal, status) {
signal.f = (signal.f & STATUS_MASK) | status;
}
@ -1579,154 +1076,6 @@ export function is_signal(val) {
);
}
/**
* @template V
* @param {unknown} val
* @returns {val is import('./types.js').Store<V>}
*/
export function is_store(val) {
return (
typeof val === 'object' &&
val !== null &&
typeof (/** @type {import('./types.js').Store<V>} */ (val).subscribe) === 'function'
);
}
/**
* This function is responsible for synchronizing a possibly bound prop with the inner component state.
* It is used whenever the compiler sees that the component writes to the prop, or when it has a default prop_value.
* @template V
* @param {Record<string, unknown>} props
* @param {string} key
* @param {number} flags
* @param {V | (() => V)} [initial]
* @returns {(() => V | ((arg: V) => V) | ((arg: V, mutation: boolean) => V))}
*/
export function prop(props, key, flags, initial) {
var immutable = (flags & PROPS_IS_IMMUTABLE) !== 0;
var runes = (flags & PROPS_IS_RUNES) !== 0;
var prop_value = /** @type {V} */ (props[key]);
var setter = get_descriptor(props, key)?.set;
if (prop_value === undefined && initial !== undefined) {
if (setter && runes) {
// TODO consolidate all these random runtime errors
throw new Error(
'ERR_SVELTE_BINDING_FALLBACK' +
(DEV
? `: Cannot pass undefined to bind:${key} because the property contains a fallback value. Pass a different value than undefined to ${key}.`
: '')
);
}
// @ts-expect-error would need a cumbersome method overload to type this
if ((flags & PROPS_IS_LAZY_INITIAL) !== 0) initial = initial();
prop_value = /** @type {V} */ (initial);
if (setter) setter(prop_value);
}
var getter = () => {
var value = /** @type {V} */ (props[key]);
if (value !== undefined) initial = undefined;
return value === undefined ? /** @type {V} */ (initial) : value;
};
// easy mode — prop is never written to
if ((flags & PROPS_IS_UPDATED) === 0) {
return getter;
}
// intermediate mode — prop is written to, but the parent component had
// `bind:foo` which means we can just call `$$props.foo = value` directly
if (setter) {
return function (/** @type {V} */ value) {
if (arguments.length === 1) {
/** @type {Function} */ (setter)(value);
return value;
} else {
return getter();
}
};
}
// hard mode. this is where it gets ugly — the value in the child should
// synchronize with the parent, but it should also be possible to temporarily
// set the value to something else locally.
var from_child = false;
var was_from_child = false;
// The derived returns the current value. The underlying mutable
// source is written to from various places to persist this value.
var inner_current_value = mutable_source(prop_value);
var current_value = derived(() => {
var parent_value = getter();
var child_value = get(inner_current_value);
if (from_child) {
from_child = false;
was_from_child = true;
return child_value;
}
was_from_child = false;
return (inner_current_value.v = parent_value);
});
if (!immutable) current_value.e = safe_equal;
return function (/** @type {V} */ value, mutation = false) {
var current = get(current_value);
// legacy nonsense — need to ensure the source is invalidated when necessary
// also needed for when handling inspect logic so we can inspect the correct source signal
if (is_signals_recorded || (DEV && inspect_fn)) {
// set this so that we don't reset to the parent value if `d`
// is invalidated because of `invalidate_inner_signals` (rather
// than because the parent or child value changed)
from_child = was_from_child;
// invoke getters so that signals are picked up by `invalidate_inner_signals`
getter();
get(inner_current_value);
}
if (arguments.length > 0) {
if (mutation || (immutable ? value !== current : safe_not_equal(value, current))) {
from_child = true;
set(inner_current_value, mutation ? current : value);
get(current_value); // force a synchronisation immediately
}
return value;
}
return current;
};
}
/**
* @param {unknown} a
* @param {unknown} b
* @returns {boolean}
*/
export function safe_not_equal(a, b) {
// eslint-disable-next-line eqeqeq
return a != a
? // eslint-disable-next-line eqeqeq
b == b
: a !== b || (a !== null && typeof a === 'object') || typeof a === 'function';
}
/**
* @param {unknown} a
* @param {unknown} b
* @returns {boolean}
*/
export function safe_equal(a, b) {
return !safe_not_equal(a, b);
}
/** @returns {Map<unknown, unknown>} */
export function get_or_init_context_map() {
const component_context = current_component_context;
@ -1755,23 +1104,6 @@ function get_parent_context(component_context) {
return null;
}
/**
* @this {any}
* @param {Record<string, unknown>} $$props
* @param {Event} event
* @returns {void}
*/
export function bubble_event($$props, event) {
var events = /** @type {Record<string, Function[] | Function>} */ ($$props.$$events)?.[
event.type
];
var callbacks = is_array(events) ? events.slice() : events == null ? [] : [events];
for (var fn of callbacks) {
// Preserve "this" context
fn.call(this, event);
}
}
/**
* @param {import('./types.js').Signal<number>} signal
* @param {1 | -1} [d]
@ -1794,17 +1126,6 @@ export function update_prop(fn, d = 1) {
return value;
}
/**
* @param {import('./types.js').Store<number>} store
* @param {number} store_value
* @param {1 | -1} [d]
* @returns {number}
*/
export function update_store(store, store_value, d = 1) {
store.set(store_value + d);
return store_value;
}
/**
* @param {import('./types.js').Signal<number>} signal
* @param {1 | -1} [d]
@ -1827,36 +1148,6 @@ export function update_pre_prop(fn, d = 1) {
return value;
}
/**
* @param {import('./types.js').Store<number>} store
* @param {number} store_value
* @param {1 | -1} [d]
* @returns {number}
*/
export function update_pre_store(store, store_value, d = 1) {
const value = store_value + d;
store.set(value);
return value;
}
/**
* Under some circumstances, imports may be reactive in legacy mode. In that case,
* they should be using `reactive_import` as part of the transformation
* @param {() => any} fn
*/
export function reactive_import(fn) {
const s = source(0);
return function () {
if (arguments.length === 1) {
set(s, get(s) + 1);
return arguments[0];
} else {
get(s);
return fn();
}
};
}
/**
* @param {Record<string, unknown>} obj
* @param {string[]} keys
@ -1881,15 +1172,6 @@ export function value_or_fallback(value, fallback) {
return value === undefined ? fallback : value;
}
/**
* Schedules a callback to run immediately before the component is unmounted.
* @param {() => any} fn
* @returns {void}
*/
function on_destroy(fn) {
user_effect(() => () => untrack(fn));
}
/**
* @param {Record<string, unknown>} props
* @param {any} runes
@ -1951,53 +1233,6 @@ export function pop(component) {
return component || /** @type {T} */ ({});
}
/**
* Invoke the getter of all signals associated with a component
* so they can be registered to the effect this function is called in.
* @param {import('./types.js').ComponentContext} context
*/
function observe_all(context) {
if (context.d) {
for (const signal of context.d) get(signal);
}
deep_read(context.s);
}
/**
* Legacy-mode only: Call `onMount` callbacks and set up `beforeUpdate`/`afterUpdate` effects
*/
export function init() {
const context = /** @type {import('./types.js').ComponentContext} */ (current_component_context);
const callbacks = context.u;
if (!callbacks) return;
// beforeUpdate
pre_effect(() => {
observe_all(context);
callbacks.b.forEach(run);
});
// onMount (must run before afterUpdate)
user_effect(() => {
const fns = untrack(() => callbacks.m.map(run));
return () => {
for (const fn of fns) {
if (typeof fn === 'function') {
fn();
}
}
};
});
// afterUpdate
user_effect(() => {
observe_all(context);
callbacks.a.forEach(run);
});
}
/**
* Deeply traverse an object and read all its properties
* so that they're all reactive in case this is `$state`

@ -8,19 +8,17 @@ import {
IF_BLOCK,
KEY_BLOCK,
ROOT_BLOCK
} from './block.js';
} from './constants.js';
import { destroy_each_item_block, get_first_element } from './dom/blocks/each.js';
import { schedule_raf_task } from './dom/task.js';
import { append_child, empty } from './operations.js';
import { effect, managed_effect, managed_pre_effect } from './reactivity/computations.js';
import {
current_block,
current_effect,
destroy_signal,
effect,
execute_effect,
managed_effect,
managed_pre_effect,
mark_subtree_inert,
schedule_raf_task,
untrack
} from './runtime.js';
import { raf } from './timing.js';

@ -1,4 +1,9 @@
import {
DERIVED,
EFFECT,
RENDER_EFFECT,
SOURCE,
PRE_EFFECT,
ROOT_BLOCK,
EACH_BLOCK,
EACH_ITEM_BLOCK,
@ -8,10 +13,9 @@ import {
HEAD_BLOCK,
DYNAMIC_COMPONENT_BLOCK,
DYNAMIC_ELEMENT_BLOCK,
SNIPPET_BLOCK
} from './block.js';
import type { STATE_SYMBOL } from './proxy.js';
import { DERIVED, EFFECT, RENDER_EFFECT, SOURCE, PRE_EFFECT } from './runtime.js';
SNIPPET_BLOCK,
STATE_SYMBOL
} from './constants.js';
// Put all internal types in this file. Once we convert to JSDoc, we can make this a d.ts file

@ -1,5 +1,4 @@
import { EACH_INDEX_REACTIVE } from '../../constants.js';
import { source, untrack } from './runtime.js';
import { untrack } from './runtime.js';
import { is_array } from './utils.js';
/** regex of all html void element names */

@ -1,44 +1,23 @@
export {
store_get,
get,
set,
set_sync,
invalidate_inner_signals,
source,
mutable_source,
derived,
derived_safe_equal,
prop,
user_effect,
render_effect,
pre_effect,
invalidate_effect,
flushSync,
bubble_event,
safe_equal,
tick,
untrack,
update,
update_prop,
update_store,
update_pre,
update_pre_prop,
update_pre_store,
mutate,
mutate_store,
value_or_fallback,
exclude_from_object,
store_set,
unsubscribe_on_destroy,
pop,
push,
reactive_import,
effect_active,
user_root_effect,
inspect,
unwrap,
freeze,
init,
deep_read
} from './client/runtime.js';
export * from './client/dev/ownership.js';
@ -46,6 +25,10 @@ export { await_block as await } from './client/dom/blocks/await.js';
export { if_block as if } from './client/dom/blocks/if.js';
export { key_block as key } from './client/dom/blocks/key.js';
export * from './client/dom/blocks/each.js';
export * from './client/reactivity/computations.js';
export * from './client/reactivity/sources.js';
export * from './client/reactivity/equality.js';
export * from './client/reactivity/store.js';
export * from './client/render.js';
export * from './client/validate.js';
export { raf } from './client/timing.js';

@ -1,10 +1,10 @@
import {
current_component_context,
get_or_init_context_map,
untrack,
user_effect
untrack
} from '../internal/client/runtime.js';
import { is_array } from '../internal/client/utils.js';
import { user_effect } from '../internal/index.js';
/**
* The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM.

@ -1,5 +1,12 @@
import { describe, assert, it } from 'vitest';
import * as $ from '../../src/internal/client/runtime';
import {
derived,
effect,
render_effect,
user_effect
} from '../../src/internal/client/reactivity/computations';
import { source } from '../../src/internal/client/reactivity/sources';
import type { ComputationSignal } from '../../src/internal/client/types';
/**
@ -13,7 +20,7 @@ function run_test(runes: boolean, fn: (runes: boolean) => () => void) {
$.push({}, runes);
// Create a render context so that effect validations etc don't fail
let execute: any;
const signal = $.render_effect(
const signal = render_effect(
() => {
execute = fn(runes);
},
@ -36,9 +43,9 @@ describe('signals', () => {
test('effect with state and derived in it', () => {
const log: string[] = [];
let count = $.source(0);
let double = $.derived(() => $.get(count) * 2);
$.effect(() => {
let count = source(0);
let double = derived(() => $.get(count) * 2);
effect(() => {
log.push(`${$.get(count)}:${$.get(double)}`);
});
@ -53,13 +60,13 @@ describe('signals', () => {
test('multiple effects with state and derived in it#1', () => {
const log: string[] = [];
let count = $.source(0);
let double = $.derived(() => $.get(count) * 2);
let count = source(0);
let double = derived(() => $.get(count) * 2);
$.effect(() => {
effect(() => {
log.push(`A:${$.get(count)}:${$.get(double)}`);
});
$.effect(() => {
effect(() => {
log.push(`B:${$.get(double)}`);
});
@ -74,13 +81,13 @@ describe('signals', () => {
test('multiple effects with state and derived in it#2', () => {
const log: string[] = [];
let count = $.source(0);
let double = $.derived(() => $.get(count) * 2);
let count = source(0);
let double = derived(() => $.get(count) * 2);
$.effect(() => {
effect(() => {
log.push(`A:${$.get(double)}`);
});
$.effect(() => {
effect(() => {
log.push(`B:${$.get(count)}:${$.get(double)}`);
});
@ -95,10 +102,10 @@ describe('signals', () => {
test('derived from state', () => {
const log: number[] = [];
let count = $.source(0);
let double = $.derived(() => $.get(count) * 2);
let count = source(0);
let double = derived(() => $.get(count) * 2);
$.effect(() => {
effect(() => {
log.push($.get(double));
});
@ -113,11 +120,11 @@ describe('signals', () => {
test('derived from derived', () => {
const log: number[] = [];
let count = $.source(0);
let double = $.derived(() => $.get(count) * 2);
let quadruple = $.derived(() => $.get(double) * 2);
let count = source(0);
let double = derived(() => $.get(count) * 2);
let quadruple = derived(() => $.get(double) * 2);
$.effect(() => {
effect(() => {
log.push($.get(quadruple));
});
@ -136,20 +143,20 @@ describe('signals', () => {
const fib = (n: number): number => (n < 2 ? 1 : fib(n - 1) + fib(n - 2));
const hard = (n: number, l: string) => n + fib(16);
const A = $.source(0);
const B = $.source(0);
const C = $.derived(() => ($.get(A) % 2) + ($.get(B) % 2));
const D = $.derived(() => numbers.map((i) => i + ($.get(A) % 2) - ($.get(B) % 2)));
const E = $.derived(() => hard($.get(C) + $.get(A) + $.get(D)[0]!, 'E'));
const F = $.derived(() => hard($.get(D)[0]! && $.get(B), 'F'));
const G = $.derived(() => $.get(C) + ($.get(C) || $.get(E) % 2) + $.get(D)[0]! + $.get(F));
$.effect(() => {
const A = source(0);
const B = source(0);
const C = derived(() => ($.get(A) % 2) + ($.get(B) % 2));
const D = derived(() => numbers.map((i) => i + ($.get(A) % 2) - ($.get(B) % 2)));
const E = derived(() => hard($.get(C) + $.get(A) + $.get(D)[0]!, 'E'));
const F = derived(() => hard($.get(D)[0]! && $.get(B), 'F'));
const G = derived(() => $.get(C) + ($.get(C) || $.get(E) % 2) + $.get(D)[0]! + $.get(F));
effect(() => {
res.push(hard($.get(G), 'H'));
});
$.effect(() => {
effect(() => {
res.push($.get(G));
});
$.effect(() => {
effect(() => {
res.push(hard($.get(F), 'J'));
});
@ -176,13 +183,13 @@ describe('signals', () => {
test('effects correctly handle unowned derived values that do not change', () => {
const log: number[] = [];
let count = $.source(0);
let count = source(0);
const read = () => {
const x = $.derived(() => ({ count: $.get(count) }));
const x = derived(() => ({ count: $.get(count) }));
return $.get(x);
};
const derivedCount = $.derived(() => read().count);
$.user_effect(() => {
const derivedCount = derived(() => read().count);
user_effect(() => {
log.push($.get(derivedCount));
});
@ -204,11 +211,11 @@ describe('signals', () => {
return () => {
const nested: ComputationSignal<string>[] = [];
const a = $.source(0);
const b = $.source(0);
const c = $.derived(() => {
const a_2 = $.derived(() => $.get(a) + '!');
const b_2 = $.derived(() => $.get(b) + '?');
const a = source(0);
const b = source(0);
const c = derived(() => {
const a_2 = derived(() => $.get(a) + '!');
const b_2 = derived(() => $.get(b) + '?');
nested.push(a_2, b_2);
return { a: $.get(a_2), b: $.get(b_2) };
@ -233,8 +240,8 @@ describe('signals', () => {
});
// outside of test function so that they are unowned signals
let count = $.source(0);
let calc = $.derived(() => {
let count = source(0);
let calc = derived(() => {
if ($.get(count) >= 2) {
return 'limit';
}
@ -244,7 +251,7 @@ describe('signals', () => {
test('effect with derived using unowned derived every time', () => {
const log: Array<number | string> = [];
const effect = $.user_effect(() => {
const effect = user_effect(() => {
log.push($.get(calc));
});
@ -263,18 +270,18 @@ describe('signals', () => {
};
});
let no_deps = $.derived(() => {
let no_deps = derived(() => {
return [];
});
test('two effects with an unowned derived that has no depedencies', () => {
const log: Array<Array<any>> = [];
$.render_effect(() => {
render_effect(() => {
log.push($.get(no_deps));
});
$.render_effect(() => {
render_effect(() => {
log.push($.get(no_deps));
});
@ -284,19 +291,19 @@ describe('signals', () => {
};
});
let some_state = $.source({});
let some_deps = $.derived(() => {
let some_state = source({});
let some_deps = derived(() => {
return [$.get(some_state)];
});
test('two effects with an unowned derived that has some depedencies', () => {
const log: Array<Array<any>> = [];
$.render_effect(() => {
render_effect(() => {
log.push($.get(some_deps));
});
$.render_effect(() => {
render_effect(() => {
log.push($.get(some_deps));
});
@ -309,8 +316,8 @@ describe('signals', () => {
test('schedules rerun when writing to signal before reading it', (runes) => {
if (!runes) return () => {};
const value = $.source({ count: 0 });
$.user_effect(() => {
const value = source({ count: 0 });
user_effect(() => {
$.set(value, { count: 0 });
$.get(value);
});

Loading…
Cancel
Save