Skip to content

chore: use proxy instead of signal in createRoot #9799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ function read_attribute(parser) {
expression,
parent: null,
metadata: {
contains_call_expression: false,
dynamic: false
}
};
Expand Down
5 changes: 4 additions & 1 deletion packages/svelte/src/compiler/phases/2-analyze/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,10 @@ const common_visitors = {
}
},
CallExpression(node, context) {
if (context.state.expression?.type === 'ExpressionTag' && !is_known_safe_call(node, context)) {
if (
context.state.expression?.type === 'ExpressionTag' ||
(context.state.expression?.type === 'SpreadAttribute' && !is_known_safe_call(node, context))
) {
context.state.expression.metadata.contains_call_expression = true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function serialize_get_binding(node, state) {

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

if (
Expand All @@ -88,7 +88,6 @@ export function serialize_get_binding(node, state) {
(!state.analysis.immutable || state.analysis.accessors || binding.reassigned)) ||
binding.kind === 'derived' ||
binding.kind === 'prop' ||
binding.kind === 'rest_prop' ||
binding.kind === 'legacy_reactive'
) {
return b.call('$.get', node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const global_visitors = {
Identifier(node, { path, state }) {
if (is_reference(node, /** @type {import('estree').Node} */ (path.at(-1)))) {
if (node.name === '$$props') {
return b.call('$.get', b.id('$$sanitized_props'));
return b.id('$$sanitized_props');
}
return serialize_get_binding(node, state);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,20 @@ function serialize_inline_component(node, component_name, context) {
}
events[attribute.name].push(handler);
} else if (attribute.type === 'SpreadAttribute') {
props_and_spreads.push(/** @type {import('estree').Expression} */ (context.visit(attribute)));
const expression = /** @type {import('estree').Expression} */ (context.visit(attribute));
if (attribute.metadata.dynamic) {
let value = expression;

if (attribute.metadata.contains_call_expression) {
const id = b.id(context.state.scope.generate('spread_element'));
context.state.init.push(b.var(id, b.call('$.derived', b.thunk(value))));
value = b.call('$.get', id);
}

props_and_spreads.push(b.thunk(value));
} else {
props_and_spreads.push(expression);
}
} else if (attribute.type === 'Attribute') {
if (attribute.name.startsWith('--')) {
custom_css_props.push(
Expand Down Expand Up @@ -895,7 +908,7 @@ function serialize_inline_component(node, component_name, context) {
? b.object(/** @type {import('estree').Property[]} */ (props_and_spreads[0]) || [])
: b.call(
'$.spread_props',
b.thunk(b.array(props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p))))
...props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p))
);
/** @param {import('estree').Identifier} node_id */
let fn = (node_id) =>
Expand Down Expand Up @@ -2764,8 +2777,8 @@ export const template_visitors = {
}
},
LetDirective(node, { state }) {
// let:x --> const x = $.derived(() => $.unwrap($$slotProps).x);
// let:x={{y, z}} --> const derived_x = $.derived(() => { const { y, z } = $.unwrap($$slotProps).x; return { y, z }));
// let:x --> const x = $.derived(() => $$slotProps.x);
// let:x={{y, z}} --> const derived_x = $.derived(() => { const { y, z } = $$slotProps.x; return { y, z }));
if (node.expression && node.expression.type !== 'Identifier') {
const name = state.scope.generate(node.name);
const bindings = state.scope.get_bindings(node);
Expand All @@ -2787,7 +2800,7 @@ export const template_visitors = {
b.object_pattern(node.expression.properties)
: // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine
b.array_pattern(node.expression.elements),
b.member(b.call('$.unwrap', b.id('$$slotProps')), b.id(node.name))
b.member(b.id('$$slotProps'), b.id(node.name))
),
b.return(b.object(bindings.map((binding) => b.init(binding.node.name, binding.node))))
])
Expand All @@ -2798,10 +2811,7 @@ export const template_visitors = {
const name = node.expression === null ? node.name : node.expression.name;
return b.const(
name,
b.call(
'$.derived',
b.thunk(b.member(b.call('$.unwrap', b.id('$$slotProps')), b.id(node.name)))
)
b.call('$.derived', b.thunk(b.member(b.id('$$slotProps'), b.id(node.name))))
);
}
},
Expand Down Expand Up @@ -2854,7 +2864,9 @@ export const template_visitors = {

for (const attribute of node.attributes) {
if (attribute.type === 'SpreadAttribute') {
spreads.push(/** @type {import('estree').Expression} */ (context.visit(attribute)));
spreads.push(
b.thunk(/** @type {import('estree').Expression} */ (context.visit(attribute)))
);
} else if (attribute.type === 'Attribute') {
const [, value] = serialize_attribute_value(attribute.value, context);
if (attribute.name === 'name') {
Expand All @@ -2873,7 +2885,7 @@ export const template_visitors = {
const props_expression =
spreads.length === 0
? b.object(props)
: b.call('$.spread_props', b.thunk(b.array([b.object(props), ...spreads])));
: b.call('$.spread_props', b.object(props), ...spreads);
const fallback =
node.fragment.nodes.length === 0
? b.literal(null)
Expand All @@ -2883,8 +2895,8 @@ export const template_visitors = {
);

const expression = is_default
? b.member(b.call('$.unwrap', b.id('$$props')), b.id('children'))
: b.member(b.member(b.call('$.unwrap', b.id('$$props')), b.id('$$slots')), name, true, true);
? b.member(b.id('$$props'), b.id('children'))
: b.member(b.member(b.id('$$props'), b.id('$$slots')), name, true, true);

const slot = b.call('$.slot', context.state.node, expression, props_expression, fallback);
context.state.init.push(b.stmt(slot));
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/src/compiler/types/template.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ export interface SpreadAttribute extends BaseNode {
type: 'SpreadAttribute';
expression: Expression;
metadata: {
contains_call_expression: boolean;
dynamic: boolean;
};
}
Expand Down
4 changes: 2 additions & 2 deletions packages/svelte/src/internal/client/each.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ function reconcile_indexed_array(
flags,
apply_transitions
) {
var is_proxied_array = STATE_SYMBOL in array;
var is_proxied_array = STATE_SYMBOL in array && /** @type {any} */ (array[STATE_SYMBOL]).i;
var a_blocks = each_block.v;
var active_transitions = each_block.s;

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

if (is_proxied_array) {
Expand Down
32 changes: 22 additions & 10 deletions packages/svelte/src/internal/client/proxy/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
increment,
source,
updating_derived,
UNINITIALIZED
UNINITIALIZED,
mutable_source
} from '../runtime.js';
import {
define_property,
Expand All @@ -17,7 +18,7 @@ import {
} from '../utils.js';
import { READONLY_SYMBOL } from './readonly.js';

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

export const STATE_SYMBOL = Symbol('$state');
Expand All @@ -30,15 +31,16 @@ const is_frozen = Object.isFrozen;
/**
* @template {StateObject} T
* @param {T} value
* @param {boolean} [immutable]
* @returns {T}
*/
export function proxy(value) {
export function proxy(value, immutable = true) {
if (typeof value === 'object' && value != null && !is_frozen(value) && !(STATE_SYMBOL in value)) {
const prototype = get_prototype_of(value);

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

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

/**
* @param {StateObject} value
* @param {boolean} immutable
* @returns {Metadata}
*/
function init(value) {
function init(value, immutable) {
return {
s: new Map(),
v: source(0),
a: is_array(value)
a: is_array(value),
i: immutable
};
}

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

const s = metadata.s.get(prop);
if (s !== undefined) set(s, proxy(descriptor.value));
if (s !== undefined) set(s, proxy(descriptor.value, metadata.i));
}

return Reflect.defineProperty(target, prop, descriptor);
Expand Down Expand Up @@ -147,7 +151,7 @@ const handler = {
(effect_active() || updating_derived) &&
(!(prop in target) || get_descriptor(target, prop)?.writable)
) {
s = source(proxy(target[prop]));
s = (metadata.i ? source : mutable_source)(proxy(target[prop], metadata.i));
metadata.s.set(prop, s);
}

Expand Down Expand Up @@ -179,7 +183,9 @@ const handler = {
let s = metadata.s.get(prop);
if (s !== undefined || (effect_active() && (!has || get_descriptor(target, prop)?.writable))) {
if (s === undefined) {
s = source(has ? proxy(target[prop]) : UNINITIALIZED);
s = (metadata.i ? source : mutable_source)(
has ? proxy(target[prop], metadata.i) : UNINITIALIZED
);
metadata.s.set(prop, s);
}
const value = get(s);
Expand All @@ -197,7 +203,7 @@ const handler = {
}
const metadata = target[STATE_SYMBOL];
const s = metadata.s.get(prop);
if (s !== undefined) set(s, proxy(value));
if (s !== undefined) set(s, proxy(value, metadata.i));
const is_array = metadata.a;
const not_has = !(prop in target);

Expand Down Expand Up @@ -234,6 +240,12 @@ const handler = {
}
};

/** @param {any} object */
export function observe(object) {
const metadata = object[STATE_SYMBOL];
if (metadata) get(metadata.v);
}

if (DEV) {
handler.setPrototypeOf = () => {
throw new Error('Cannot set prototype of $state object');
Expand Down
Loading