diff --git a/.changeset/nine-laws-rush.md b/.changeset/nine-laws-rush.md new file mode 100644 index 0000000000..e0a0fc15a0 --- /dev/null +++ b/.changeset/nine-laws-rush.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: simplify set calls for proxyable values diff --git a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts index 63fe3223cf..243e1c64a3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts +++ b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts @@ -30,7 +30,7 @@ export interface ClientTransformState extends TransformState { /** turn `foo` into e.g. `$.get(foo)` */ read: (id: Identifier) => Expression; /** turn `foo = bar` into e.g. `$.set(foo, bar)` */ - assign?: (node: Identifier, value: Expression) => Expression; + assign?: (node: Identifier, value: Expression, proxy?: boolean) => Expression; /** turn `foo.bar = baz` into e.g. `$.mutate(foo, $.get(foo).bar = baz);` */ mutate?: (node: Identifier, mutation: AssignmentExpression | UpdateExpression) => Expression; /** turn `foo++` into e.g. `$.update(foo)` */ 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 421118cf68..28e3fabb19 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -45,14 +45,6 @@ export function build_getter(node, state) { return node; } -/** - * @param {Expression} value - * @param {Expression} previous - */ -export function build_proxy_reassignment(value, previous) { - return dev ? b.call('$.proxy', value, b.null, previous) : b.call('$.proxy', value); -} - /** * @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} node * @param {ComponentContext} context diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js index a8c615af93..150c56e166 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js @@ -1,5 +1,4 @@ -/** @import { Location } from 'locate-character' */ -/** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, Literal, MemberExpression, Pattern } from 'estree' */ +/** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, Pattern } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { Context } from '../types.js' */ import * as b from '../../../../utils/builders.js'; @@ -8,8 +7,8 @@ import { get_attribute_expression, is_event_attribute } from '../../../../utils/ast.js'; -import { dev, filename, is_ignored, locate_node, locator } from '../../../../state.js'; -import { build_proxy_reassignment, should_proxy } from '../utils.js'; +import { dev, is_ignored, locate_node } from '../../../../state.js'; +import { should_proxy } from '../utils.js'; import { visit_assignment_expression } from '../../shared/assignments.js'; /** @@ -65,21 +64,12 @@ function build_assignment(operator, left, right, context) { context.visit(build_assignment_value(operator, left, right)) ); - if ( + const needs_proxy = private_state.kind === 'state' && is_non_coercive_operator(operator) && - should_proxy(value, context.state.scope) - ) { - value = build_proxy_reassignment(value, b.member(b.this, private_state.id)); - } - - if (context.state.in_constructor) { - // inside the constructor, we can assign to `this.#foo.v` rather than using `$.set`, - // since nothing is tracking the signal at this point - return b.assignment(operator, /** @type {Pattern} */ (context.visit(left)), value); - } + should_proxy(value, context.state.scope); - return b.call('$.set', left, value); + return b.call('$.set', left, value, needs_proxy && b.true); } } @@ -113,20 +103,18 @@ function build_assignment(operator, left, right, context) { context.visit(build_assignment_value(operator, left, right)) ); - if ( + return transform.assign( + object, + value, !is_primitive && - binding.kind !== 'prop' && - binding.kind !== 'bindable_prop' && - binding.kind !== 'raw_state' && - binding.kind !== 'store_sub' && - context.state.analysis.runes && - should_proxy(right, context.state.scope) && - is_non_coercive_operator(operator) - ) { - value = build_proxy_reassignment(value, object); - } - - return transform.assign(object, value); + binding.kind !== 'prop' && + binding.kind !== 'bindable_prop' && + binding.kind !== 'raw_state' && + binding.kind !== 'store_sub' && + context.state.analysis.runes && + should_proxy(right, context.state.scope) && + is_non_coercive_operator(operator) + ); } // mutation diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js index ed800e5226..5787b590a8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js @@ -5,7 +5,7 @@ import { dev, is_ignored } from '../../../../state.js'; import * as b from '../../../../utils/builders.js'; import { regex_invalid_identifier_chars } from '../../../patterns.js'; import { get_rune } from '../../../scope.js'; -import { build_proxy_reassignment, should_proxy } from '../utils.js'; +import { should_proxy } from '../utils.js'; /** * @param {ClassBody} node @@ -142,29 +142,20 @@ export function ClassBody(node, context) { // get foo() { return this.#foo; } body.push(b.method('get', definition.key, [], [b.return(b.call('$.get', member))])); - if (field.kind === 'state') { + if (field.kind === 'state' || field.kind === 'raw_state') { // set foo(value) { this.#foo = value; } const value = b.id('value'); - const prev = b.member(b.this, field.id); body.push( b.method( 'set', definition.key, [value], - [b.stmt(b.call('$.set', member, build_proxy_reassignment(value, prev)))] + [b.stmt(b.call('$.set', member, value, field.kind === 'state' && b.true))] ) ); } - if (field.kind === 'raw_state') { - // set foo(value) { this.#foo = value; } - const value = b.id('value'); - body.push( - b.method('set', definition.key, [value], [b.stmt(b.call('$.set', member, value))]) - ); - } - if (dev && (field.kind === 'derived' || field.kind === 'derived_by')) { body.push( b.method( diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/declarations.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/declarations.js index 0bd8c352f6..a13ecfed2c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/declarations.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/declarations.js @@ -24,8 +24,8 @@ export function add_state_transformers(context) { ) { context.state.transform[name] = { read: binding.declaration_kind === 'var' ? (node) => b.call('$.safe_get', node) : get_value, - assign: (node, value) => { - let call = b.call('$.set', node, value); + assign: (node, value, proxy = false) => { + let call = b.call('$.set', node, value, proxy && b.true); if (context.state.scope.get(`$${node.name}`)?.kind === 'store_sub') { call = b.call('$.store_unsub', call, b.literal(`$${node.name}`), b.id('$$stores')); diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 49584e8626..92508945c9 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -33,6 +33,7 @@ import * as e from '../errors.js'; import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js'; import { get_stack } from '../dev/tracing.js'; import { component_context, is_runes } from '../context.js'; +import { proxy } from '../proxy.js'; export let inspect_effects = new Set(); export const old_values = new Map(); @@ -143,9 +144,10 @@ export function mutate(source, value) { * @template V * @param {Source<V>} source * @param {V} value + * @param {boolean} [should_proxy] * @returns {V} */ -export function set(source, value) { +export function set(source, value, should_proxy = false) { if ( active_reaction !== null && !untracking && @@ -158,7 +160,9 @@ export function set(source, value) { e.state_unsafe_mutation(); } - return internal_set(source, value); + let new_value = should_proxy ? proxy(value, null, source) : value; + + return internal_set(source, new_value); } /** diff --git a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js index fa990b33ee..390e86a351 100644 --- a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js @@ -23,7 +23,7 @@ export default function Bind_component_snippet($$anchor) { return $.get(value); }, set value($$value) { - $.set(value, $.proxy($$value)); + $.set(value, $$value, true); } }); diff --git a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js index 2898f31a6f..2133974176 100644 --- a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js @@ -12,14 +12,14 @@ export default function Class_state_field_constructor_assignment($$anchor, $$pro } set a(value) { - $.set(this.#a, $.proxy(value)); + $.set(this.#a, value, true); } #b = $.state(); constructor() { this.a = 1; - this.#b.v = 2; + $.set(this.#b, 2); } } diff --git a/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/client/index.svelte.js index 9651713c52..47f297bce9 100644 --- a/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/client/index.svelte.js @@ -8,8 +8,8 @@ let d = 4; export function update(array) { ( - $.set(a, $.proxy(array[0])), - $.set(b, $.proxy(array[1])) + $.set(a, array[0], true), + $.set(b, array[1], true) ); [c, d] = array; diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js index c545608bca..762a23754c 100644 --- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js @@ -13,7 +13,7 @@ export default function Function_prop_no_getter($$anchor) { Button($$anchor, { onmousedown: () => $.set(count, $.get(count) + 1), onmouseup, - onmouseenter: () => $.set(count, $.proxy(plusOne($.get(count)))), + onmouseenter: () => $.set(count, plusOne($.get(count)), true), children: ($$anchor, $$slotProps) => { $.next();