From 34dbee71fc12c056b649e977eddebd83a99d644e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 20 Feb 2024 18:24:59 -0500 Subject: [PATCH] derived --- .../src/internal/client/custom-element.js | 2 +- .../src/internal/client/dom/blocks/await.js | 2 +- .../src/internal/client/dom/blocks/each.js | 2 +- .../src/internal/client/dom/blocks/if.js | 2 +- .../src/internal/client/dom/blocks/key.js | 2 +- packages/svelte/src/internal/client/proxy.js | 2 +- .../{effects.js => computations.js} | 38 ++++++++++++++++ packages/svelte/src/internal/client/render.js | 2 +- .../svelte/src/internal/client/runtime.js | 40 ++--------------- .../svelte/src/internal/client/transitions.js | 2 +- packages/svelte/src/internal/index.js | 4 +- packages/svelte/tests/signals/test.ts | 45 ++++++++++--------- 12 files changed, 76 insertions(+), 67 deletions(-) rename packages/svelte/src/internal/client/reactivity/{effects.js => computations.js} (83%) diff --git a/packages/svelte/src/internal/client/custom-element.js b/packages/svelte/src/internal/client/custom-element.js index 0bd64cdf50..92d1a46573 100644 --- a/packages/svelte/src/internal/client/custom-element.js +++ b/packages/svelte/src/internal/client/custom-element.js @@ -1,6 +1,6 @@ import { createClassComponent } from '../../legacy/legacy-client.js'; import { destroy_signal } from './runtime.js'; -import { render_effect } from './reactivity/effects.js'; +import { render_effect } from './reactivity/computations.js'; import { open, close } from './render.js'; import { define_property } from './utils.js'; diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index 0d7b8daf55..19f4e61c96 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -10,7 +10,7 @@ import { flushSync, push_destroy_fn } from '../../runtime.js'; -import { render_effect } from '../../reactivity/effects.js'; +import { render_effect } from '../../reactivity/computations.js'; import { trigger_transitions } from '../../transitions.js'; /** @returns {import('../../types.js').AwaitBlock} */ diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index ef3c9c9ed8..b6ca2d402f 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -24,7 +24,7 @@ import { push_destroy_fn, set_signal_value } from '../../runtime.js'; -import { render_effect } from '../../reactivity/effects.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'; diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index cd03fe8c05..9573b7cca1 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -7,7 +7,7 @@ import { } from '../../hydration.js'; import { remove } from '../../reconciler.js'; import { current_block, destroy_signal, execute_effect, push_destroy_fn } from '../../runtime.js'; -import { render_effect } from '../../reactivity/effects.js'; +import { render_effect } from '../../reactivity/computations.js'; import { trigger_transitions } from '../../transitions.js'; /** @returns {import('../../types.js').IfBlock} */ diff --git a/packages/svelte/src/internal/client/dom/blocks/key.js b/packages/svelte/src/internal/client/dom/blocks/key.js index fbe716a9a2..076bc9c8d8 100644 --- a/packages/svelte/src/internal/client/dom/blocks/key.js +++ b/packages/svelte/src/internal/client/dom/blocks/key.js @@ -8,7 +8,7 @@ import { execute_effect, push_destroy_fn } from '../../runtime.js'; -import { render_effect } from '../../reactivity/effects.js'; +import { render_effect } from '../../reactivity/computations.js'; import { trigger_transitions } from '../../transitions.js'; import { safe_not_equal } from '../../reactivity/equality.js'; diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 60b4165d75..7d5e97a42b 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -8,7 +8,7 @@ import { batch_inspect, current_component_context } from './runtime.js'; -import { effect_active } from './reactivity/effects.js'; +import { effect_active } from './reactivity/computations.js'; import { array_prototype, define_property, diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/computations.js similarity index 83% rename from packages/svelte/src/internal/client/reactivity/effects.js rename to packages/svelte/src/internal/client/reactivity/computations.js index 955e91b215..a5b55d4571 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/computations.js @@ -1,19 +1,25 @@ import { DEV } from 'esm-env'; import { + CLEAN, + DERIVED, DIRTY, EFFECT, MANAGED, PRE_EFFECT, RENDER_EFFECT, + UNINITIALIZED, + UNOWNED, create_computation_signal, current_block, current_component_context, + current_consumer, current_effect, destroy_signal, flush_local_render_effects, push_reference, schedule_effect } from '../runtime.js'; +import { default_equals, safe_equal } from './equality.js'; /** * @param {import('../types.js').EffectType} type @@ -178,3 +184,35 @@ 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 {() => V} init + * @returns {import('../types.js').ComputationSignal} + */ +/*#__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} */ ( + 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} + */ +/*#__NO_SIDE_EFFECTS__*/ +export function derived_safe_equal(init) { + const signal = derived(init); + signal.e = safe_equal; + return signal; +} diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index cc35acc468..139af02404 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -43,7 +43,7 @@ import { pop, deep_read } from './runtime.js'; -import { render_effect, effect, managed_effect } from './reactivity/effects.js'; +import { render_effect, effect, managed_effect } from './reactivity/computations.js'; import { current_hydration_fragment, get_hydration_fragment, diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 960b25abce..b5510fb7f1 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -19,9 +19,9 @@ import { } from '../../constants.js'; import { STATE_SYMBOL, unstate } from './proxy.js'; import { EACH_BLOCK, IF_BLOCK } from './block.js'; -import { pre_effect, user_effect } from './reactivity/effects.js'; +import { derived, pre_effect, user_effect } from './reactivity/computations.js'; import { mutable_source, source } from './reactivity/sources.js'; -import { default_equals, safe_equal, safe_not_equal } from './reactivity/equality.js'; +import { safe_equal, safe_not_equal } from './reactivity/equality.js'; export const SOURCE = 1; export const DERIVED = 1 << 1; @@ -29,7 +29,7 @@ export const EFFECT = 1 << 2; export const PRE_EFFECT = 1 << 3; export const RENDER_EFFECT = 1 << 4; export const MANAGED = 1 << 6; -const UNOWNED = 1 << 7; +export const UNOWNED = 1 << 7; export const CLEAN = 1 << 8; export const DIRTY = 1 << 9; export const MAYBE_DIRTY = 1 << 10; @@ -69,7 +69,7 @@ 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; @@ -1252,38 +1252,6 @@ export function destroy_signal(signal) { } } -/** - * @template V - * @param {() => V} init - * @returns {import('./types.js').ComputationSignal} - */ -/*#__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} */ ( - 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} - */ -/*#__NO_SIDE_EFFECTS__*/ -export function derived_safe_equal(init) { - const signal = derived(init); - signal.e = safe_equal; - return signal; -} - /** * Use `untrack` to prevent something from being treated as an `$effect`/`$derived` dependency. * diff --git a/packages/svelte/src/internal/client/transitions.js b/packages/svelte/src/internal/client/transitions.js index 7cb5f3a36c..5d86a77d86 100644 --- a/packages/svelte/src/internal/client/transitions.js +++ b/packages/svelte/src/internal/client/transitions.js @@ -11,7 +11,7 @@ import { } from './block.js'; import { destroy_each_item_block, get_first_element } from './dom/blocks/each.js'; import { append_child, empty } from './operations.js'; -import { effect, managed_effect, managed_pre_effect } from './reactivity/effects.js'; +import { effect, managed_effect, managed_pre_effect } from './reactivity/computations.js'; import { current_block, current_effect, diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index c8262e2cdd..2752b8048e 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -4,8 +4,6 @@ export { set, set_sync, invalidate_inner_signals, - derived, - derived_safe_equal, prop, flushSync, bubble_event, @@ -37,7 +35,7 @@ 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/effects.js'; +export * from './client/reactivity/computations.js'; export * from './client/reactivity/sources.js'; export * from './client/reactivity/equality.js'; export * from './client/render.js'; diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index 2628a67fa6..8b91601a3b 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -1,6 +1,11 @@ import { describe, assert, it } from 'vitest'; import * as $ from '../../src/internal/client/runtime'; -import { effect, render_effect, user_effect } from '../../src/internal/client/reactivity/effects'; +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'; @@ -39,7 +44,7 @@ describe('signals', () => { const log: string[] = []; let count = source(0); - let double = $.derived(() => $.get(count) * 2); + let double = derived(() => $.get(count) * 2); effect(() => { log.push(`${$.get(count)}:${$.get(double)}`); }); @@ -56,7 +61,7 @@ describe('signals', () => { const log: string[] = []; let count = source(0); - let double = $.derived(() => $.get(count) * 2); + let double = derived(() => $.get(count) * 2); effect(() => { log.push(`A:${$.get(count)}:${$.get(double)}`); @@ -77,7 +82,7 @@ describe('signals', () => { const log: string[] = []; let count = source(0); - let double = $.derived(() => $.get(count) * 2); + let double = derived(() => $.get(count) * 2); effect(() => { log.push(`A:${$.get(double)}`); @@ -98,7 +103,7 @@ describe('signals', () => { const log: number[] = []; let count = source(0); - let double = $.derived(() => $.get(count) * 2); + let double = derived(() => $.get(count) * 2); effect(() => { log.push($.get(double)); @@ -116,8 +121,8 @@ describe('signals', () => { const log: number[] = []; let count = source(0); - let double = $.derived(() => $.get(count) * 2); - let quadruple = $.derived(() => $.get(double) * 2); + let double = derived(() => $.get(count) * 2); + let quadruple = derived(() => $.get(double) * 2); effect(() => { log.push($.get(quadruple)); @@ -140,11 +145,11 @@ describe('signals', () => { 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)); + 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')); }); @@ -180,10 +185,10 @@ describe('signals', () => { 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); + const derivedCount = derived(() => read().count); user_effect(() => { log.push($.get(derivedCount)); }); @@ -208,9 +213,9 @@ describe('signals', () => { const a = source(0); const b = source(0); - const c = $.derived(() => { - const a_2 = $.derived(() => $.get(a) + '!'); - const b_2 = $.derived(() => $.get(b) + '?'); + 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) }; @@ -236,7 +241,7 @@ describe('signals', () => { // outside of test function so that they are unowned signals let count = source(0); - let calc = $.derived(() => { + let calc = derived(() => { if ($.get(count) >= 2) { return 'limit'; } @@ -265,7 +270,7 @@ describe('signals', () => { }; }); - let no_deps = $.derived(() => { + let no_deps = derived(() => { return []; }); @@ -287,7 +292,7 @@ describe('signals', () => { }); let some_state = source({}); - let some_deps = $.derived(() => { + let some_deps = derived(() => { return [$.get(some_state)]; });