Comparing version 5.0.0-next.172 to 5.0.0-next.173
@@ -5,3 +5,3 @@ { | ||
"license": "MIT", | ||
"version": "5.0.0-next.172", | ||
"version": "5.0.0-next.173", | ||
"type": "module", | ||
@@ -8,0 +8,0 @@ "types": "./types/index.d.ts", |
@@ -157,15 +157,8 @@ /** @import * as ESTree from 'estree' */ | ||
if (store_setup.length === 0) { | ||
store_setup.push( | ||
b.const('$$subscriptions', b.object([])), | ||
b.stmt(b.call('$.unsubscribe_on_destroy', b.id('$$subscriptions'))) | ||
); | ||
store_setup.push(b.const('$$stores', b.call('$.setup_stores'))); | ||
} | ||
// We're creating an arrow function that gets the store value which minifies better for two or more references | ||
const store_reference = serialize_get_binding(b.id(name.slice(1)), instance_state); | ||
const store_get = b.call( | ||
'$.store_get', | ||
store_reference, | ||
b.literal(name), | ||
b.id('$$subscriptions') | ||
); | ||
const store_get = b.call('$.store_get', store_reference, b.literal(name), b.id('$$stores')); | ||
store_setup.push( | ||
@@ -172,0 +165,0 @@ b.const( |
@@ -348,3 +348,3 @@ import * as b from '../../../utils/builders.js'; | ||
if (state.scope.get(`$${left.name}`)?.kind === 'store_sub') { | ||
return b.call('$.store_unsub', call, b.literal(`$${left.name}`), b.id('$$subscriptions')); | ||
return b.call('$.store_unsub', call, b.literal(`$${left.name}`), b.id('$$stores')); | ||
} else { | ||
@@ -351,0 +351,0 @@ return call; |
@@ -114,6 +114,6 @@ export { add_locations } from './dev/elements.js'; | ||
mutate_store, | ||
setup_stores, | ||
store_get, | ||
store_set, | ||
store_unsub, | ||
unsubscribe_on_destroy, | ||
update_pre_store, | ||
@@ -120,0 +120,0 @@ update_store |
@@ -7,3 +7,2 @@ import { CLEAN, DERIVED, DESTROYED, DIRTY, MAYBE_DIRTY, UNOWNED } from '../constants.js'; | ||
set_signal_status, | ||
mark_reactions, | ||
current_skip_reaction, | ||
@@ -43,3 +42,3 @@ update_reaction, | ||
if (current_reaction !== null && (current_reaction.f & DERIVED) !== 0) { | ||
var current_derived = /** @type {import('#client').Derived<V>} */ (current_reaction); | ||
var current_derived = /** @type {import('#client').Derived} */ (current_reaction); | ||
if (current_derived.deriveds === null) { | ||
@@ -105,3 +104,2 @@ current_derived.deriveds = [signal]; | ||
derived.version = increment_version(); | ||
mark_reactions(derived, DIRTY, false); | ||
} | ||
@@ -108,0 +106,0 @@ } |
@@ -97,3 +97,4 @@ import { | ||
teardown: null, | ||
transitions: null | ||
transitions: null, | ||
version: 0 | ||
}; | ||
@@ -100,0 +101,0 @@ |
@@ -5,3 +5,3 @@ import { DEV } from 'esm-env'; | ||
current_reaction, | ||
current_dependencies, | ||
new_deps, | ||
current_effect, | ||
@@ -11,3 +11,2 @@ current_untracked_writes, | ||
is_runes, | ||
mark_reactions, | ||
schedule_effect, | ||
@@ -18,10 +17,19 @@ set_current_untracked_writes, | ||
increment_version, | ||
update_effect, | ||
inspect_effects | ||
update_effect | ||
} from '../runtime.js'; | ||
import { equals, safe_equals } from './equality.js'; | ||
import { CLEAN, DERIVED, DIRTY, BRANCH_EFFECT } from '../constants.js'; | ||
import { | ||
CLEAN, | ||
DERIVED, | ||
DIRTY, | ||
BRANCH_EFFECT, | ||
INSPECT_EFFECT, | ||
UNOWNED, | ||
MAYBE_DIRTY | ||
} from '../constants.js'; | ||
import { UNINITIALIZED } from '../../../constants.js'; | ||
import * as e from '../errors.js'; | ||
let inspect_effects = new Set(); | ||
/** | ||
@@ -97,3 +105,3 @@ * @template V | ||
mark_reactions(source, DIRTY, true); | ||
mark_reactions(source, DIRTY); | ||
@@ -116,3 +124,3 @@ // If the current signal is running for the first time, it won't have any | ||
) { | ||
if (current_dependencies !== null && current_dependencies.includes(source)) { | ||
if (new_deps !== null && new_deps.includes(source)) { | ||
set_signal_status(current_effect, DIRTY); | ||
@@ -140,1 +148,42 @@ schedule_effect(current_effect); | ||
} | ||
/** | ||
* @param {import('#client').Value} signal | ||
* @param {number} status should be DIRTY or MAYBE_DIRTY | ||
* @returns {void} | ||
*/ | ||
function mark_reactions(signal, status) { | ||
var reactions = signal.reactions; | ||
if (reactions === null) return; | ||
var runes = is_runes(); | ||
var length = reactions.length; | ||
for (var i = 0; i < length; i++) { | ||
var reaction = reactions[i]; | ||
var flags = reaction.f; | ||
// Skip any effects that are already dirty | ||
if ((flags & DIRTY) !== 0) continue; | ||
// In legacy mode, skip the current effect to prevent infinite loops | ||
if (!runes && reaction === current_effect) continue; | ||
// Inspect effects need to run immediately, so that the stack trace makes sense | ||
if (DEV && (flags & INSPECT_EFFECT) !== 0) { | ||
inspect_effects.add(reaction); | ||
continue; | ||
} | ||
set_signal_status(reaction, status); | ||
// If the signal a) was previously clean or b) is an unowned derived, then mark it | ||
if ((flags & (CLEAN | UNOWNED)) !== 0) { | ||
if ((flags & DERIVED) !== 0) { | ||
mark_reactions(/** @type {import('#client').Derived} */ (reaction), MAYBE_DIRTY); | ||
} else { | ||
schedule_effect(/** @type {import('#client').Effect} */ (reaction)); | ||
} | ||
} | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
/** @import { StoreReferencesContainer, Source } from '#client' */ | ||
/** @import { StoreReferencesContainer } from '#client' */ | ||
/** @import { Store } from '#shared' */ | ||
@@ -6,4 +6,4 @@ import { subscribe_to_store } from '../../../store/utils.js'; | ||
import { UNINITIALIZED } from '../../../constants.js'; | ||
import { get, untrack } from '../runtime.js'; | ||
import { effect } from './effects.js'; | ||
import { get } from '../runtime.js'; | ||
import { teardown } from './effects.js'; | ||
import { mutable_source, set } from './sources.js'; | ||
@@ -22,26 +22,21 @@ | ||
export function store_get(store, store_name, stores) { | ||
/** @type {StoreReferencesContainer[''] | undefined} */ | ||
let entry = stores[store_name]; | ||
const is_new = entry === undefined; | ||
const entry = (stores[store_name] ??= { | ||
store: null, | ||
source: mutable_source(UNINITIALIZED), | ||
unsubscribe: noop | ||
}); | ||
if (is_new) { | ||
entry = { | ||
store: null, | ||
last_value: null, | ||
value: mutable_source(UNINITIALIZED), | ||
unsubscribe: noop | ||
}; | ||
stores[store_name] = entry; | ||
} | ||
if (is_new || entry.store !== store) { | ||
if (entry.store !== store) { | ||
entry.unsubscribe(); | ||
entry.store = store ?? null; | ||
entry.unsubscribe = connect_store_to_signal(store, entry.value); | ||
if (store == null) { | ||
set(entry.source, undefined); | ||
entry.unsubscribe = noop; | ||
} else { | ||
entry.unsubscribe = subscribe_to_store(store, (v) => set(entry.source, v)); | ||
} | ||
} | ||
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; | ||
return get(entry.source); | ||
} | ||
@@ -71,16 +66,2 @@ | ||
/** | ||
* @template V | ||
* @param {Store<V> | null | undefined} store | ||
* @param {Source<V>} source | ||
*/ | ||
function connect_store_to_signal(store, source) { | ||
if (store == null) { | ||
set(source, undefined); | ||
return noop; | ||
} | ||
return subscribe_to_store(store, (v) => set(source, v)); | ||
} | ||
/** | ||
* Sets the new value of a store and returns that value. | ||
@@ -102,5 +83,5 @@ * @template V | ||
export function invalidate_store(stores, store_name) { | ||
const store = stores[store_name]; | ||
if (store.store) { | ||
store_set(store.store, store.value.v); | ||
var entry = stores[store_name]; | ||
if (entry.store !== null) { | ||
store_set(entry.store, entry.source.v); | ||
} | ||
@@ -111,8 +92,10 @@ } | ||
* Unsubscribes from all auto-subscribed stores on destroy | ||
* @param {StoreReferencesContainer} stores | ||
* @returns {StoreReferencesContainer} | ||
*/ | ||
export function unsubscribe_on_destroy(stores) { | ||
on_destroy(() => { | ||
let store_name; | ||
for (store_name in stores) { | ||
export function setup_stores() { | ||
/** @type {StoreReferencesContainer} */ | ||
const stores = {}; | ||
teardown(() => { | ||
for (var store_name in stores) { | ||
const ref = stores[store_name]; | ||
@@ -122,2 +105,4 @@ ref.unsubscribe(); | ||
}); | ||
return stores; | ||
} | ||
@@ -159,10 +144,1 @@ | ||
} | ||
/** | ||
* Schedules a callback to run immediately before the component is unmounted. | ||
* @param {() => any} fn | ||
* @returns {void} | ||
*/ | ||
function on_destroy(fn) { | ||
effect(() => () => untrack(fn)); | ||
} |
@@ -6,2 +6,4 @@ import type { ComponentContext, Dom, Equals, TemplateNode, TransitionManager } from '#client'; | ||
f: number; | ||
/** Write version */ | ||
version: number; | ||
} | ||
@@ -16,4 +18,2 @@ | ||
v: V; | ||
/** Write version */ | ||
version: number; | ||
} | ||
@@ -20,0 +20,0 @@ |
@@ -32,4 +32,3 @@ import { DEV } from 'esm-env'; | ||
DISCONNECTED, | ||
STATE_FROZEN_SYMBOL, | ||
INSPECT_EFFECT | ||
STATE_FROZEN_SYMBOL | ||
} from './constants.js'; | ||
@@ -67,4 +66,2 @@ import { flush_tasks } from './dom/task.js'; | ||
export let inspect_effects = new Set(); | ||
// Handle effect queues | ||
@@ -94,6 +91,13 @@ | ||
/** @type {null | import('#client').Value[]} */ | ||
export let current_dependencies = null; | ||
let current_dependencies_index = 0; | ||
/** | ||
* The dependencies of the reaction that is currently being executed. In many cases, | ||
* the dependencies are unchanged between runs, and so this will be `null` unless | ||
* and until a new dependency is accessed — we track this via `skipped_deps` | ||
* @type {null | import('#client').Value[]} | ||
*/ | ||
export let new_deps = null; | ||
let skipped_deps = 0; | ||
/** | ||
* Tracks writes that the effect it's executed in doesn't listen to yet, | ||
@@ -163,11 +167,7 @@ * so that the dependency can be added to the effect later on if it then reads it | ||
var flags = reaction.f; | ||
var is_dirty = (flags & DIRTY) !== 0; | ||
if (is_dirty) { | ||
if ((flags & DIRTY) !== 0) { | ||
return true; | ||
} | ||
var is_unowned = (flags & UNOWNED) !== 0; | ||
var is_disconnected = (flags & DISCONNECTED) !== 0; | ||
if ((flags & MAYBE_DIRTY) !== 0) { | ||
@@ -177,14 +177,12 @@ var dependencies = reaction.deps; | ||
if (dependencies !== null) { | ||
var length = dependencies.length; | ||
var reactions; | ||
var is_unowned = (flags & UNOWNED) !== 0; | ||
for (var i = 0; i < length; i++) { | ||
for (var i = 0; i < dependencies.length; i++) { | ||
var dependency = dependencies[i]; | ||
if (!is_dirty && check_dirtiness(/** @type {import('#client').Derived} */ (dependency))) { | ||
update_derived(/** @type {import('#client').Derived} **/ (dependency)); | ||
if (check_dirtiness(/** @type {import('#client').Derived} */ (dependency))) { | ||
update_derived(/** @type {import('#client').Derived} */ (dependency)); | ||
} | ||
if ((reaction.f & DIRTY) !== 0) { | ||
// `reaction` might now be dirty, as a result of calling `update_derived` | ||
if (dependency.version > reaction.version) { | ||
return true; | ||
@@ -194,10 +192,3 @@ } | ||
if (is_unowned) { | ||
// If we're working with an unowned derived signal, then we need to check | ||
// if our dependency write version is higher. If it is then we can assume | ||
// that state has changed to a newer version and thus this unowned signal | ||
// is also dirty. | ||
if (dependency.version > /** @type {import('#client').Derived} */ (reaction).version) { | ||
return true; | ||
} | ||
// TODO is there a more logical place to do this work? | ||
if (!current_skip_reaction && !dependency?.reactions?.includes(reaction)) { | ||
@@ -209,16 +200,2 @@ // If we are working with an unowned signal as part of an effect (due to !current_skip_reaction) | ||
} | ||
} else if (is_disconnected) { | ||
// It might be that the derived was was dereferenced from its dependencies but has now come alive again. | ||
// In thise case, we need to re-attach it to the graph and mark it dirty if any of its dependencies have | ||
// changed since. | ||
if (dependency.version > /** @type {import('#client').Derived} */ (reaction).version) { | ||
is_dirty = true; | ||
} | ||
reactions = dependency.reactions; | ||
if (reactions === null) { | ||
dependency.reactions = [reaction]; | ||
} else if (!reactions.includes(reaction)) { | ||
reactions.push(reaction); | ||
} | ||
} | ||
@@ -228,12 +205,6 @@ } | ||
// Unowned signals are always maybe dirty, as we instead check their dependency versions. | ||
if (!is_unowned) { | ||
set_signal_status(reaction, CLEAN); | ||
} | ||
if (is_disconnected) { | ||
reaction.f ^= DISCONNECTED; | ||
} | ||
set_signal_status(reaction, CLEAN); | ||
} | ||
return is_dirty; | ||
return false; | ||
} | ||
@@ -307,4 +278,4 @@ | ||
export function update_reaction(reaction) { | ||
var previous_dependencies = current_dependencies; | ||
var previous_dependencies_index = current_dependencies_index; | ||
var previous_deps = new_deps; | ||
var previous_skipped_deps = skipped_deps; | ||
var previous_untracked_writes = current_untracked_writes; | ||
@@ -314,4 +285,4 @@ var previous_reaction = current_reaction; | ||
current_dependencies = /** @type {null | import('#client').Value[]} */ (null); | ||
current_dependencies_index = 0; | ||
new_deps = /** @type {null | import('#client').Value[]} */ (null); | ||
skipped_deps = 0; | ||
current_untracked_writes = null; | ||
@@ -323,24 +294,19 @@ current_reaction = (reaction.f & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null; | ||
var result = /** @type {Function} */ (0, reaction.fn)(); | ||
var dependencies = /** @type {import('#client').Value<unknown>[]} **/ (reaction.deps); | ||
var deps = reaction.deps; | ||
if (current_dependencies !== null) { | ||
if (new_deps !== null) { | ||
var dependency; | ||
var i; | ||
if (dependencies !== null) { | ||
var deps_length = dependencies.length; | ||
if (deps !== null) { | ||
/** All dependencies of the reaction, including those tracked on the previous run */ | ||
var array = | ||
current_dependencies_index === 0 | ||
? current_dependencies | ||
: dependencies.slice(0, current_dependencies_index).concat(current_dependencies); | ||
var array = skipped_deps === 0 ? new_deps : deps.slice(0, skipped_deps).concat(new_deps); | ||
// If we have more than 16 elements in the array then use a Set for faster performance | ||
// TODO: evaluate if we should always just use a Set or not here? | ||
var set = | ||
array.length > 16 && deps_length - current_dependencies_index > 1 ? new Set(array) : null; | ||
var set = array.length > 16 ? new Set(array) : null; | ||
for (i = current_dependencies_index; i < deps_length; i++) { | ||
dependency = dependencies[i]; | ||
// Remove dependencies that should no longer be tracked | ||
for (i = skipped_deps; i < deps.length; i++) { | ||
dependency = deps[i]; | ||
@@ -353,16 +319,14 @@ if (set !== null ? !set.has(dependency) : !array.includes(dependency)) { | ||
if (dependencies !== null && current_dependencies_index > 0) { | ||
dependencies.length = current_dependencies_index + current_dependencies.length; | ||
for (i = 0; i < current_dependencies.length; i++) { | ||
dependencies[current_dependencies_index + i] = current_dependencies[i]; | ||
if (deps !== null && skipped_deps > 0) { | ||
deps.length = skipped_deps + new_deps.length; | ||
for (i = 0; i < new_deps.length; i++) { | ||
deps[skipped_deps + i] = new_deps[i]; | ||
} | ||
} else { | ||
reaction.deps = /** @type {import('#client').Value<V>[]} **/ ( | ||
dependencies = current_dependencies | ||
); | ||
reaction.deps = deps = new_deps; | ||
} | ||
if (!current_skip_reaction) { | ||
for (i = current_dependencies_index; i < dependencies.length; i++) { | ||
dependency = dependencies[i]; | ||
for (i = skipped_deps; i < deps.length; i++) { | ||
dependency = deps[i]; | ||
var reactions = dependency.reactions; | ||
@@ -380,5 +344,5 @@ | ||
} | ||
} else if (dependencies !== null && current_dependencies_index < dependencies.length) { | ||
remove_reactions(reaction, current_dependencies_index); | ||
dependencies.length = current_dependencies_index; | ||
} else if (deps !== null && skipped_deps < deps.length) { | ||
remove_reactions(reaction, skipped_deps); | ||
deps.length = skipped_deps; | ||
} | ||
@@ -388,4 +352,4 @@ | ||
} finally { | ||
current_dependencies = previous_dependencies; | ||
current_dependencies_index = previous_dependencies_index; | ||
new_deps = previous_deps; | ||
skipped_deps = previous_skipped_deps; | ||
current_untracked_writes = previous_untracked_writes; | ||
@@ -507,2 +471,4 @@ current_reaction = previous_reaction; | ||
effect.teardown = typeof teardown === 'function' ? teardown : null; | ||
effect.version = current_version; | ||
} catch (error) { | ||
@@ -773,3 +739,4 @@ handle_error(/** @type {Error} */ (error), effect, current_component_context); | ||
export function get(signal) { | ||
const flags = signal.f; | ||
var flags = signal.f; | ||
if ((flags & DESTROYED) !== 0) { | ||
@@ -785,22 +752,21 @@ return signal.v; | ||
if (current_reaction !== null) { | ||
const unowned = (current_reaction.f & UNOWNED) !== 0; | ||
const dependencies = current_reaction.deps; | ||
if ( | ||
current_dependencies === null && | ||
dependencies !== null && | ||
dependencies[current_dependencies_index] === signal && | ||
!(unowned && current_effect !== null) | ||
) { | ||
current_dependencies_index++; | ||
} else if ( | ||
dependencies === null || | ||
current_dependencies_index === 0 || | ||
dependencies[current_dependencies_index - 1] !== signal | ||
) { | ||
if (current_dependencies === null) { | ||
current_dependencies = [signal]; | ||
} else if (current_dependencies[current_dependencies.length - 1] !== signal) { | ||
current_dependencies.push(signal); | ||
var deps = current_reaction.deps; | ||
// If the signal is accessing the same dependencies in the same | ||
// order as it did last time, increment `skipped_deps` | ||
// rather than updating `new_deps`, which creates GC cost | ||
if (new_deps === null && deps !== null && deps[skipped_deps] === signal) { | ||
skipped_deps++; | ||
} | ||
// Otherwise, create or push to `new_deps`, but only if this | ||
// dependency wasn't the last one that was accessed | ||
else if (deps === null || skipped_deps === 0 || deps[skipped_deps - 1] !== signal) { | ||
if (new_deps === null) { | ||
new_deps = [signal]; | ||
} else if (new_deps[new_deps.length - 1] !== signal) { | ||
new_deps.push(signal); | ||
} | ||
} | ||
if ( | ||
@@ -818,7 +784,27 @@ current_untracked_writes !== null && | ||
if ( | ||
(flags & DERIVED) !== 0 && | ||
check_dirtiness(/** @type {import('#client').Derived} */ (signal)) | ||
) { | ||
update_derived(/** @type {import('#client').Derived} **/ (signal)); | ||
if ((flags & DERIVED) !== 0) { | ||
var derived = /** @type {import('#client').Derived} */ (signal); | ||
if (check_dirtiness(derived)) { | ||
update_derived(derived); | ||
} | ||
if ((flags & DISCONNECTED) !== 0) { | ||
// reconnect to the graph | ||
deps = derived.deps; | ||
if (deps !== null) { | ||
for (var i = 0; i < deps.length; i++) { | ||
var dep = deps[i]; | ||
var reactions = dep.reactions; | ||
if (reactions === null) { | ||
dep.reactions = [derived]; | ||
} else if (!reactions.includes(derived)) { | ||
reactions.push(derived); | ||
} | ||
} | ||
} | ||
derived.f ^= DISCONNECTED; | ||
} | ||
} | ||
@@ -867,48 +853,2 @@ | ||
/** | ||
* @param {import('#client').Value} signal | ||
* @param {number} status should be DIRTY or MAYBE_DIRTY | ||
* @param {boolean} force_schedule | ||
* @returns {void} | ||
*/ | ||
export function mark_reactions(signal, status, force_schedule) { | ||
var reactions = signal.reactions; | ||
if (reactions === null) return; | ||
var runes = is_runes(); | ||
var length = reactions.length; | ||
for (var i = 0; i < length; i++) { | ||
var reaction = reactions[i]; | ||
var flags = reaction.f; | ||
if (DEV && (flags & INSPECT_EFFECT) !== 0) { | ||
inspect_effects.add(reaction); | ||
continue; | ||
} | ||
// We skip any effects that are already dirty. Additionally, we also | ||
// skip if the reaction is the same as the current effect (except if we're not in runes or we | ||
// are in force schedule mode). | ||
if ((flags & DIRTY) !== 0 || ((!force_schedule || !runes) && reaction === current_effect)) { | ||
continue; | ||
} | ||
set_signal_status(reaction, status); | ||
// If the signal a) was previously clean or b) is an unowned derived, then mark it | ||
if ((flags & (CLEAN | UNOWNED)) !== 0) { | ||
if ((flags & DERIVED) !== 0) { | ||
mark_reactions( | ||
/** @type {import('#client').Derived} */ (reaction), | ||
MAYBE_DIRTY, | ||
force_schedule | ||
); | ||
} else { | ||
schedule_effect(/** @type {import('#client').Effect} */ (reaction)); | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
* Use `untrack` to prevent something from being treated as an `$effect`/`$derived` dependency. | ||
@@ -915,0 +855,0 @@ * |
@@ -151,5 +151,4 @@ import type { Store } from '#shared'; | ||
store: Store<any> | null; | ||
last_value: any; | ||
unsubscribe: Function; | ||
value: Source<any>; | ||
source: Source<any>; | ||
} | ||
@@ -156,0 +155,0 @@ >; |
@@ -9,3 +9,3 @@ // generated during release, do not modify | ||
*/ | ||
export const VERSION = '5.0.0-next.172'; | ||
export const VERSION = '5.0.0-next.173'; | ||
export const PUBLIC_VERSION = '5'; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2113038
46362
30
47
298