Skip to content

Commit 01a2117

Browse files
chore: use proxy instead of signal in createRoot (#9799)
* use proxy instead of signal in createRoot * DRY * remove for now * lint * chore: use proxies instead of signals for spread/rest props (#9801) * use proxies instead of signals for spread/rest * fix some spread attribute stuff * remove is_signal calls * simplify some more * more * remove some unnecessary unwrapping * another * simplify * simplify * simplify * remove another MaybeSignal * more * remove more unwraps * code-golf, docs --------- Co-authored-by: Rich Harris <[email protected]> Co-authored-by: Simon Holthausen <[email protected]> * add missing jsdoc annotation --------- Co-authored-by: Rich Harris <[email protected]> Co-authored-by: Simon Holthausen <[email protected]>
1 parent 3c2e656 commit 01a2117

File tree

15 files changed

+219
-241
lines changed

15 files changed

+219
-241
lines changed

packages/svelte/src/compiler/phases/1-parse/state/element.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,7 @@ function read_attribute(parser) {
456456
expression,
457457
parent: null,
458458
metadata: {
459+
contains_call_expression: false,
459460
dynamic: false
460461
}
461462
};

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,10 @@ const common_visitors = {
906906
}
907907
},
908908
CallExpression(node, context) {
909-
if (context.state.expression?.type === 'ExpressionTag' && !is_known_safe_call(node, context)) {
909+
if (
910+
context.state.expression?.type === 'ExpressionTag' ||
911+
(context.state.expression?.type === 'SpreadAttribute' && !is_known_safe_call(node, context))
912+
) {
910913
context.state.expression.metadata.contains_call_expression = true;
911914
}
912915

packages/svelte/src/compiler/phases/3-transform/client/utils.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export function serialize_get_binding(node, state) {
6767

6868
if (binding.kind === 'prop' && binding.node.name === '$$props') {
6969
// Special case for $$props which only exists in the old world
70-
return b.call('$.unwrap', node);
70+
return node;
7171
}
7272

7373
if (
@@ -88,7 +88,6 @@ export function serialize_get_binding(node, state) {
8888
(!state.analysis.immutable || state.analysis.accessors || binding.reassigned)) ||
8989
binding.kind === 'derived' ||
9090
binding.kind === 'prop' ||
91-
binding.kind === 'rest_prop' ||
9291
binding.kind === 'legacy_reactive'
9392
) {
9493
return b.call('$.get', node);

packages/svelte/src/compiler/phases/3-transform/client/visitors/global.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const global_visitors = {
77
Identifier(node, { path, state }) {
88
if (is_reference(node, /** @type {import('estree').Node} */ (path.at(-1)))) {
99
if (node.name === '$$props') {
10-
return b.call('$.get', b.id('$$sanitized_props'));
10+
return b.id('$$sanitized_props');
1111
}
1212
return serialize_get_binding(node, state);
1313
}

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,20 @@ function serialize_inline_component(node, component_name, context) {
756756
}
757757
events[attribute.name].push(handler);
758758
} else if (attribute.type === 'SpreadAttribute') {
759-
props_and_spreads.push(/** @type {import('estree').Expression} */ (context.visit(attribute)));
759+
const expression = /** @type {import('estree').Expression} */ (context.visit(attribute));
760+
if (attribute.metadata.dynamic) {
761+
let value = expression;
762+
763+
if (attribute.metadata.contains_call_expression) {
764+
const id = b.id(context.state.scope.generate('spread_element'));
765+
context.state.init.push(b.var(id, b.call('$.derived', b.thunk(value))));
766+
value = b.call('$.get', id);
767+
}
768+
769+
props_and_spreads.push(b.thunk(value));
770+
} else {
771+
props_and_spreads.push(expression);
772+
}
760773
} else if (attribute.type === 'Attribute') {
761774
if (attribute.name.startsWith('--')) {
762775
custom_css_props.push(
@@ -895,7 +908,7 @@ function serialize_inline_component(node, component_name, context) {
895908
? b.object(/** @type {import('estree').Property[]} */ (props_and_spreads[0]) || [])
896909
: b.call(
897910
'$.spread_props',
898-
b.thunk(b.array(props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p))))
911+
...props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p))
899912
);
900913
/** @param {import('estree').Identifier} node_id */
901914
let fn = (node_id) =>
@@ -2764,8 +2777,8 @@ export const template_visitors = {
27642777
}
27652778
},
27662779
LetDirective(node, { state }) {
2767-
// let:x --> const x = $.derived(() => $.unwrap($$slotProps).x);
2768-
// let:x={{y, z}} --> const derived_x = $.derived(() => { const { y, z } = $.unwrap($$slotProps).x; return { y, z }));
2780+
// let:x --> const x = $.derived(() => $$slotProps.x);
2781+
// let:x={{y, z}} --> const derived_x = $.derived(() => { const { y, z } = $$slotProps.x; return { y, z }));
27692782
if (node.expression && node.expression.type !== 'Identifier') {
27702783
const name = state.scope.generate(node.name);
27712784
const bindings = state.scope.get_bindings(node);
@@ -2787,7 +2800,7 @@ export const template_visitors = {
27872800
b.object_pattern(node.expression.properties)
27882801
: // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine
27892802
b.array_pattern(node.expression.elements),
2790-
b.member(b.call('$.unwrap', b.id('$$slotProps')), b.id(node.name))
2803+
b.member(b.id('$$slotProps'), b.id(node.name))
27912804
),
27922805
b.return(b.object(bindings.map((binding) => b.init(binding.node.name, binding.node))))
27932806
])
@@ -2798,10 +2811,7 @@ export const template_visitors = {
27982811
const name = node.expression === null ? node.name : node.expression.name;
27992812
return b.const(
28002813
name,
2801-
b.call(
2802-
'$.derived',
2803-
b.thunk(b.member(b.call('$.unwrap', b.id('$$slotProps')), b.id(node.name)))
2804-
)
2814+
b.call('$.derived', b.thunk(b.member(b.id('$$slotProps'), b.id(node.name))))
28052815
);
28062816
}
28072817
},
@@ -2854,7 +2864,9 @@ export const template_visitors = {
28542864

28552865
for (const attribute of node.attributes) {
28562866
if (attribute.type === 'SpreadAttribute') {
2857-
spreads.push(/** @type {import('estree').Expression} */ (context.visit(attribute)));
2867+
spreads.push(
2868+
b.thunk(/** @type {import('estree').Expression} */ (context.visit(attribute)))
2869+
);
28582870
} else if (attribute.type === 'Attribute') {
28592871
const [, value] = serialize_attribute_value(attribute.value, context);
28602872
if (attribute.name === 'name') {
@@ -2873,7 +2885,7 @@ export const template_visitors = {
28732885
const props_expression =
28742886
spreads.length === 0
28752887
? b.object(props)
2876-
: b.call('$.spread_props', b.thunk(b.array([b.object(props), ...spreads])));
2888+
: b.call('$.spread_props', b.object(props), ...spreads);
28772889
const fallback =
28782890
node.fragment.nodes.length === 0
28792891
? b.literal(null)
@@ -2883,8 +2895,8 @@ export const template_visitors = {
28832895
);
28842896

28852897
const expression = is_default
2886-
? b.member(b.call('$.unwrap', b.id('$$props')), b.id('children'))
2887-
: b.member(b.member(b.call('$.unwrap', b.id('$$props')), b.id('$$slots')), name, true, true);
2898+
? b.member(b.id('$$props'), b.id('children'))
2899+
: b.member(b.member(b.id('$$props'), b.id('$$slots')), name, true, true);
28882900

28892901
const slot = b.call('$.slot', context.state.node, expression, props_expression, fallback);
28902902
context.state.init.push(b.stmt(slot));

packages/svelte/src/compiler/types/template.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ export interface SpreadAttribute extends BaseNode {
434434
type: 'SpreadAttribute';
435435
expression: Expression;
436436
metadata: {
437+
contains_call_expression: boolean;
437438
dynamic: boolean;
438439
};
439440
}

packages/svelte/src/internal/client/each.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ function reconcile_indexed_array(
243243
flags,
244244
apply_transitions
245245
) {
246-
var is_proxied_array = STATE_SYMBOL in array;
246+
var is_proxied_array = STATE_SYMBOL in array && /** @type {any} */ (array[STATE_SYMBOL]).i;
247247
var a_blocks = each_block.v;
248248
var active_transitions = each_block.s;
249249

@@ -351,7 +351,7 @@ function reconcile_tracked_array(
351351
) {
352352
var a_blocks = each_block.v;
353353
const is_computed_key = keys !== null;
354-
var is_proxied_array = STATE_SYMBOL in array;
354+
var is_proxied_array = STATE_SYMBOL in array && /** @type {any} */ (array[STATE_SYMBOL]).i;
355355
var active_transitions = each_block.s;
356356

357357
if (is_proxied_array) {

packages/svelte/src/internal/client/proxy/proxy.js

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
increment,
77
source,
88
updating_derived,
9-
UNINITIALIZED
9+
UNINITIALIZED,
10+
mutable_source
1011
} from '../runtime.js';
1112
import {
1213
define_property,
@@ -17,7 +18,7 @@ import {
1718
} from '../utils.js';
1819
import { READONLY_SYMBOL } from './readonly.js';
1920

20-
/** @typedef {{ s: Map<string | symbol, import('../types.js').SourceSignal<any>>; v: import('../types.js').SourceSignal<number>; a: boolean }} Metadata */
21+
/** @typedef {{ s: Map<string | symbol, import('../types.js').SourceSignal<any>>; v: import('../types.js').SourceSignal<number>; a: boolean, i: boolean }} Metadata */
2122
/** @typedef {Record<string | symbol, any> & { [STATE_SYMBOL]: Metadata }} StateObject */
2223

2324
export const STATE_SYMBOL = Symbol('$state');
@@ -30,15 +31,16 @@ const is_frozen = Object.isFrozen;
3031
/**
3132
* @template {StateObject} T
3233
* @param {T} value
34+
* @param {boolean} [immutable]
3335
* @returns {T}
3436
*/
35-
export function proxy(value) {
37+
export function proxy(value, immutable = true) {
3638
if (typeof value === 'object' && value != null && !is_frozen(value) && !(STATE_SYMBOL in value)) {
3739
const prototype = get_prototype_of(value);
3840

3941
// TODO handle Map and Set as well
4042
if (prototype === object_prototype || prototype === array_prototype) {
41-
define_property(value, STATE_SYMBOL, { value: init(value), writable: false });
43+
define_property(value, STATE_SYMBOL, { value: init(value, immutable), writable: false });
4244

4345
// @ts-expect-error not sure how to fix this
4446
return new Proxy(value, handler);
@@ -100,13 +102,15 @@ export function unstate(value) {
100102

101103
/**
102104
* @param {StateObject} value
105+
* @param {boolean} immutable
103106
* @returns {Metadata}
104107
*/
105-
function init(value) {
108+
function init(value, immutable) {
106109
return {
107110
s: new Map(),
108111
v: source(0),
109-
a: is_array(value)
112+
a: is_array(value),
113+
i: immutable
110114
};
111115
}
112116

@@ -117,7 +121,7 @@ const handler = {
117121
const metadata = target[STATE_SYMBOL];
118122

119123
const s = metadata.s.get(prop);
120-
if (s !== undefined) set(s, proxy(descriptor.value));
124+
if (s !== undefined) set(s, proxy(descriptor.value, metadata.i));
121125
}
122126

123127
return Reflect.defineProperty(target, prop, descriptor);
@@ -147,7 +151,7 @@ const handler = {
147151
(effect_active() || updating_derived) &&
148152
(!(prop in target) || get_descriptor(target, prop)?.writable)
149153
) {
150-
s = source(proxy(target[prop]));
154+
s = (metadata.i ? source : mutable_source)(proxy(target[prop], metadata.i));
151155
metadata.s.set(prop, s);
152156
}
153157

@@ -179,7 +183,9 @@ const handler = {
179183
let s = metadata.s.get(prop);
180184
if (s !== undefined || (effect_active() && (!has || get_descriptor(target, prop)?.writable))) {
181185
if (s === undefined) {
182-
s = source(has ? proxy(target[prop]) : UNINITIALIZED);
186+
s = (metadata.i ? source : mutable_source)(
187+
has ? proxy(target[prop], metadata.i) : UNINITIALIZED
188+
);
183189
metadata.s.set(prop, s);
184190
}
185191
const value = get(s);
@@ -197,7 +203,7 @@ const handler = {
197203
}
198204
const metadata = target[STATE_SYMBOL];
199205
const s = metadata.s.get(prop);
200-
if (s !== undefined) set(s, proxy(value));
206+
if (s !== undefined) set(s, proxy(value, metadata.i));
201207
const is_array = metadata.a;
202208
const not_has = !(prop in target);
203209

@@ -234,6 +240,12 @@ const handler = {
234240
}
235241
};
236242

243+
/** @param {any} object */
244+
export function observe(object) {
245+
const metadata = object[STATE_SYMBOL];
246+
if (metadata) get(metadata.v);
247+
}
248+
237249
if (DEV) {
238250
handler.setPrototypeOf = () => {
239251
throw new Error('Cannot set prototype of $state object');

0 commit comments

Comments
 (0)