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 35e200b06e..dce8e0cca3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -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); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/global.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/global.js index 4b053e0566..51032cdd5f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/global.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/global.js @@ -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); } 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 fb43ec654b..d621d63492 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 @@ -895,7 +895,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) : b.thunk(p))) ); /** @param {import('estree').Identifier} node_id */ let fn = (node_id) => @@ -2854,7 +2854,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') { @@ -2873,7 +2875,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) diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 3884aa0685..b83c430449 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -2528,71 +2528,93 @@ export function spread_dynamic_element_attributes(node, prev, attrs, css_hash) { } /** - * @param {import('./types.js').Signal> | Record} props_signal + * @type {ProxyHandler<{ props: Record, exclude: Array }>}} + */ +const rest_props_handler = { + get(target, key) { + if (target.exclude.includes(key)) return; + return target.props[key]; + }, + getOwnPropertyDescriptor(target, key) { + if (target.exclude.includes(key)) return; + if (key in target.props) { + return { + enumerable: true, + configurable: true, + value: target.props[key] + }; + } + }, + has(target, key) { + if (target.exclude.includes(key)) return false; + return key in target.props; + }, + ownKeys(target) { + /** @type {Array} */ + const keys = []; + + for (let key in target.props) { + if (!target.exclude.includes(key)) keys.push(key); + } + + return keys; + } +}; + +/** + * @param {import('./types.js').Signal> | Record} props * @param {string[]} rest * @returns {Record} */ -export function rest_props(props_signal, rest) { - return derived(() => { - var props = unwrap(props_signal); - - observe(props); - - /** @type {Record} */ - var rest_props = {}; +export function rest_props(props, rest) { + return new Proxy({ props, exclude: rest }, rest_props_handler); +} - for (const key in props) { - if (rest.includes(key)) continue; +/** + * @template {Record} T + * @type {ProxyHandler<{ props: Array T)> }>}} + */ +const spread_props_handler = { + get(target, key) { + let i = target.props.length; + while (i--) { + let p = target.props[i]; + if (typeof p === 'function') p = p(); + if (key in p) return p[key]; + } + }, + getOwnPropertyDescriptor() { + return { enumerable: true, configurable: true }; + }, + has(target, key) { + for (let p of target.props) { + if (typeof p === 'function') p = p(); + if (key in p) return true; + } - const { enumerable } = /** @type {PropertyDescriptor} */ (get_descriptor(props, key)); + return false; + }, + ownKeys(target) { + /** @type {Array} */ + const keys = []; - define_property(rest_props, key, { - get: () => props[key], - enumerable - }); + for (let p of target.props) { + if (typeof p === 'function') p = p(); + for (const key in p) { + if (!keys.includes(key)) keys.push(key); + } } - return rest_props; - }); -} + return keys; + } +}; /** - * @param {Record[] | (() => Record[])} props + * @param {Array | (() => Record)>} props * @returns {any} */ -export function spread_props(props) { - if (typeof props === 'function') { - return derived(() => { - return spread_props(props()); - }); - } - - /** @type {Record} */ - const merged_props = {}; - let key; - for (let i = 0; i < props.length; i++) { - const obj = props[i]; - for (key in obj) { - const desc = /** @type {PropertyDescriptor} */ (get_descriptor(obj, key)); - const getter = desc.get; - if (getter !== undefined) { - define_property(merged_props, key, { - enumerable: true, - configurable: true, - get: getter - }); - } else if (desc.get !== undefined) { - merged_props[key] = obj[key]; - } else { - define_property(merged_props, key, { - enumerable: true, - configurable: true, - value: obj[key] - }); - } - } - } - return merged_props; +export function spread_props(...props) { + return new Proxy({ props }, spread_props_handler); } /** diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 9a0fe975ae..a4e2b2cea3 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1426,6 +1426,10 @@ export function prop_source(props_obj, key, flags, default_value) { const immutable = (flags & PROPS_IS_IMMUTABLE) !== 0; const runes = (flags & PROPS_IS_RUNES) !== 0; + if (is_signal(props_obj)) { + // throw new Error('nope'); + } + const props = is_signal(props_obj) ? get(props_obj) : props_obj; const update_bound_prop = get_descriptor(props, key)?.set; let value = props[key];