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 e9d3b519c7..8c5534f66d 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 @@ -628,6 +628,9 @@ function serialize_inline_component(node, component_name, context) { /** @type {Array} */ const props_and_spreads = []; + /** @type {(import('estree').Expression | null)[]} */ + const spreads_keys = []; + /** @type {import('estree').ExpressionStatement[]} */ const lets = []; @@ -661,6 +664,7 @@ function serialize_inline_component(node, component_name, context) { props.push(prop); if (!current_is_props) { props_and_spreads.push(props); + spreads_keys.push(null); } } for (const attribute of node.attributes) { @@ -685,8 +689,19 @@ function serialize_inline_component(node, component_name, context) { } props_and_spreads.push(b.thunk(value)); + const keys = b.call( + '$.derived', + b.thunk( + b.call( + b.member(b.call('Object.keys', b.logical('??', value, b.object([]))), b.id('join')), + b.literal(',') + ) + ) + ); + spreads_keys.push(b.thunk(keys)); } else { props_and_spreads.push(expression); + spreads_keys.push(null); } } else if (attribute.type === 'Attribute') { if (attribute.name.startsWith('--')) { @@ -843,7 +858,8 @@ function serialize_inline_component(node, component_name, context) { ? b.object(/** @type {import('estree').Property[]} */ (props_and_spreads[0]) || []) : b.call( '$.spread_props', - ...props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p)) + ...props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p)), + b.array(spreads_keys) ); /** @param {import('estree').Identifier} node_id */ let fn = (node_id) => @@ -2929,6 +2945,9 @@ export const template_visitors = { /** @type {import('estree').Expression[]} */ const spreads = []; + /** @type {(import('estree').Expression | null)[]} */ + const spreads_keys = []; + /** @type {import('estree').ExpressionStatement[]} */ const lets = []; @@ -2939,9 +2958,18 @@ export const template_visitors = { for (const attribute of node.attributes) { if (attribute.type === 'SpreadAttribute') { - spreads.push( - b.thunk(/** @type {import('estree').Expression} */ (context.visit(attribute))) + const value = /** @type {import('estree').Expression} */ (context.visit(attribute)); + const keys = b.call( + '$.derived', + b.thunk( + b.call( + b.member(b.call('Object.keys', b.logical('??', value, b.object([]))), b.id('join')), + b.literal(',') + ) + ) ); + spreads.push(b.thunk(value)); + spreads_keys.push(b.thunk(keys)); } else if (attribute.type === 'Attribute') { const [, value] = serialize_attribute_value(attribute.value, context); if (attribute.name === 'name') { @@ -2950,8 +2978,10 @@ export const template_visitors = { } else if (attribute.name !== 'slot') { if (attribute.metadata.dynamic) { props.push(b.get(attribute.name, [b.return(value)])); + spreads_keys.push(null); } else { props.push(b.init(attribute.name, value)); + spreads_keys.push(null); } } } else if (attribute.type === 'LetDirective') { @@ -2965,7 +2995,7 @@ export const template_visitors = { const props_expression = spreads.length === 0 ? b.object(props) - : b.call('$.spread_props', b.object(props), ...spreads); + : b.call('$.spread_props', b.object(props), ...spreads, b.array(spreads_keys)); const fallback = node.fragment.nodes.length === 0 ? b.literal(null) diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index aab7f3d619..a480a6d53e 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -87,7 +87,7 @@ export function rest_props(props, exclude, name) { * that looks like `() => { dynamic: props }, { static: prop }, ..` and wraps * them so that the whole thing is passed to the component as the `$$props` argument. * @template {Record} T - * @type {ProxyHandler<{ props: Array T)> }>}} + * @type {ProxyHandler<{ props: (Array T)>), keys: T }>}} */ const spread_props_handler = { get(target, key) { @@ -95,7 +95,6 @@ const spread_props_handler = { while (i--) { let p = target.props[i]; let obj = p; - debugger; // in case the prop is the spread prop calling the function // will track that state as a dep even if the requested key // is not in it. to avoid that we call the function in untrack @@ -104,6 +103,8 @@ const spread_props_handler = { untrack(() => { if (is_function(p)) obj = p(); }); + const keys_function = target.keys[i]; + if (keys_function && keys_function instanceof Function) get(keys_function()); if (typeof obj === 'object' && obj !== null && key in obj) { if (is_function(p)) p = p(); return p[key]; @@ -123,6 +124,8 @@ const spread_props_handler = { untrack(() => { if (is_function(p)) obj = p(); }); + const keys_function = target.keys[i]; + if (keys_function && keys_function instanceof Function) get(keys_function()); if (typeof obj === 'object' && obj !== null && key in obj) { if (is_function(p)) p = p(); return get_descriptor(p, key); @@ -131,17 +134,8 @@ const spread_props_handler = { }, has(target, key) { for (let p of target.props) { - let obj = p; - // in case the prop is the spread prop calling the function - // will track that state as a dep even if the requested key - // is not in it. to avoid that we call the function in untrack - // and check on the object. If it's there we call the function again - // to track and then access the key - untrack(() => { - if (is_function(p)) obj = p(); - }); - if (key in obj) { - if (is_function(p)) p(); + if (is_function(p)) p = p(); + if (key in p) { return true; } } @@ -153,20 +147,9 @@ const spread_props_handler = { const keys = []; for (let p of target.props) { - let obj = p; - // in case the prop is the spread prop calling the function - // will track that state as a dep even if the requested key - // is not in it. to avoid that we call the function in untrack - // and check on the object. If it's there we call the function again - // to track and then access the key - untrack(() => { - if (is_function(p)) obj = p(); - }); - for (const key in obj) { - if (!keys.includes(key)) { - if (is_function(p)) obj = p(); - keys.push(key); - } + if (is_function(p)) p = p(); + for (const key in p) { + if (!keys.includes(key)) keys.push(key); } } @@ -179,7 +162,8 @@ const spread_props_handler = { * @returns {any} */ export function spread_props(...props) { - return new Proxy({ props }, spread_props_handler); + const keys = props.pop(); + return new Proxy({ props, keys }, spread_props_handler); } /**