From 10aacfa603d7a7f139ca3c63556c2ef7af071ba1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 5 Dec 2023 13:56:46 -0500 Subject: [PATCH] chore: remove `exposable` (#9783) * use flags for prop_source, this will be useful later * remove exposable/expose stuff --------- Co-authored-by: Rich Harris --- .../phases/3-transform/client/utils.js | 47 ++++++---- .../3-transform/client/visitors/template.js | 31 ++----- packages/svelte/src/constants.js | 3 + packages/svelte/src/internal/client/render.js | 6 +- .../svelte/src/internal/client/runtime.js | 92 +++---------------- packages/svelte/src/internal/index.js | 2 - .../_expected/client/index.svelte.js | 2 +- 7 files changed, 57 insertions(+), 126 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index d6700add11..0227313214 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -1,6 +1,7 @@ import * as b from '../../../utils/builders.js'; import { extract_paths, is_simple_expression } from '../../../utils/ast.js'; import { error } from '../../../errors.js'; +import { PROPS_CALL_DEFAULT_VALUE, PROPS_IS_IMMUTABLE } from '../../../../constants.js'; /** * @template {import('./types').ClientTransformState} State @@ -359,29 +360,43 @@ export function get_props_method(binding, state, name, default_value) { (state.analysis.immutable ? binding.reassigned : binding.mutated); if (needs_source) { - args.push(b.literal(state.analysis.immutable)); - } + let flags = 0; - if (default_value) { - // To avoid eagerly evaluating the right-hand-side, we wrap it in a thunk if necessary - if (is_simple_expression(default_value)) { - args.push(default_value); - } else { - if ( - default_value.type === 'CallExpression' && - default_value.callee.type === 'Identifier' && - default_value.arguments.length === 0 - ) { - args.push(default_value.callee); + /** @type {import('estree').Expression | undefined} */ + let arg; + + if (state.analysis.immutable) { + flags |= PROPS_IS_IMMUTABLE; + } + + if (default_value) { + // To avoid eagerly evaluating the right-hand-side, we wrap it in a thunk if necessary + if (is_simple_expression(default_value)) { + arg = default_value; } else { - args.push(b.thunk(default_value)); + if ( + default_value.type === 'CallExpression' && + default_value.callee.type === 'Identifier' && + default_value.arguments.length === 0 + ) { + arg = default_value.callee; + } else { + arg = b.thunk(default_value); + } + + flags |= PROPS_CALL_DEFAULT_VALUE; } + } - args.push(b.true); + if (flags || arg) { + args.push(b.literal(flags)); + if (arg) args.push(arg); } + + return b.call('$.prop_source', ...args); } - return b.call(needs_source ? '$.prop_source' : '$.prop', ...args); + return b.call('$.prop', ...args); } /** diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index feb9174187..fb43ec654b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -795,32 +795,17 @@ function serialize_inline_component(node, component_name, context) { push_prop( b.get(attribute.name, [ b.return( - b.call( - '$.exposable', - b.thunk( - /** @type {import('estree').Expression} */ (context.visit(attribute.expression)) - ) - ) + /** @type {import('estree').Expression} */ (context.visit(attribute.expression)) ) ]) ); - // If the binding is just a reference to a top level state variable - // we don't need a setter as the inner component can write to the signal directly - const binding = - attribute.expression.type !== 'Identifier' - ? null - : context.state.scope.get(attribute.expression.name); - if ( - binding === null || - (binding.kind !== 'state' && binding.kind !== 'prop' && binding.kind !== 'rest_prop') - ) { - const assignment = b.assignment('=', attribute.expression, b.id('$$value')); - push_prop( - b.set(attribute.name, [ - b.stmt(serialize_set_binding(assignment, context, () => context.visit(assignment))) - ]) - ); - } + + const assignment = b.assignment('=', attribute.expression, b.id('$$value')); + push_prop( + b.set(attribute.name, [ + b.stmt(serialize_set_binding(assignment, context, () => context.visit(assignment))) + ]) + ); } } } diff --git a/packages/svelte/src/constants.js b/packages/svelte/src/constants.js index 6b7cc123f5..d5daaf700b 100644 --- a/packages/svelte/src/constants.js +++ b/packages/svelte/src/constants.js @@ -5,6 +5,9 @@ export const EACH_IS_CONTROLLED = 1 << 3; export const EACH_IS_ANIMATED = 1 << 4; export const EACH_IS_IMMUTABLE = 1 << 6; +export const PROPS_IS_IMMUTABLE = 1; +export const PROPS_CALL_DEFAULT_VALUE = 1 << 1; + /** List of Element events that will be delegated */ export const DelegatedEvents = [ 'beforeinput', diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 2111f412e4..bb9faa2707 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -34,7 +34,6 @@ import { untrack, effect, flushSync, - expose, safe_not_equal, current_block, source, @@ -1193,10 +1192,7 @@ export function bind_prop(props, prop, value) { /** @param {V | null} value */ const update = (value) => { const current_props = unwrap(props); - const signal = expose(() => current_props[prop]); - if (is_signal(signal)) { - set(signal, value); - } else if (Object.getOwnPropertyDescriptor(current_props, prop)?.set !== undefined) { + if (get_descriptor(current_props, prop)?.set !== undefined) { current_props[prop] = value; } }; diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 2d3e5204c7..29f1b733dd 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1,7 +1,8 @@ import { DEV } from 'esm-env'; import { subscribe_to_store } from '../../store/utils.js'; import { EMPTY_FUNC, run_all } from '../common.js'; -import { get_descriptors, is_array } from './utils.js'; +import { get_descriptor, get_descriptors, is_array } from './utils.js'; +import { PROPS_CALL_DEFAULT_VALUE, PROPS_IS_IMMUTABLE } from '../../constants.js'; export const SOURCE = 1; export const DERIVED = 1 << 1; @@ -30,8 +31,7 @@ let current_scheduler_mode = FLUSH_MICROTASK; // Used for handling scheduling let is_micro_task_queued = false; let is_task_queued = false; -// Used for exposing signals -let is_signal_exposed = false; + // Handle effect queues /** @type {import('./types.js').EffectSignal[]} */ @@ -63,8 +63,6 @@ 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; -/** @type {null | import('./types.js').Signal} */ -let current_captured_signal = null; // 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; @@ -800,23 +798,6 @@ export function unsubscribe_on_destroy(stores) { }); } -/** - * Wraps a function and marks execution context so that the last signal read from can be captured - * using the `expose` function. - * @template V - * @param {() => V} fn - * @returns {V} - */ -export function exposable(fn) { - const previous_is_signal_exposed = is_signal_exposed; - try { - is_signal_exposed = true; - return fn(); - } finally { - is_signal_exposed = previous_is_signal_exposed; - } -} - /** * @template V * @param {import('./types.js').Signal} signal @@ -836,10 +817,6 @@ export function get(signal) { return signal.v; } - if (is_signal_exposed && current_should_capture_signal) { - current_captured_signal = signal; - } - if (is_signals_recorded) { captured_signals.add(signal); } @@ -906,31 +883,6 @@ export function set_sync(signal, value) { flushSync(() => set(signal, value)); } -/** - * Invokes a function and captures the last signal that is read during the invocation - * if that signal is read within the `exposable` function context. - * If a signal is captured, it returns the signal instead of the read value. - * @template V - * @param {() => V} possible_signal_fn - * @returns {any} - */ -export function expose(possible_signal_fn) { - const previous_captured_signal = current_captured_signal; - const previous_should_capture_signal = current_should_capture_signal; - current_captured_signal = null; - current_should_capture_signal = true; - try { - const value = possible_signal_fn(); - if (current_captured_signal === null) { - return value; - } - return current_captured_signal; - } finally { - current_captured_signal = previous_captured_signal; - current_should_capture_signal = previous_should_capture_signal; - } -} - /** * Invokes a function and captures all signals that are read during the invocation, * then invalidates them. @@ -1463,35 +1415,19 @@ export function is_store(val) { * @template V * @param {import('./types.js').MaybeSignal>} props_obj * @param {string} key - * @param {boolean} immutable + * @param {number} flags * @param {V | (() => V)} [default_value] - * @param {boolean} [call_default_value] * @returns {import('./types.js').Signal | (() => V)} */ -export function prop_source(props_obj, key, immutable, default_value, call_default_value) { +export function prop_source(props_obj, key, flags, default_value) { + const call_default_value = (flags & PROPS_CALL_DEFAULT_VALUE) !== 0; + const immutable = (flags & PROPS_IS_IMMUTABLE) !== 0; + const props = is_signal(props_obj) ? get(props_obj) : props_obj; - const possible_signal = /** @type {import('./types.js').MaybeSignal} */ ( - expose(() => props[key]) - ); - const update_bound_prop = Object.getOwnPropertyDescriptor(props, key)?.set; + const update_bound_prop = get_descriptor(props, key)?.set; let value = props[key]; const should_set_default_value = value === undefined && default_value !== undefined; - if ( - is_signal(possible_signal) && - possible_signal.v === value && - update_bound_prop === undefined - ) { - if (should_set_default_value) { - set( - possible_signal, - // @ts-expect-error would need a cumbersome method overload to type this - call_default_value ? default_value() : default_value - ); - } - return possible_signal; - } - if (should_set_default_value) { value = // @ts-expect-error would need a cumbersome method overload to type this @@ -1534,7 +1470,7 @@ export function prop_source(props_obj, key, immutable, default_value, call_defau } }); - if (is_signal(possible_signal) && update_bound_prop !== undefined) { + if (update_bound_prop !== undefined) { let ignore_first = !should_set_default_value; sync_effect(() => { // Before if to ensure signal dependency is registered @@ -1548,11 +1484,9 @@ export function prop_source(props_obj, key, immutable, default_value, call_defau return; } - if (not_equal(immutable, propagating_value, possible_signal.v)) { - ignore_next1 = true; - did_update_to_defined = true; - untrack(() => update_bound_prop(propagating_value)); - } + ignore_next1 = true; + did_update_to_defined = true; + untrack(() => update_bound_prop(propagating_value)); }); } diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index 840e8cfd43..b66715e286 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, - expose, - exposable, source, mutable_source, derived, diff --git a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js index b4df70d14c..5ed73f0f29 100644 --- a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js @@ -6,7 +6,7 @@ import * as $ from "svelte/internal"; export default function Svelte_element($$anchor, $$props) { $.push($$props, true); - let tag = $.prop_source($$props, "tag", true, 'hr'); + let tag = $.prop_source($$props, "tag", 1, 'hr'); /* Init */ var fragment = $.comment($$anchor); var node = $.child_frag(fragment);