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)) ||
binding.kind === 'derived' ||
binding.kind === 'prop' ||
binding.kind === 'rest_prop' ||
binding.kind === 'legacy_reactive'
) {
return b.call('$.get', node);

@ -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);
}

@ -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)

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

@ -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];

Loading…
Cancel
Save