From f6e821a497489eca6592c63b0e587a86769625cd Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 6 Dec 2023 16:30:32 -0500 Subject: [PATCH] WIP --- .../3-transform/client/transform-client.js | 4 +- .../phases/3-transform/client/utils.js | 50 +++++--- packages/svelte/src/internal/client/render.js | 2 +- .../svelte/src/internal/client/runtime.js | 114 +++++------------- .../_expected/client/index.svelte.js | 6 +- 5 files changed, 74 insertions(+), 102 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index d69b70ec08..59ef148db3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -255,8 +255,8 @@ export function client_component(source, analysis, options) { const key = binding.prop_alias ?? name; properties.push( - b.get(key, [b.return(b.call('$.get', b.id(name)))]), - b.set(key, [b.stmt(b.call('$.set_sync', b.id(name), b.id('$$value')))]) + b.get(key, [b.return(b.call(b.id(name)))]), + b.set(key, [b.stmt(b.call(b.id(name), b.id('$$value')))]) ); } } 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 915e9795fe..90f5c6ec29 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -74,11 +74,14 @@ export function serialize_get_binding(node, state) { } if ( - !state.analysis.accessors && - !(state.analysis.immutable ? binding.reassigned : binding.mutated) + state.analysis.accessors || + (state.analysis.immutable ? binding.reassigned : binding.mutated) || + binding.initial ) { - return binding.initial ? b.call(node) : b.member(b.id('$$props'), node); + return b.call(node); } + + return b.member(b.id('$$props'), node); } if (binding.kind === 'legacy_reactive_import') { @@ -89,7 +92,6 @@ export function serialize_get_binding(node, state) { (binding.kind === 'state' && (!state.analysis.immutable || state.analysis.accessors || binding.reassigned)) || binding.kind === 'derived' || - binding.kind === 'prop' || binding.kind === 'legacy_reactive' ) { return b.call('$.get', node); @@ -208,9 +210,12 @@ export function serialize_set_binding(node, context, fallback) { } const value = get_assignment_value(node, context); + const serialize = () => { if (left === node.left) { - if (is_store) { + if (binding.kind === 'prop') { + return b.call(left, value); + } else if (is_store) { return b.call('$.store_set', serialize_get_binding(b.id(left_name), state), value); } else { return b.call( @@ -232,15 +237,27 @@ export function serialize_set_binding(node, context, fallback) { b.call('$' + left_name) ); } else if (!state.analysis.runes) { - return b.call( - '$.mutate', - b.id(left_name), - b.assignment( - node.operator, - /** @type {import('estree').Pattern} */ (visit(node.left)), - value - ) - ); + if (binding.kind === 'prop') { + return b.call( + left, + b.assignment( + node.operator, + /** @type {import('estree').Pattern} */ (visit(node.left)), + value + ), + b.literal(true) + ); + } else { + return b.call( + '$.mutate', + b.id(left_name), + b.assignment( + node.operator, + /** @type {import('estree').Pattern} */ (visit(node.left)), + value + ) + ); + } } else { return b.assignment( node.operator, @@ -365,7 +382,10 @@ export function get_prop_source(binding, state, name, initial) { flags |= PROPS_IS_RUNES; } - if (state.analysis.immutable ? binding.reassigned : binding.mutated) { + if ( + state.analysis.accessors || + (state.analysis.immutable ? binding.reassigned : binding.mutated) + ) { flags |= PROPS_IS_UPDATED; } diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index ab8d44bb37..13af5dcf66 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -2661,7 +2661,7 @@ export function createRoot(component, options) { /** @param {any} value */ set(value) { // @ts-expect-error TS doesn't know key exists on accessor - accessors[key] = value; + flushSync(() => (accessors[key] = value)); }, enumerable: true }); diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 8e2e53932c..9958b8553e 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1409,7 +1409,7 @@ export function is_store(val) { * @param {string} key * @param {number} flags * @param {V | (() => V)} [initial] - * @returns {import('./types.js').Signal | (() => V)} + * @returns {(() => V | ((arg: V) => V) | ((arg: V, mutation: boolean) => V))} */ export function prop(props, key, flags, initial) { var immutable = (flags & PROPS_IS_IMMUTABLE) !== 0; @@ -1422,101 +1422,53 @@ export function prop(props, key, flags, initial) { } var value = /** @type {V} */ (props[key]); - var should_set_default_value = value === undefined && initial !== undefined; - if (should_set_default_value) { + if (value === undefined && initial !== undefined) { // @ts-expect-error would need a cumbersome method overload to type this - value = (flags & PROPS_IS_LAZY_INITIAL) !== 0 ? initial() : initial; + if ((flags & PROPS_IS_LAZY_INITIAL) !== 0) initial = initial(); if (DEV && runes) { - value = readonly(proxy(/** @type {any} */ (value))); + initial = readonly(proxy(/** @type {any} */ (initial))); } - } - if ((flags & PROPS_IS_UPDATED) === 0) { - return () => { - var value = /** @type {V} */ (props[key]); - if (value !== undefined) initial = undefined; - return value === undefined ? /** @type {V} */ (initial) : value; - }; + value = /** @type {V} */ (initial); } - var source_signal = immutable ? source(value) : mutable_source(value); - - // Synchronize prop changes with source signal. - // Needs special equality checking because the prop in the - // parent could be changed through `foo.bar = 'new value'`. - var ignore_next1 = false; - var ignore_next2 = false; - var did_update_to_defined = !should_set_default_value; - - var mount = true; - sync_effect(() => { - // Before if to ensure signal dependency is registered - var propagating_value = props[key]; - if (mount) { - mount = false; - return; - } - if (ignore_next1) { - ignore_next1 = false; - return; - } + var getter = () => { + var value = /** @type {V} */ (props[key]); + if (value !== undefined) initial = undefined; + return value === undefined ? /** @type {V} */ (initial) : value; + }; - if ( - // Ensure that updates from undefined to undefined are ignored - (did_update_to_defined || propagating_value !== undefined) && - not_equal(immutable, propagating_value, source_signal.v) - ) { - ignore_next2 = true; - did_update_to_defined = true; - // TODO figure out why we need it this way and the explain in a comment; - // some tests fail is we just do set_signal_value(source_signal, propagating_value) - untrack(() => set_signal_value(source_signal, propagating_value)); - } - }); + if ((flags & PROPS_IS_UPDATED) === 0) { + return getter; + } - if (setter !== undefined) { - var ignore_first = did_update_to_defined; - sync_effect(() => { - // Before if to ensure signal dependency is registered - const propagating_value = get(source_signal); - if (ignore_first) { - ignore_first = false; - return; - } - if (ignore_next2) { - ignore_next2 = false; - return; + if (setter) { + return function (/** @type {V} */ value) { + if (arguments.length === 1) { + /** @type {Function} */ (setter)(value); + return value; + } else { + return getter(); } - - ignore_next1 = true; - did_update_to_defined = true; - untrack(() => /** @type {Function} */ (setter)(propagating_value)); - }); + }; } - return /** @type {import('./types.js').Signal} */ (source_signal); -} + var source_signal = immutable ? source(value) : mutable_source(value); -/** - * @param {boolean} immutable - * @param {unknown} a - * @param {unknown} b - * @returns {boolean} - */ -function not_equal(immutable, a, b) { - return immutable ? immutable_not_equal(a, b) : safe_not_equal(a, b); -} + pre_effect(() => { + set(source_signal, getter()); + }); -/** - * @param {unknown} a - * @param {unknown} b - * @returns {boolean} - */ -function immutable_not_equal(a, b) { - // eslint-disable-next-line eqeqeq - return a != a ? b == b : a !== b; + return function (/** @type {V} */ value, mutation = false) { + if (arguments.length > 0) { + (mutation ? mutate : set)(source_signal, value); + return value; + } else { + return get(source_signal); + } + }; } /** 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 bd4c1a30a1..ac61fc9e6d 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,12 +6,12 @@ import * as $ from "svelte/internal"; export default function Svelte_element($$anchor, $$props) { $.push($$props, true); - let tag = $.prop_source($$props, "tag", 3, 'hr'); + let tag = $.prop($$props, "tag", 3, 'hr'); /* Init */ var fragment = $.comment($$anchor); var node = $.child_frag(fragment); - $.element(node, () => $.get(tag)); + $.element(node, () => tag()); $.close_frag($$anchor, fragment); $.pop(); -} \ No newline at end of file +}