chore: use proxy instead of signal in createRoot (#9799)

* use proxy instead of signal in createRoot

* DRY

* remove for now

* lint

* chore: use proxies instead of signals for spread/rest props (#9801)

* use proxies instead of signals for spread/rest

* fix some spread attribute stuff

* remove is_signal calls

* simplify some more

* more

* remove some unnecessary unwrapping

* another

* simplify

* simplify

* simplify

* remove another MaybeSignal

* more

* remove more unwraps

* code-golf, docs

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>

* add missing jsdoc annotation

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
pull/9814/head
Rich Harris 10 months ago committed by GitHub
parent 3c2e656187
commit 01a2117330
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -456,6 +456,7 @@ function read_attribute(parser) {
expression,
parent: null,
metadata: {
contains_call_expression: false,
dynamic: false
}
};

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

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

@ -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(
@ -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) =>
@ -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);
@ -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))))
])
@ -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))))
);
}
},
@ -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') {
@ -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)
@ -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));

@ -434,6 +434,7 @@ export interface SpreadAttribute extends BaseNode {
type: 'SpreadAttribute';
expression: Expression;
metadata: {
contains_call_expression: boolean;
dynamic: boolean;
};
}

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

@ -6,7 +6,8 @@ import {
increment,
source,
updating_derived,
UNINITIALIZED
UNINITIALIZED,
mutable_source
} from '../runtime.js';
import {
define_property,
@ -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');
@ -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);
@ -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
};
}
@ -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);
@ -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);
}
@ -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);
@ -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);
@ -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');

