@@ -59,8 +59,8 @@ let current_queued_pre_and_render_effects = [];
59
59
/** @type {import('./types.js').EffectSignal[] } */
60
60
let current_queued_effects = [ ] ;
61
61
62
- /** @type {{t : import('./types.js').ComputationSignal, p: Array<string | symbol>, r: import('./types.js').ComputationSignal } | null } */
63
- let current_path = null ;
62
+ /** @type {{ s : import('./types.js').ComputationSignal, p: Array<string | symbol> } | null } */
63
+ let current_derived_proxy_property = null ;
64
64
65
65
/** @type {Array<() => void> } */
66
66
let current_queued_tasks = [ ] ;
@@ -341,7 +341,7 @@ function execute_signal_fn(signal) {
341
341
const previous_skip_consumer = current_skip_consumer ;
342
342
const is_render_effect = ( flags & RENDER_EFFECT ) !== 0 ;
343
343
const previous_untracking = current_untracking ;
344
- const previous_path = current_path ;
344
+ const previous_derived_proxy_property = current_derived_proxy_property ;
345
345
current_dependencies = /** @type {null | import('./types.js').Signal[] } */ ( null ) ;
346
346
current_dependencies_index = 0 ;
347
347
current_untracked_writes = null ;
@@ -350,7 +350,7 @@ function execute_signal_fn(signal) {
350
350
current_component_context = signal . x ;
351
351
current_skip_consumer = ! is_flushing_effect && ( flags & UNOWNED ) !== 0 ;
352
352
current_untracking = false ;
353
- current_path = null ;
353
+ current_derived_proxy_property = null ;
354
354
355
355
// Render effects are invoked when the UI is about to be updated - run beforeUpdate at that point
356
356
if ( is_render_effect && current_component_context ?. u != null ) {
@@ -371,8 +371,8 @@ function execute_signal_fn(signal) {
371
371
} else {
372
372
res = /** @type {() => V } */ ( init ) ( ) ;
373
373
}
374
- if ( current_path !== null ) {
375
- push_derived_path ( ) ;
374
+ if ( current_derived_proxy_property !== null ) {
375
+ capture_fine_grain_derived_property ( ) ;
376
376
}
377
377
let dependencies = /** @type {import('./types.js').Signal<unknown>[] } **/ ( signal . d ) ;
378
378
if ( current_dependencies !== null ) {
@@ -444,7 +444,7 @@ function execute_signal_fn(signal) {
444
444
current_component_context = previous_component_context ;
445
445
current_skip_consumer = previous_skip_consumer ;
446
446
current_untracking = previous_untracking ;
447
- current_path = previous_path ;
447
+ current_derived_proxy_property = previous_derived_proxy_property ;
448
448
}
449
449
}
450
450
@@ -536,7 +536,7 @@ export function execute_effect(signal) {
536
536
if ( ( signal . f & DESTROYED ) !== 0 ) {
537
537
return ;
538
538
}
539
- const teardown = signal . v ;
539
+ const teardown = /** @type { null | (() => void) } */ ( signal . v ) ;
540
540
const previous_effect = current_effect ;
541
541
current_effect = signal ;
542
542
@@ -830,16 +830,30 @@ export async function tick() {
830
830
* @returns {void }
831
831
*/
832
832
function update_derived ( signal , force_schedule ) {
833
+ let derived_value =
834
+ /** @type {import('./types.js').DerivedSignalValue<V> | typeof UNINITIALIZED } */ ( signal . v ) ;
835
+ if ( derived_value === UNINITIALIZED ) {
836
+ signal . v = derived_value = /** @type {import('./types.js').DerivedSignalValue<V> } */ ( {
837
+ p : null ,
838
+ v : UNINITIALIZED ,
839
+ o : null
840
+ } ) ;
841
+ }
833
842
const previous_updating_derived = updating_derived ;
834
843
updating_derived = true ;
844
+ if ( derived_value . p !== null ) {
845
+ derived_value . p = null ;
846
+ derived_value . o ?. clear ( ) ;
847
+ }
835
848
destroy_references ( signal ) ;
836
849
const value = execute_signal_fn ( signal ) ;
837
850
updating_derived = previous_updating_derived ;
838
851
const status = current_skip_consumer || ( signal . f & UNOWNED ) !== 0 ? DIRTY : CLEAN ;
839
852
set_signal_status ( signal , status ) ;
840
853
const equals = /** @type {import('./types.js').EqualsFunctions } */ ( signal . e ) ;
841
- if ( ! equals ( value , signal . v ) ) {
842
- signal . v = value ;
854
+
855
+ if ( ! equals ( value , derived_value . v ) ) {
856
+ derived_value . v = value ;
843
857
mark_signal_consumers ( signal , DIRTY , force_schedule ) ;
844
858
845
859
// @ts -expect-error
@@ -939,36 +953,49 @@ export function unsubscribe_on_destroy(stores) {
939
953
} ) ;
940
954
}
941
955
942
- function push_derived_path ( ) {
943
- const path =
944
- /** @type {{t: import('./types.js').ComputationSignal, p: Array<string | symbol>, r: import('./types.js').ComputationSignal } } */ (
945
- current_path
956
+ /**
957
+ * If the `current_derived_proxy_property` is not `null` then that means we should look at the current
958
+ * property on the object and see if actually need original derived object dependency. For example,
959
+ * if you had this:
960
+ *
961
+ * a.b.c
962
+ *
963
+ * Under-the-hood, `a` might be a derived signal, so we'd call get() on it. Resulting in `a` being a dependency
964
+ * for the currently active effect. The accessors to `b` and `c` would result in the `current_derived_proxy_property`
965
+ * changing to include ['b', 'c'] in the `current_derived_proxy_property.p` paths property. We can then use that to
966
+ * determine a new derived temporary signal that encapsulates a.b.c. This temporarly signal then becomes the dependency
967
+ * and we no longer need to the original depdency to `a` for the current effect. Thus making
968
+ */
969
+ function capture_fine_grain_derived_property ( ) {
970
+ const derived_property =
971
+ /** @type {{ s: import('./types.js').ComputationSignal, p: Array<string | symbol> } } */ (
972
+ current_derived_proxy_property
946
973
) ;
947
- if ( is_last_current_dependency ( path . r ) ) {
974
+ if ( is_last_current_dependency ( derived_property . s ) ) {
948
975
if ( current_dependencies === null ) {
949
976
current_dependencies_index -- ;
950
977
} else {
951
978
current_dependencies . pop ( ) ;
952
979
}
953
980
}
954
981
const derived_prop = derived ( ( ) => {
955
- let value = /** @type {any } */ ( get ( path . t ) ) ;
956
- const property_path = path . p ;
982
+ let value = /** @type {any } */ ( get ( derived_property . s , true ) ) ;
983
+ const property_path = derived_property . p ;
957
984
for ( let i = 0 ; i < property_path . length ; i ++ ) {
958
985
value = value ?. [ property_path [ i ] ] ;
959
986
}
960
987
return value ;
961
988
} ) ;
962
- current_path = null ;
963
- get ( derived_prop ) ;
989
+ current_derived_proxy_property = null ;
990
+ get ( derived_prop , true ) ;
964
991
}
965
992
966
993
/**
967
994
* @template V
968
995
* @param {import('./types.js').Signal<V> } signal
969
996
* @returns {V }
970
997
*/
971
- export function get ( signal ) {
998
+ export function get ( signal , skip_derived_proxy = false ) {
972
999
// @ts -expect-error
973
1000
if ( DEV && signal . inspect && inspect_fn ) {
974
1001
/** @type {import('./types.js').SignalDebug } */ ( signal ) . inspect . add ( inspect_fn ) ;
@@ -977,12 +1004,16 @@ export function get(signal) {
977
1004
}
978
1005
979
1006
const flags = signal . f ;
1007
+ const is_derived = ( flags & DERIVED ) !== 0 ;
1008
+ let value = signal . v ;
980
1009
if ( ( flags & DESTROYED ) !== 0 ) {
981
- return signal . v ;
1010
+ return /** @type {V } */ (
1011
+ is_derived ? /** @type {import('./types.js').DerivedSignalValue<V> } */ ( value ) . v : value
1012
+ ) ;
982
1013
}
983
1014
984
- if ( current_path !== null ) {
985
- push_derived_path ( ) ;
1015
+ if ( current_derived_proxy_property !== null ) {
1016
+ capture_fine_grain_derived_property ( ) ;
986
1017
}
987
1018
988
1019
if ( is_signals_recorded ) {
@@ -1022,18 +1053,47 @@ export function get(signal) {
1022
1053
}
1023
1054
}
1024
1055
1025
- if ( ( flags & DERIVED ) !== 0 && is_signal_dirty ( signal ) ) {
1026
- if ( DEV ) {
1027
- // we want to avoid tracking indirect dependencies
1028
- const previous_inspect_fn = inspect_fn ;
1029
- inspect_fn = null ;
1030
- update_derived ( /** @type {import('./types.js').ComputationSignal<V> } **/ ( signal ) , false ) ;
1031
- inspect_fn = previous_inspect_fn ;
1032
- } else {
1033
- update_derived ( /** @type {import('./types.js').ComputationSignal<V> } **/ ( signal ) , false ) ;
1056
+ if ( is_derived ) {
1057
+ if ( is_signal_dirty ( signal ) ) {
1058
+ if ( DEV ) {
1059
+ // we want to avoid tracking indirect dependencies
1060
+ const previous_inspect_fn = inspect_fn ;
1061
+ inspect_fn = null ;
1062
+ update_derived ( /** @type {import('./types.js').ComputationSignal<V> } **/ ( signal ) , false ) ;
1063
+ inspect_fn = previous_inspect_fn ;
1064
+ } else {
1065
+ update_derived ( /** @type {import('./types.js').ComputationSignal<V> } **/ ( signal ) , false ) ;
1066
+ }
1067
+ }
1068
+ const derived_signal_value = /** @type {import('./types.js').DerivedSignalValue<V> } */ (
1069
+ signal . v
1070
+ ) ;
1071
+ const value = derived_signal_value . v ;
1072
+ // If we are working with a derived that might be an object or array, then we might also want to
1073
+ // apply the fine-grain derived property heuristic to them. However, we only need this heuristic in some cases:
1074
+ // - inside a user effect ($effect or $effect.pre)
1075
+ // - inside another derived ($derived)
1076
+ // Else we don't need to bother doing this as render effects and the rest of the internal architecture applys
1077
+ // diffing which is more optimal than creating many derived signals. However, we can't do diffing inside user
1078
+ // effects (far too many complications with cleanup functions etc).
1079
+ if (
1080
+ ! skip_derived_proxy &&
1081
+ is_runes ( signal . x ) &&
1082
+ effect_active_and_not_render_effect ( ) &&
1083
+ should_proxy_derived_value ( value )
1084
+ ) {
1085
+ let proxy = derived_signal_value . p ;
1086
+ if ( proxy === null ) {
1087
+ proxy = derived_signal_value . p = create_derived_proxy (
1088
+ /** @type {import('./types.js').ComputationSignal<V> } **/ ( signal ) ,
1089
+ value
1090
+ ) ;
1091
+ }
1092
+ return proxy ;
1034
1093
}
1094
+ return value ;
1035
1095
}
1036
- return signal . v ;
1096
+ return /** @type { V } */ ( signal . v ) ;
1037
1097
}
1038
1098
1039
1099
/**
@@ -1366,30 +1426,36 @@ function is_last_current_dependency(signal) {
1366
1426
1367
1427
/**
1368
1428
* @template V
1369
- * @param {() => any } init
1370
- * @returns {import('./types.js').ComputationSignal<V> }
1429
+ * @param {import("./types.js").ComputationSignal<V> } signal
1430
+ * @param {V } value
1431
+ * @param {ProxyHandler<any> } handler
1432
+ * @param {(string | symbol)[] } path
1433
+ * @returns {V }
1371
1434
*/
1372
- /*#__NO_SIDE_EFFECTS__*/
1373
- export function derived_proxy ( init ) {
1374
- const derived_object = derived ( init ) ;
1375
- const proxied_objects = new Map ( ) ;
1376
-
1377
- /**
1378
- * @param {V } value
1379
- * @param {(string | symbol)[] } path
1380
- * @returns {V }
1381
- */
1382
- function proxify_object ( value , path ) {
1383
- const keys = new Set ( Reflect . ownKeys ( /** @type {object } */ ( value ) ) ) ;
1384
- const proxy = new Proxy ( value , handler ) ;
1385
- proxied_objects . set ( value , {
1386
- x : proxy ,
1387
- k : keys ,
1388
- p : path
1389
- } ) ;
1390
- return proxy ;
1391
- }
1435
+ function proxify_object ( signal , value , handler , path ) {
1436
+ const keys = new Set ( Reflect . ownKeys ( /** @type {object } */ ( value ) ) ) ;
1437
+ const proxy = new Proxy ( value , handler ) ;
1438
+ const derived_value = /** @type {import('./types.js').DerivedSignalValue<V> } */ ( signal . v ) ;
1439
+ let proxied_objects = derived_value . o ;
1440
+ if ( proxied_objects === null ) {
1441
+ derived_value . o = proxied_objects = new Map ( ) ;
1442
+ }
1443
+ proxied_objects . set ( value , {
1444
+ x : proxy ,
1445
+ k : keys ,
1446
+ p : path
1447
+ } ) ;
1448
+ return proxy ;
1449
+ }
1392
1450
1451
+ /**
1452
+ * @template V
1453
+ * @param {import("./types.js").ComputationSignal<V> } signal
1454
+ * @param {V } derived_value
1455
+ * @returns {V }
1456
+ */
1457
+ /*#__NO_SIDE_EFFECTS__*/
1458
+ function create_derived_proxy ( signal , derived_value ) {
1393
1459
const handler = {
1394
1460
/**
1395
1461
* @param {any } target
@@ -1398,12 +1464,14 @@ export function derived_proxy(init) {
1398
1464
*/
1399
1465
get ( target , prop , receiver ) {
1400
1466
const value = Reflect . get ( target , prop , receiver ) ;
1467
+ const derived_value = /** @type {import('./types.js').DerivedSignalValue<V> } */ ( signal . v ) ;
1468
+ const proxied_objects = /** @type {any } */ ( derived_value . o ) ;
1401
1469
const { k : keys , p : path } = proxied_objects . get ( target ) ;
1402
1470
1403
1471
if (
1404
- ( effect_active_and_not_render_effect ( ) || updating_derived ) &&
1472
+ effect_active_and_not_render_effect ( ) &&
1405
1473
keys . has ( prop ) &&
1406
- is_last_current_dependency ( proxied_derived )
1474
+ is_last_current_dependency ( signal )
1407
1475
) {
1408
1476
const type = typeof value ;
1409
1477
let new_path ;
@@ -1420,10 +1488,10 @@ export function derived_proxy(init) {
1420
1488
STATE_SYMBOL in value
1421
1489
) {
1422
1490
new_path = [ ...path , prop ] ;
1423
- if ( current_path !== null ) {
1424
- push_derived_path ( ) ;
1491
+ if ( current_derived_proxy_property !== null ) {
1492
+ capture_fine_grain_derived_property ( ) ;
1425
1493
} else {
1426
- current_path = { t : derived_object , p : new_path , r : proxied_derived } ;
1494
+ current_derived_proxy_property = { s : signal , p : new_path } ;
1427
1495
}
1428
1496
}
1429
1497
if ( should_proxy_derived_value ( value ) ) {
@@ -1434,29 +1502,14 @@ export function derived_proxy(init) {
1434
1502
if ( ! new_path ) {
1435
1503
new_path = [ ...path , prop ] ;
1436
1504
}
1437
- return proxify_object ( value , new_path ) ;
1505
+ return proxify_object ( signal , value , handler , new_path ) ;
1438
1506
}
1439
1507
}
1440
1508
return value ;
1441
1509
}
1442
1510
} ;
1443
1511
1444
- const proxied_derived = derived ( ( ) => {
1445
- const value = get ( derived_object ) ;
1446
- if ( should_proxy_derived_value ( value ) ) {
1447
- return proxify_object ( value , [ ] ) ;
1448
- } else if ( proxied_objects . size > 0 ) {
1449
- proxied_objects . clear ( ) ;
1450
- }
1451
- return value ;
1452
- } ) ;
1453
-
1454
- // Cleanup when the derived is destroyed
1455
- proxied_derived . y = ( ) => {
1456
- proxied_objects . clear ( ) ;
1457
- } ;
1458
-
1459
- return proxied_derived ;
1512
+ return proxify_object ( signal , derived_value , handler , [ ] ) ;
1460
1513
}
1461
1514
1462
1515
/**
0 commit comments