Skip to content

Commit 11ec907

Browse files
authored
chore: simplify props (#16270)
* simplify props * simplify * tweak * reorder a bit * simplify * unused * more * more * tweak * also appears to be unnecessary * changeset * apparently this is also unnecessary * explanatory comment
1 parent d427ffd commit 11ec907

File tree

4 files changed

+73
-122
lines changed

4 files changed

+73
-122
lines changed

.changeset/new-trees-behave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
chore: simplify props

packages/svelte/src/internal/client/constants.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ export const DESTROYED = 1 << 14;
1515
export const EFFECT_RAN = 1 << 15;
1616
/** 'Transparent' effects do not create a transition boundary */
1717
export const EFFECT_TRANSPARENT = 1 << 16;
18-
/** Svelte 4 legacy mode props need to be handled with deriveds and be recognized elsewhere, hence the dedicated flag */
19-
export const LEGACY_DERIVED_PROP = 1 << 17;
2018
export const INSPECT_EFFECT = 1 << 18;
2119
export const HEAD_EFFECT = 1 << 19;
2220
export const EFFECT_HAS_DERIVED = 1 << 20;

packages/svelte/src/internal/client/reactivity/props.js

Lines changed: 67 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ import {
88
PROPS_IS_UPDATED
99
} from '../../../constants.js';
1010
import { get_descriptor, is_function } from '../../shared/utils.js';
11-
import { mutable_source, set, source, update } from './sources.js';
11+
import { set, source, update } from './sources.js';
1212
import { derived, derived_safe_equal } from './deriveds.js';
13-
import { get, captured_signals, untrack } from '../runtime.js';
14-
import { safe_equals } from './equality.js';
13+
import { get, untrack } from '../runtime.js';
1514
import * as e from '../errors.js';
16-
import { LEGACY_DERIVED_PROP, LEGACY_PROPS, STATE_SYMBOL } from '#client/constants';
15+
import { LEGACY_PROPS, STATE_SYMBOL } from '#client/constants';
1716
import { proxy } from '../proxy.js';
1817
import { capture_store_binding } from './store.js';
1918
import { legacy_mode_flag } from '../../flags/index.js';
@@ -260,89 +259,92 @@ function has_destroyed_component_ctx(current_value) {
260259
* @returns {(() => V | ((arg: V) => V) | ((arg: V, mutation: boolean) => V))}
261260
*/
262261
export function prop(props, key, flags, fallback) {
263-
var immutable = (flags & PROPS_IS_IMMUTABLE) !== 0;
264262
var runes = !legacy_mode_flag || (flags & PROPS_IS_RUNES) !== 0;
265263
var bindable = (flags & PROPS_IS_BINDABLE) !== 0;
266264
var lazy = (flags & PROPS_IS_LAZY_INITIAL) !== 0;
267-
var is_store_sub = false;
268-
var prop_value;
269-
270-
if (bindable) {
271-
[prop_value, is_store_sub] = capture_store_binding(() => /** @type {V} */ (props[key]));
272-
} else {
273-
prop_value = /** @type {V} */ (props[key]);
274-
}
275-
276-
// Can be the case when someone does `mount(Component, props)` with `let props = $state({...})`
277-
// or `createClassComponent(Component, props)`
278-
var is_entry_props = STATE_SYMBOL in props || LEGACY_PROPS in props;
279-
280-
var setter =
281-
(bindable &&
282-
(get_descriptor(props, key)?.set ??
283-
(is_entry_props && key in props && ((v) => (props[key] = v))))) ||
284-
undefined;
285265

286266
var fallback_value = /** @type {V} */ (fallback);
287267
var fallback_dirty = true;
288-
var fallback_used = false;
289268

290269
var get_fallback = () => {
291-
fallback_used = true;
292270
if (fallback_dirty) {
293271
fallback_dirty = false;
294-
if (lazy) {
295-
fallback_value = untrack(/** @type {() => V} */ (fallback));
296-
} else {
297-
fallback_value = /** @type {V} */ (fallback);
298-
}
272+
273+
fallback_value = lazy
274+
? untrack(/** @type {() => V} */ (fallback))
275+
: /** @type {V} */ (fallback);
299276
}
300277

301278
return fallback_value;
302279
};
303280

304-
if (prop_value === undefined && fallback !== undefined) {
305-
if (setter && runes) {
306-
e.props_invalid_value(key);
307-
}
281+
/** @type {((v: V) => void) | undefined} */
282+
var setter;
308283

309-
prop_value = get_fallback();
310-
if (setter) setter(prop_value);
284+
if (bindable) {
285+
// Can be the case when someone does `mount(Component, props)` with `let props = $state({...})`
286+
// or `createClassComponent(Component, props)`
287+
var is_entry_props = STATE_SYMBOL in props || LEGACY_PROPS in props;
288+
289+
setter =
290+
get_descriptor(props, key)?.set ??
291+
(is_entry_props && key in props ? (v) => (props[key] = v) : undefined);
292+
}
293+
294+
var initial_value;
295+
var is_store_sub = false;
296+
297+
if (bindable) {
298+
[initial_value, is_store_sub] = capture_store_binding(() => /** @type {V} */ (props[key]));
299+
} else {
300+
initial_value = /** @type {V} */ (props[key]);
301+
}
302+
303+
if (initial_value === undefined && fallback !== undefined) {
304+
initial_value = get_fallback();
305+
306+
if (setter) {
307+
if (runes) e.props_invalid_value(key);
308+
setter(initial_value);
309+
}
311310
}
312311

313312
/** @type {() => V} */
314313
var getter;
314+
315315
if (runes) {
316316
getter = () => {
317317
var value = /** @type {V} */ (props[key]);
318318
if (value === undefined) return get_fallback();
319319
fallback_dirty = true;
320-
fallback_used = false;
321320
return value;
322321
};
323322
} else {
324-
// Svelte 4 did not trigger updates when a primitive value was updated to the same value.
325-
// Replicate that behavior through using a derived
326-
var derived_getter = (immutable ? derived : derived_safe_equal)(
327-
() => /** @type {V} */ (props[key])
328-
);
329-
derived_getter.f |= LEGACY_DERIVED_PROP;
330323
getter = () => {
331-
var value = get(derived_getter);
332-
if (value !== undefined) fallback_value = /** @type {V} */ (undefined);
324+
var value = /** @type {V} */ (props[key]);
325+
326+
if (value !== undefined) {
327+
// in legacy mode, we don't revert to the fallback value
328+
// if the prop goes from defined to undefined. The easiest
329+
// way to model this is to make the fallback undefined
330+
// as soon as the prop has a value
331+
fallback_value = /** @type {V} */ (undefined);
332+
}
333+
333334
return value === undefined ? fallback_value : value;
334335
};
335336
}
336337

337-
// easy mode — prop is never written to
338-
if ((flags & PROPS_IS_UPDATED) === 0 && runes) {
338+
// prop is never written to — we only need a getter
339+
if ((flags & PROPS_IS_UPDATED) === 0) {
339340
return getter;
340341
}
341342

342-
// intermediate mode — prop is written to, but the parent component had
343-
// `bind:foo` which means we can just call `$$props.foo = value` directly
343+
// prop is written to, but the parent component had `bind:foo` which
344+
// means we can just call `$$props.foo = value` directly
344345
if (setter) {
345346
var legacy_parent = props.$$legacy;
347+
346348
return function (/** @type {any} */ value, /** @type {boolean} */ mutation) {
347349
if (arguments.length > 0) {
348350
// We don't want to notify if the value was mutated and the parent is in runes mode.
@@ -352,82 +354,39 @@ export function prop(props, key, flags, fallback) {
352354
if (!runes || !mutation || legacy_parent || is_store_sub) {
353355
/** @type {Function} */ (setter)(mutation ? getter() : value);
354356
}
357+
355358
return value;
356-
} else {
357-
return getter();
358359
}
360+
361+
return getter();
359362
};
360363
}
361364

362-
// hard mode. this is where it gets ugly — the value in the child should
363-
// synchronize with the parent, but it should also be possible to temporarily
364-
// set the value to something else locally.
365-
var from_child = false;
366-
var was_from_child = false;
367-
368-
// The derived returns the current value. The underlying mutable
369-
// source is written to from various places to persist this value.
370-
var inner_current_value = mutable_source(prop_value);
371-
var current_value = derived(() => {
372-
var parent_value = getter();
373-
var child_value = get(inner_current_value);
374-
375-
if (from_child) {
376-
from_child = false;
377-
was_from_child = true;
378-
return child_value;
379-
}
380-
381-
was_from_child = false;
382-
return (inner_current_value.v = parent_value);
383-
});
384-
385-
// Ensure we eagerly capture the initial value if it's bindable
386-
if (bindable) {
387-
get(current_value);
388-
}
365+
// prop is written to, but there's no binding, which means we
366+
// create a derived that we can write to locally
367+
var d = ((flags & PROPS_IS_IMMUTABLE) !== 0 ? derived : derived_safe_equal)(getter);
389368

390-
if (!immutable) current_value.equals = safe_equals;
369+
// Capture the initial value if it's bindable
370+
if (bindable) get(d);
391371

392372
return function (/** @type {any} */ value, /** @type {boolean} */ mutation) {
393-
// legacy nonsense — need to ensure the source is invalidated when necessary
394-
// also needed for when handling inspect logic so we can inspect the correct source signal
395-
if (captured_signals !== null) {
396-
// set this so that we don't reset to the parent value if `d`
397-
// is invalidated because of `invalidate_inner_signals` (rather
398-
// than because the parent or child value changed)
399-
from_child = was_from_child;
400-
// invoke getters so that signals are picked up by `invalidate_inner_signals`
401-
getter();
402-
get(inner_current_value);
403-
}
404-
405373
if (arguments.length > 0) {
406-
const new_value = mutation ? get(current_value) : runes && bindable ? proxy(value) : value;
407-
408-
if (!current_value.equals(new_value)) {
409-
from_child = true;
410-
set(inner_current_value, new_value);
411-
// To ensure the fallback value is consistent when used with proxies, we
412-
// update the local fallback_value, but only if the fallback is actively used
413-
if (fallback_used && fallback_value !== undefined) {
414-
fallback_value = new_value;
415-
}
374+
const new_value = mutation ? get(d) : runes && bindable ? proxy(value) : value;
416375

417-
if (has_destroyed_component_ctx(current_value)) {
418-
return value;
419-
}
376+
set(d, new_value);
420377

421-
untrack(() => get(current_value)); // force a synchronisation immediately
378+
if (fallback_value !== undefined) {
379+
fallback_value = new_value;
422380
}
423381

424382
return value;
425383
}
426384

427-
if (has_destroyed_component_ctx(current_value)) {
428-
return current_value.v;
385+
// TODO is this still necessary post-#16263?
386+
if (has_destroyed_component_ctx(d)) {
387+
return d.v;
429388
}
430389

431-
return get(current_value);
390+
return get(d);
432391
};
433392
}

packages/svelte/src/internal/client/runtime.js

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
STATE_SYMBOL,
2121
BLOCK_EFFECT,
2222
ROOT_EFFECT,
23-
LEGACY_DERIVED_PROP,
2423
DISCONNECTED,
2524
EFFECT_IS_UPDATING,
2625
STALE_REACTION
@@ -863,17 +862,7 @@ export function invalidate_inner_signals(fn) {
863862
var captured = capture_signals(() => untrack(fn));
864863

865864
for (var signal of captured) {
866-
// Go one level up because derived signals created as part of props in legacy mode
867-
if ((signal.f & LEGACY_DERIVED_PROP) !== 0) {
868-
for (const dep of /** @type {Derived} */ (signal).deps || []) {
869-
if ((dep.f & DERIVED) === 0) {
870-
// Use internal_set instead of set here and below to avoid mutation validation
871-
internal_set(dep, dep.v);
872-
}
873-
}
874-
} else {
875-
internal_set(signal, signal.v);
876-
}
865+
internal_set(signal, signal.v);
877866
}
878867
}
879868

0 commit comments

Comments
 (0)