@ -27,22 +27,17 @@ import {
get,
is_signal,
push_destroy_fn,
set,
execute_effect,
UNINITIALIZED,
derived,
untrack,
effect,
flushSync,
safe_not_equal,
current_block,
source,
managed_effect,
push,
current_component_context,
pop,
unwrap,
mutable_source
pop
} from './runtime.js';
import {
current_hydration_fragment,
@ -56,12 +51,13 @@ import {
get_descriptor,
get_descriptors,
is_array,
is_function,
object_assign,
object_entries,
object_keys
} from './utils.js';
import { is_promise } from '../common.js';
import { bind_transition, trigger_transitions } from './transitions.js';
import { proxy } from './proxy/proxy.js';
/** @type {Set<string>} */
const all_registerd_events = new Set();
@ -227,7 +223,7 @@ export function close_frag(anchor, dom) {
}
/**
* @param {import('./types.js').MaybeSignal<(event: Event, ...args: Array<unknown>) => void>} fn
* @param {(event: Event, ...args: Array<unknown>) => void} fn
* @returns {(event: Event, ...args: unknown[]) => void}
*/
export function trusted(fn) {
@ -235,13 +231,13 @@ export function trusted(fn) {
const event = /** @type {Event} */ (args[0]);
if (event.isTrusted) {
// @ts-ignore
unwrap(fn).apply(this, args);
fn.apply(this, args);
}
};
}
/**
* @param {import('./types.js').MaybeSignal<(event: Event, ...args: Array<unknown>) => void>} fn
* @param {(event: Event, ...args: Array<unknown>) => void} fn
* @returns {(event: Event, ...args: unknown[]) => void}
*/
export function self(fn) {
@ -250,13 +246,13 @@ export function self(fn) {
// @ts-ignore
if (event.target === this) {
// @ts-ignore
unwrap(fn).apply(this, args);
fn.apply(this, args);
}
};
}
/**
* @param {import('./types.js').MaybeSignal<(event: Event, ...args: Array<unknown>) => void>} fn
* @param {(event: Event, ...args: Array<unknown>) => void} fn
* @returns {(event: Event, ...args: unknown[]) => void}
*/
export function stopPropagation(fn) {
@ -264,12 +260,12 @@ export function stopPropagation(fn) {
const event = /** @type {Event} */ (args[0]);
event.stopPropagation();
// @ts-ignore
return unwrap(fn).apply(this, args);
return fn.apply(this, args);
};
}
/**
* @param {import('./types.js').MaybeSignal<(event: Event, ...args: Array<unknown>) => void>} fn
* @param {(event: Event, ...args: Array<unknown>) => void} fn
* @returns {(event: Event, ...args: unknown[]) => void}
*/
export function once(fn) {
@ -280,12 +276,12 @@ export function once(fn) {
}
ran = true;
// @ts-ignore
return unwrap(fn).apply(this, args);
return fn.apply(this, args);
};
}
/**
* @param {import('./types.js').MaybeSignal<(event: Event, ...args: Array<unknown>) => void>} fn
* @param {(event: Event, ...args: Array<unknown>) => void} fn
* @returns {(event: Event, ...args: unknown[]) => void}
*/
export function stopImmediatePropagation(fn) {
@ -293,12 +289,12 @@ export function stopImmediatePropagation(fn) {
const event = /** @type {Event} */ (args[0]);
event.stopImmediatePropagation();
// @ts-ignore
return unwrap(fn).apply(this, args);
return fn.apply(this, args);
};
}
/**
* @param {import('./types.js').MaybeSignal<(event: Event, ...args: Array<unknown>) => void>} fn
* @param {(event: Event, ...args: Array<unknown>) => void} fn
* @returns {(event: Event, ...args: unknown[]) => void}
*/
export function preventDefault(fn) {
@ -306,7 +302,7 @@ export function preventDefault(fn) {
const event = /** @type {Event} */ (args[0]);
event.preventDefault();
// @ts-ignore
return unwrap(fn).apply(this, args);
return fn.apply(this, args);
};
}
@ -1188,27 +1184,25 @@ export function bind_property(property, event_name, type, dom, get_value, update
}
});
}
/**
* Makes an `export`ed (non-prop) variable available on the `$$props` object
* so that consumers can do `bind:x` on the component.
* @template V
* @param {import('./types.js').MaybeSignal<Record<string, unknown>>} props
* @param {Record<string, unknown>} props
* @param {string} prop
* @param {V} value
* @returns {void}
*/
export function bind_prop(props, prop, value) {
/** @param {V | null} value */
const update = (value) => {
const current_props = unwrap(props);
if (get_descriptor(current_props, prop)?.set !== undefined) {
current_props[prop] = value;
}
};
update(value);
render_effect(() => () => {
update(null);
});
const desc = get_descriptor(props, prop);
if (desc && desc.set) {
props[prop] = value;
render_effect(() => () => {
props[prop] = null;
});
}
}
/**
@ -2531,68 +2525,98 @@ 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>}
* The proxy handler for rest props (i.e. `const { x, ...rest } = $props()`).
* Is passed the full `$$props` object and excludes the named props.
* @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);
/** @type {Record<string, unknown>} */
var rest_props = {};
for (var key in props) {
if (rest.includes(key)) continue;
const { get, value, enumerable } = /** @type {PropertyDescriptor} */ (
get_descriptor(props, key)
);
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 = [];
define_property(rest_props, key, get ? { get, enumerable } : { value, enumerable });
for (let key in target.props) {
if (!target.exclude.includes(key)) keys.push(key);
}
return rest_props;
});
}
return keys;
}
};
/**
* @param {Record<string, unknown>[] | (() => Record<string, unknown>[])} props
* @returns {any}
* @param {import('./types.js').Signal<Record<string, unknown>> | Record<string, unknown>} props
* @param {string[]} rest
* @returns {Record<string, unknown>}
*/
export function spread_props(props) {
if (typeof props === 'function') {
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]
});
export function rest_props(props, rest) {
return new Proxy({ props, exclude: rest }, rest_props_handler);
}
/**
* The proxy handler for spread props. Handles the incoming array of props
* 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<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 (is_function(p)) p = p();
if (typeof p === 'object' && p !== null && key in p) return p[key];
}
},
getOwnPropertyDescriptor() {
return { enumerable: true, configurable: true };
},
has(target, key) {
for (let p of target.props) {
if (is_function(p)) p = p();
if (key in p) return true;
}
return false;
},
ownKeys(target) {
/** @type {Array<string | symbol>} */
const keys = [];
for (let p of target.props) {
if (is_function(p)) 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);
}
/**
@ -2616,73 +2640,14 @@ export function spread_props(props) {
* @returns {Exports & { $destroy: () => void; $set: (props: Partial<Props>) => void; }}
*/
export function createRoot(component, options) {
// The following definitions aren't duplicative. We need _sources to update single props and
// _props in case the component uses $$props / $$restProps / const { x, ...rest } = $props().
/** @type {any} */
const _props = {};
/** @type {any} */
const _sources = {};
/**
* @param {string} name
* @param {any} value
*/
function add_prop(name, value) {
const prop = source(value);
_sources[name] = prop;
define_property(_props, name, {
get() {
return get(prop);
},
enumerable: true
});
}
const props = proxy(/** @type {any} */ (options.props) || {}, false);
for (const prop in options.props || {}) {
add_prop(
prop,
// @ts-expect-error TS doesn't understand this properly
options.props[prop]
);
}
// The proxy ensures that we can add new signals on the fly when a prop signal is accessed from within the component
// but no corresponding prop value was set from the outside. The whole things becomes a _propsSignal
// so that adding new props is reflected in the component if it uses $$props or $$restProps.
const props_proxy = new Proxy(_props, {
/**
* @param {any} target
* @param {any} property
*/
get: (target, property) => {
if (typeof property !== 'string') return target[property];
if (!(property in _sources)) {
add_prop(property, undefined);
}
return _props[property];
}
});
// We're resetting the same proxy instance for updates, therefore bypass equality checks
const props_source = mutable_source(props_proxy);
let [accessors, $destroy] = mount(component, {
...options,
// @ts-expect-error We hide the "the props object could be a signal" fact from the public typings
props: props_source
});
let [accessors, $destroy] = mount(component, { ...options, props });
const result =
/** @type {Exports & { $destroy: () => void; $set: (props: Partial<Props>) => void; }} */ ({
$set: (props) => {
for (const [prop, value] of object_entries(props)) {
if (prop in _sources) {
set(_sources[prop], value);
} else {
add_prop(prop, value);
set(props_source, props_proxy);
}
}
$set: (next) => {
object_assign(props, next);
},
$destroy
});
@ -2837,11 +2802,10 @@ export function access_props(props) {
}
/**
* @param {import('./types.js').MaybeSignal<Record<string, any>>} props
* @param {Record<string, any>} props
* @returns {Record<string, any>}
*/
export function sanitize_slots(props) {
props = unwrap(props);
const sanitized = { ...props.$$slots };
if (props.children) sanitized.default = props.children;
return sanitized;

@ -4,6 +4,7 @@ import { EMPTY_FUNC, run_all } from '../common.js';
import { get_descriptor, get_descriptors, is_array } from './utils.js';
import { PROPS_CALL_DEFAULT_VALUE, PROPS_IS_IMMUTABLE, PROPS_IS_RUNES } from '../../constants.js';
import { readonly } from './proxy/readonly.js';
import { observe } from './proxy/proxy.js';
export const SOURCE = 1;
export const DERIVED = 1 << 1;
@ -96,32 +97,6 @@ export function set_is_ssr(ssr) {
is_ssr = ssr;
}
/**
* @param {import('./types.js').MaybeSignal<Record<string, unknown>>} props
* @returns {import('./types.js').ComponentContext}
*/
export function create_component_context(props) {
const parent = current_component_context;
return {
// accessors
a: null,
// context
c: null,
// effects
e: null,
// mounted
m: false,
// parent
p: parent,
// props
s: props,
// runes
r: false,
// update_callbacks
u: null
};
}
/**
* @param {null | import('./types.js').ComponentContext} context
* @returns {boolean}
@ -1414,18 +1389,17 @@ export function is_store(val) {
* - otherwise create a signal that updates whenever the value is updated from the parent, and when it's updated
* from within the component itself, call the setter of the parent which will propagate the value change back
* @template V
* @param {import('./types.js').MaybeSignal<Record<string, unknown>>} props_obj
* @param {Record<string, unknown>} props
* @param {string} key
* @param {number} flags
* @param {V | (() => V)} [default_value]
* @returns {import('./types.js').Signal<V> | (() => V)}
*/
export function prop_source(props_obj, key, flags, default_value) {
export function prop_source(props, key, flags, default_value) {
const call_default_value = (flags & PROPS_CALL_DEFAULT_VALUE) !== 0;
const immutable = (flags & PROPS_IS_IMMUTABLE) !== 0;
const runes = (flags & PROPS_IS_RUNES) !== 0;
const props = is_signal(props_obj) ? get(props_obj) : props_obj;
const update_bound_prop = get_descriptor(props, key)?.set;
let value = props[key];
const should_set_default_value = value === undefined && default_value !== undefined;
@ -1456,7 +1430,8 @@ export function prop_source(props_obj, key, flags, default_value) {
let mount = true;
sync_effect(() => {
const props = is_signal(props_obj) ? get(props_obj) : props_obj;
observe(props);
// Before if to ensure signal dependency is registered
const propagating_value = props[key];
if (mount) {
@ -1506,12 +1481,13 @@ export function prop_source(props_obj, key, flags, default_value) {
/**
* If the prop is readonly and has no fallback value, we can use this function, else we need to use `prop_source`.
* @param {import('./types.js').MaybeSignal<Record<string, unknown>>} props_obj
* @param {Record<string, unknown>} props
* @param {string} key
* @returns {any}
*/
export function prop(props_obj, key) {
return is_signal(props_obj) ? () => get(props_obj)[key] : () => props_obj[key];
export function prop(props, key) {
// TODO skip this, and rewrite as `$$props.foo`
return () => props[key];
}
/**
@ -1591,23 +1567,18 @@ function get_parent_context(component_context) {
/**
* @this {any}
* @param {import('./types.js').MaybeSignal<Record<string, unknown>>} $$props
* @param {Record<string, unknown>} $$props
* @param {Event} event
* @returns {void}
*/
export function bubble_event($$props, event) {
const events = /** @type {Record<string, Function[] | Function>} */ (unwrap($$props).$$events)?.[
var events = /** @type {Record<string, Function[] | Function>} */ ($$props.$$events)?.[
event.type
];
const callbacks = is_array(events) ? events.slice() : events == null ? [] : [events];
let fn;
for (fn of callbacks) {
var callbacks = is_array(events) ? events.slice() : events == null ? [] : [events];
for (var fn of callbacks) {
// Preserve "this" context
if (is_signal(fn)) {
get(fn).call(this, event);
} else {
fn.call(this, event);
}
fn.call(this, event);
}
}
@ -1752,14 +1723,29 @@ export function onDestroy(fn) {
}
/**
* @param {import('./types.js').MaybeSignal<Record<string, unknown>>} props
* @param {Record<string, unknown>} props
* @param {any} runes
* @returns {void}
*/
export function push(props, runes = false) {
const context_stack_item = create_component_context(props);
context_stack_item.r = runes;
current_component_context = context_stack_item;
current_component_context = {
// accessors
a: null,
// context
c: null,
// effects
e: null,
// mounted
m: false,
// parent
p: current_component_context,
// props
s: props,
// runes
r: runes,
// update_callbacks
u: null
};
}
/**
@ -1815,7 +1801,7 @@ function deep_read(value, visited = new Set()) {
}
/**
* @param {() => import('./types.js').MaybeSignal<>} get_value
* @param {() => any} get_value
* @param {Function} inspect
* @returns {void}
*/

@ -36,7 +36,7 @@ export type Store<V> = {
export type ComponentContext = {
/** props */
s: MaybeSignal<Record<string, unknown>>;
s: Record<string, unknown>;
/** accessors */
a: Record<string, any> | null;
/** effectgs */

@ -8,3 +8,11 @@ export var object_assign = Object.assign;
export var define_property = Object.defineProperty;
export var get_descriptor = Object.getOwnPropertyDescriptor;
export var get_descriptors = Object.getOwnPropertyDescriptors;
/**
* @param {any} thing
* @returns {thing is Function}
*/
export function is_function(thing) {
return typeof thing === 'function';
}

@ -5,13 +5,10 @@ import {
is_ssr,
managed_effect,
untrack,
is_signal,
get,
user_effect,
flush_local_render_effects
} from '../internal/client/runtime.js';
import { is_array } from '../internal/client/utils.js';
import { unwrap } from '../internal/index.js';
/**
* The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM.
@ -139,10 +136,9 @@ export function createEventDispatcher() {
}
return (type, detail, options) => {
const $$events = /** @type {Record<string, Function | Function[]>} */ (
unwrap(component_context.s).$$events
);
const events = $$events?.[/** @type {any} */ (type)];
const events = /** @type {Record<string, Function | Function[]>} */ (
component_context.s.$$events
)?.[/** @type {any} */ (type)];
if (events) {
const callbacks = is_array(events) ? events.slice() : [events];
@ -150,11 +146,7 @@ export function createEventDispatcher() {
// in a server (non-DOM) environment?
const event = create_custom_event(/** @type {string} */ (type), detail, options);
for (const fn of callbacks) {
if (is_signal(fn)) {
get(fn).call(component_context.a, event);
} else {
fn.call(component_context.a, event);
}
fn.call(component_context.a, event);
}
return !event.defaultPrevented;
}

@ -10,6 +10,6 @@ export default test({
});
await Promise.resolve();
assert.htmlEqual(target.innerHTML, '{"visible":true,"foo":"bar"} {"foo":"bar"}');
assert.htmlEqual(target.innerHTML, '{"foo":"bar","visible":true} {"foo":"bar"}');
}
});

@ -1,5 +1,5 @@
<script>
const { foo, default1 = 1, default2 = 2, default3 = 3, ...others } = $props();
const { foo, default1 = 1, default2 = 2, default3 = 3, ...others } = $props();
</script>
{foo} {default1} {default2} {default3} {others.bar}
{foo} {default1} {default2} {default3} {others.bar}

Loading…
Cancel
Save