use proxies instead of signals for spread/rest

pull/9801/head
Rich Harris 3 years ago
parent e6ce68d7a8
commit 702aff80e5

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

@ -7,7 +7,7 @@ export const global_visitors = {
Identifier(node, { path, state }) { Identifier(node, { path, state }) {
if (is_reference(node, /** @type {import('estree').Node} */ (path.at(-1)))) { if (is_reference(node, /** @type {import('estree').Node} */ (path.at(-1)))) {
if (node.name === '$$props') { if (node.name === '$$props') {
return b.call('$.get', b.id('$$sanitized_props')); return b.id('$$sanitized_props');
} }
return serialize_get_binding(node, state); return serialize_get_binding(node, state);
} }

@ -895,7 +895,7 @@ function serialize_inline_component(node, component_name, context) {
? b.object(/** @type {import('estree').Property[]} */ (props_and_spreads[0]) || []) ? b.object(/** @type {import('estree').Property[]} */ (props_and_spreads[0]) || [])
: b.call( : b.call(
'$.spread_props', '$.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 */ /** @param {import('estree').Identifier} node_id */
let fn = (node_id) => let fn = (node_id) =>
@ -2854,7 +2854,9 @@ export const template_visitors = {
for (const attribute of node.attributes) { for (const attribute of node.attributes) {
if (attribute.type === 'SpreadAttribute') { 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') { } else if (attribute.type === 'Attribute') {
const [, value] = serialize_attribute_value(attribute.value, context); const [, value] = serialize_attribute_value(attribute.value, context);
if (attribute.name === 'name') { if (attribute.name === 'name') {
@ -2873,7 +2875,7 @@ export const template_visitors = {
const props_expression = const props_expression =
spreads.length === 0 spreads.length === 0
? b.object(props) ? 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 = const fallback =
node.fragment.nodes.length === 0 node.fragment.nodes.length === 0
? b.literal(null) ? b.literal(null)

@ -2528,71 +2528,93 @@ export function spread_dynamic_element_attributes(node, prev, attrs, css_hash) {
} }
/** /**
* @param {import('./types.js').Signal<Record<string, unknown>> | Record<string, unknown>} props_signal * @type {ProxyHandler<{ props: Record<string | symbol, unknown>, exclude: Array<string | symbol> }>}}
*/
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<string | symbol>} */
const keys = [];
for (let key in target.props) {
if (!target.exclude.includes(key)) keys.push(key);
}
return keys;
}
};
/**
* @param {import('./types.js').Signal<Record<string, unknown>> | Record<string, unknown>} props
* @param {string[]} rest * @param {string[]} rest
* @returns {Record<string, unknown>} * @returns {Record<string, unknown>}
*/ */
export function rest_props(props_signal, rest) { export function rest_props(props, rest) {
return derived(() => { return new Proxy({ props, exclude: rest }, rest_props_handler);
var props = unwrap(props_signal); }
observe(props);
/** @type {Record<string, unknown>} */
var rest_props = {};
for (const key in props) { /**
if (rest.includes(key)) continue; * @template {Record<string | symbol, unknown>} T
* @type {ProxyHandler<{ props: Array<T | (() => 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<string | symbol>} */
const keys = [];
define_property(rest_props, key, { for (let p of target.props) {
get: () => props[key], if (typeof p === 'function') p = p();
enumerable for (const key in p) {
}); if (!keys.includes(key)) keys.push(key);
}
} }
return rest_props; return keys;
}); }
} };
/** /**
* @param {Record<string, unknown>[] | (() => Record<string, unknown>[])} props * @param {Array<Record<string, unknown> | (() => Record<string, unknown>)>} props
* @returns {any} * @returns {any}
*/ */
export function spread_props(props) { export function spread_props(...props) {
if (typeof props === 'function') { return new Proxy({ props }, spread_props_handler);
return derived(() => {
return spread_props(props());
});
}
/** @type {Record<string, unknown>} */
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;
} }
/** /**

@ -1426,6 +1426,10 @@ export function prop_source(props_obj, key, flags, default_value) {
const immutable = (flags & PROPS_IS_IMMUTABLE) !== 0; const immutable = (flags & PROPS_IS_IMMUTABLE) !== 0;
const runes = (flags & PROPS_IS_RUNES) !== 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 props = is_signal(props_obj) ? get(props_obj) : props_obj;
const update_bound_prop = get_descriptor(props, key)?.set; const update_bound_prop = get_descriptor(props, key)?.set;
let value = props[key]; let value = props[key];

Loading…
Cancel
Save