distinguish between source and mutable_source

proxied-state-each-blocks
Rich Harris 9 months ago
parent b581a64727
commit 08c069d5e0

@ -175,7 +175,7 @@ export function client_component(source, analysis, options) {
for (const [name, binding] of analysis.instance.scope.declarations) {
if (binding.kind === 'legacy_reactive') {
legacy_reactive_declarations.push(b.const(name, b.call('$.source')));
legacy_reactive_declarations.push(b.const(name, b.call('$.mutable_source')));
}
if (binding.kind === 'store_sub') {
if (store_setup.length === 0) {

@ -347,7 +347,7 @@ export function serialize_hoistable_params(node, context) {
*/
export function get_props_method(binding, state, name, default_value) {
/** @type {import('estree').Expression[]} */
const args = [b.id('$$props'), b.literal(name)];
const args = [b.id('$$props'), b.literal(name), b.literal(state.analysis.immutable)];
if (default_value) {
// To avoid eagerly evaluating the right-hand-side, we wrap it in a thunk if necessary
@ -382,7 +382,7 @@ export function get_props_method(binding, state, name, default_value) {
export function create_state_declarators(declarator, scope, value) {
// in the simple `let count = $state(0)` case, we rewrite `$state` as `$.source`
if (declarator.id.type === 'Identifier') {
return [b.declarator(declarator.id, b.call('$.source', value))];
return [b.declarator(declarator.id, b.call('$.mutable_source', value))];
}
const tmp = scope.generate('tmp');
@ -392,7 +392,10 @@ export function create_state_declarators(declarator, scope, value) {
...paths.map((path) => {
const value = path.expression?.(b.id(tmp));
const binding = scope.get(/** @type {import('estree').Identifier} */ (path.node).name);
return b.declarator(path.node, binding?.kind === 'state' ? b.call('$.source', value) : value);
return b.declarator(
path.node,
binding?.kind === 'state' ? b.call('$.mutable_source', value) : value
);
})
];
}

@ -27,6 +27,7 @@ import {
DOMBooleanAttributes,
EACH_INDEX_REACTIVE,
EACH_IS_CONTROLLED,
EACH_IS_IMMUTABLE,
EACH_ITEM_REACTIVE,
EACH_KEYED
} from '../../../../../constants.js';
@ -2117,14 +2118,13 @@ export const template_visitors = {
// array or use nested reactivity through runes.
// TODO this feels a bit "hidden performance boost"-style, investigate if there's a way
// to make this apply in more cases
/** @type {number} */
let each_type;
let each_type = 0;
if (
node.key &&
(node.key.type !== 'Identifier' || !node.index || node.key.name !== node.index)
) {
each_type = EACH_KEYED;
each_type |= EACH_KEYED;
if (
node.key.type === 'Identifier' &&
node.context.type === 'Identifier' &&
@ -2141,12 +2141,17 @@ export const template_visitors = {
each_type |= EACH_INDEX_REACTIVE;
}
} else {
each_type = EACH_ITEM_REACTIVE;
each_type |= EACH_ITEM_REACTIVE;
}
if (each_node_meta.is_controlled) {
each_type |= EACH_IS_CONTROLLED;
}
if (context.state.analysis.immutable) {
each_type |= EACH_IS_IMMUTABLE;
}
// Find the parent each blocks which contain the arrays to invalidate
// TODO decide how much of this we want to keep for runes mode. For now we're bailing out below
const indirect_dependencies = collect_parent_each_blocks(context).flatMap((block) => {

@ -4,6 +4,7 @@ export const EACH_KEYED = 1 << 2;
export const EACH_IS_CONTROLLED = 1 << 3;
export const EACH_IS_ANIMATED = 1 << 4;
export const EACH_IS_PROXIED = 1 << 5;
export const EACH_IS_IMMUTABLE = 1 << 6;
/** List of Element events that will be delegated */
export const DelegatedEvents = [

@ -2,6 +2,7 @@ import {
EACH_INDEX_REACTIVE,
EACH_IS_ANIMATED,
EACH_IS_CONTROLLED,
EACH_IS_IMMUTABLE,
EACH_IS_PROXIED,
EACH_ITEM_REACTIVE,
EACH_KEYED
@ -21,6 +22,7 @@ import {
destroy_signal,
execute_effect,
lazy_property,
mutable_source,
push_destroy_fn,
render_effect,
schedule_task,
@ -747,6 +749,8 @@ function each_item_block(array, item, key, index, render_fn, flags) {
? lazy_property(array, index)
: each_item_not_reactive
? item
: (flags & EACH_IS_IMMUTABLE) === 0
? mutable_source(item)
: source(item);
const index_value = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index);
const block = create_each_item_block(item_value, index_value, key);

@ -44,7 +44,8 @@ import {
current_component_context,
pop,
unwrap,
default_equals
default_equals,
mutable_source
} from './runtime.js';
import {
current_hydration_fragment,
@ -2625,7 +2626,6 @@ export function createRoot(component, options) {
*/
function add_prop(name, value) {
const prop = source(value);
prop.e = default_equals; // TODO should this be safe_equals?
_sources[name] = prop;
define_property(_props, name, {
get() {
@ -2659,10 +2659,9 @@ export function createRoot(component, options) {
return _props[property];
}
});
const props_source = source(props_proxy);
// We're resetting the same proxy instance for updates, therefore bypass equality checks
props_source.e = safe_equal;
const props_source = mutable_source(props_proxy);
let [accessors, $destroy] = mount(component, {
...options,

@ -163,7 +163,7 @@ function create_source_signal(flags, value) {
// consumers
c: null,
// equals
e: null,
e: default_equals,
// flags
f: flags,
// value
@ -178,7 +178,7 @@ function create_source_signal(flags, value) {
// consumers
c: null,
// equals
e: null,
e: default_equals,
// flags
f: flags,
// value
@ -698,7 +698,7 @@ export function store_get(store, store_name, stores) {
entry = {
store: null,
last_value: null,
value: source(UNINITIALIZED),
value: mutable_source(UNINITIALIZED),
unsubscribe: EMPTY_FUNC
};
// TODO: can we remove this code? it was refactored out when we split up source/comptued signals
@ -1148,10 +1148,21 @@ export function derived(init) {
export function source(initial_value) {
const source = create_source_signal(SOURCE | CLEAN, initial_value);
source.x = current_component_context;
source.e = get_equals_method();
return source;
}
/**
* @template V
* @param {V} initial_value
* @returns {import('./types.js').SourceSignal<V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function mutable_source(initial_value) {
const s = source(initial_value);
s.e = safe_equal;
return s;
}
/**
* @returns {import('./types.js').EqualsFunctions}
*/
@ -1424,11 +1435,12 @@ export function is_store(val) {
* @template V
* @param {import('./types.js').MaybeSignal<Record<string, unknown>>} props_obj
* @param {string} key
* @param {boolean} immutable
* @param {V | (() => V)} [default_value]
* @param {boolean} [call_default_value]
* @returns {import('./types.js').Signal<V> | (() => V)}
*/
export function prop_source(props_obj, key, default_value, call_default_value) {
export function prop_source(props_obj, key, immutable, default_value, call_default_value) {
const props = is_signal(props_obj) ? get(props_obj) : props_obj;
const possible_signal = /** @type {import('./types.js').MaybeSignal<V>} */ (
expose(() => props[key])
@ -1440,8 +1452,7 @@ export function prop_source(props_obj, key, default_value, call_default_value) {
if (
is_signal(possible_signal) &&
possible_signal.v === value &&
update_bound_prop === undefined &&
get_equals_method() === possible_signal.e
update_bound_prop === undefined
) {
if (should_set_default_value) {
set(
@ -1459,13 +1470,11 @@ export function prop_source(props_obj, key, default_value, call_default_value) {
call_default_value ? default_value() : default_value;
}
const source_signal = source(value);
const source_signal = immutable ? source(value) : mutable_source(value);
// Synchronize prop changes with source signal.
// Needs special equality checking because the prop in the
// parent could be changed through `foo.bar = 'new value'`.
const immutable = /** @type {import('./types.js').ComponentContext} */ (current_component_context)
.i;
let ignore_next1 = false;
let ignore_next2 = false;
let did_update_to_defined = !should_set_default_value;

@ -7,6 +7,7 @@ export {
expose,
exposable,
source,
mutable_source,
derived,
prop,
prop_source,

@ -11,4 +11,4 @@
<div><input type='checkbox' bind:checked={item.completed}><p>{item.description}</p></div>
{/each}
<p>{numCompleted} completed</p>
<p>{numCompleted} completed</p>

@ -6,7 +6,7 @@ import * as $ from "svelte/internal";
export default function Svelte_element($$anchor, $$props) {
$.push($$props, true);
let tag = $.prop_source($$props, "tag", 'hr', false);
let tag = $.prop_source($$props, "tag", true, 'hr', false);
/* Init */
var fragment = $.comment($$anchor);
var node = $.child_frag(fragment);
@ -14,4 +14,4 @@ export default function Svelte_element($$anchor, $$props) {
$.element(node, () => $.get(tag));
$.close_frag($$anchor, fragment);
$.pop();
}
}

Loading…
Cancel
Save