ripple 0.3.127 → 0.3.128
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/package.json +2 -2
- package/src/runtime/internal/client/blocks.js +20 -24
- package/src/runtime/internal/client/constants.js +5 -5
- package/src/runtime/internal/client/expression.js +142 -101
- package/src/runtime/internal/client/for.js +217 -145
- package/src/runtime/internal/client/index.js +5 -1
- package/src/runtime/internal/client/operations.js +24 -10
- package/src/runtime/internal/client/render.js +22 -3
- package/src/runtime/internal/client/runtime.js +246 -182
- package/src/runtime/internal/client/selector.js +74 -0
- package/src/runtime/internal/client/types.d.ts +11 -0
- package/src/runtime/internal/server/index.js +2 -0
- package/tests/client/basic/basic.reactivity.test.tsrx +33 -0
- package/tests/client/for-selector.test.tsrx +221 -0
- package/tests/hydration/compiled/client/basic.js +44 -44
- package/tests/hydration/compiled/client/composite.js +3 -3
- package/tests/hydration/compiled/client/events.js +56 -29
- package/tests/hydration/compiled/client/for.js +76 -70
- package/tests/hydration/compiled/client/head.js +11 -8
- package/tests/hydration/compiled/client/hmr.js +4 -4
- package/tests/hydration/compiled/client/html-in-template.js +3 -3
- package/tests/hydration/compiled/client/html.js +141 -141
- package/tests/hydration/compiled/client/if-children.js +29 -29
- package/tests/hydration/compiled/client/if-fragment-controlflow.js +16 -16
- package/tests/hydration/compiled/client/if.js +10 -10
- package/tests/hydration/compiled/client/mixed-control-flow.js +26 -22
- package/tests/hydration/compiled/client/nested-control-flow.js +114 -92
- package/tests/hydration/compiled/client/portal.js +8 -8
- package/tests/hydration/compiled/client/reactivity.js +38 -15
- package/tests/hydration/compiled/client/streaming.js +16 -16
- package/tests/hydration/compiled/client/switch.js +4 -4
- package/tests/hydration/compiled/client/track-async-serialization.js +18 -18
- package/tests/hydration/compiled/client/try.js +8 -8
- package/tests/hydration/compiled/server/events.js +5 -53
- package/tests/hydration/compiled/server/for.js +2 -18
- package/tests/hydration/compiled/server/head.js +1 -9
- package/tests/hydration/compiled/server/html.js +1 -9
- package/tests/hydration/compiled/server/reactivity.js +2 -34
|
@@ -18,9 +18,9 @@ import {
|
|
|
18
18
|
DERIVED,
|
|
19
19
|
COMPUTED_PROPERTY,
|
|
20
20
|
CONTAINS_TEARDOWN,
|
|
21
|
-
CONTAINS_UPDATE,
|
|
22
21
|
DESTROYED,
|
|
23
22
|
EFFECT_BLOCK,
|
|
23
|
+
FOR_BLOCK,
|
|
24
24
|
PAUSED,
|
|
25
25
|
PRE_EFFECT_BLOCK,
|
|
26
26
|
ROOT_BLOCK,
|
|
@@ -34,7 +34,8 @@ import {
|
|
|
34
34
|
SUSPENSE_REJECTED,
|
|
35
35
|
TRY_BLOCK,
|
|
36
36
|
DIRECT_CHILD_BLOCK,
|
|
37
|
-
|
|
37
|
+
SCHEDULED,
|
|
38
|
+
SELECTOR,
|
|
38
39
|
} from './constants.js';
|
|
39
40
|
import {
|
|
40
41
|
begin_boundary_request,
|
|
@@ -92,14 +93,18 @@ let scheduler_mode = FLUSH_MICROTASK;
|
|
|
92
93
|
let is_micro_task_queued = false;
|
|
93
94
|
/** @type {number} */
|
|
94
95
|
let clock = 0;
|
|
95
|
-
/**
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Blocks scheduled for the next flush. `sorted` tracks whether they were
|
|
98
|
+
* pushed in creation order, so the common case avoids a sort.
|
|
99
|
+
* @typedef {{ blocks: Block[]; sorted: boolean; last: number }} Queue
|
|
100
|
+
*/
|
|
101
|
+
/** @type {Queue} */
|
|
102
|
+
let queue = create_queue();
|
|
103
|
+
|
|
104
|
+
/** @returns {Queue} */
|
|
105
|
+
function create_queue() {
|
|
106
|
+
return { blocks: [], sorted: true, last: 0 };
|
|
107
|
+
}
|
|
103
108
|
/** @type {(() => void)[]} */
|
|
104
109
|
let queued_microtasks = [];
|
|
105
110
|
/** @type {number} */
|
|
@@ -213,6 +218,7 @@ function update_derived(computed) {
|
|
|
213
218
|
function update_tracked_value_clock(tracked, value) {
|
|
214
219
|
tracked.__v = value;
|
|
215
220
|
tracked.c = increment_clock();
|
|
221
|
+
mark_subscribers(tracked);
|
|
216
222
|
}
|
|
217
223
|
|
|
218
224
|
/**
|
|
@@ -252,11 +258,11 @@ function run_derived(computed) {
|
|
|
252
258
|
|
|
253
259
|
var value = computed.fn();
|
|
254
260
|
|
|
255
|
-
computed
|
|
261
|
+
finish_dependencies(computed, active_dependency);
|
|
256
262
|
|
|
257
263
|
return value;
|
|
258
264
|
} catch (error) {
|
|
259
|
-
computed
|
|
265
|
+
finish_dependencies(computed, active_dependency);
|
|
260
266
|
if (error === ASYNC_DERIVED_READ_THROWN) {
|
|
261
267
|
// Check if any dependency is rejected — if so, propagate rejection
|
|
262
268
|
var dep = active_dependency;
|
|
@@ -296,8 +302,10 @@ export function handle_error(error, block) {
|
|
|
296
302
|
|
|
297
303
|
/**
|
|
298
304
|
* @param {Block} block
|
|
305
|
+
* @param {boolean} [first_run] true when the block has no children, teardown,
|
|
306
|
+
* or dependencies yet, so that cleanup can be skipped
|
|
299
307
|
*/
|
|
300
|
-
export function run_block(block) {
|
|
308
|
+
export function run_block(block, first_run = false) {
|
|
301
309
|
var previous_block = active_block;
|
|
302
310
|
var previous_reaction = active_reaction;
|
|
303
311
|
var previous_tracking = tracking;
|
|
@@ -309,8 +317,13 @@ export function run_block(block) {
|
|
|
309
317
|
active_reaction = block;
|
|
310
318
|
active_component = block.co;
|
|
311
319
|
|
|
312
|
-
|
|
313
|
-
|
|
320
|
+
if (!first_run) {
|
|
321
|
+
// A list's children are all item branches, so there is nothing to sweep.
|
|
322
|
+
if ((block.f & FOR_BLOCK) === 0) {
|
|
323
|
+
destroy_non_branch_children(block);
|
|
324
|
+
}
|
|
325
|
+
run_teardown(block);
|
|
326
|
+
}
|
|
314
327
|
|
|
315
328
|
tracking = (block.f & (ROOT_BLOCK | BRANCH_BLOCK)) === 0;
|
|
316
329
|
active_dependency = null;
|
|
@@ -327,11 +340,11 @@ export function run_block(block) {
|
|
|
327
340
|
}
|
|
328
341
|
}
|
|
329
342
|
|
|
330
|
-
block
|
|
343
|
+
finish_dependencies(block, active_dependency);
|
|
331
344
|
} catch (error) {
|
|
332
345
|
var is_component_direct = false;
|
|
333
346
|
var is_try_fn_block = false;
|
|
334
|
-
block
|
|
347
|
+
finish_dependencies(block, active_dependency);
|
|
335
348
|
// When a derived read throws ASYNC_DERIVED_READ_THROWN, it means the
|
|
336
349
|
// derived is still SUSPENSE_PENDING. The dependency was already registered,
|
|
337
350
|
// so we swallow the throw and let the parent continue processing. When
|
|
@@ -420,8 +433,10 @@ class TrackedValue {
|
|
|
420
433
|
this.d = null;
|
|
421
434
|
/** @type {number} */
|
|
422
435
|
this.f = TRACKED;
|
|
423
|
-
/** @type {
|
|
436
|
+
/** @type {any} hydration hash, or the key of a selector match flag */
|
|
424
437
|
this.h = hash;
|
|
438
|
+
/** @type {Dependency | null} */
|
|
439
|
+
this.sb = null;
|
|
425
440
|
/** @type {any} */
|
|
426
441
|
this.__v = v;
|
|
427
442
|
}
|
|
@@ -482,6 +497,8 @@ class DerivedValue {
|
|
|
482
497
|
this.fn = fn;
|
|
483
498
|
/** @type {string | undefined} */
|
|
484
499
|
this.h = hash;
|
|
500
|
+
/** @type {Dependency | null} */
|
|
501
|
+
this.sb = null;
|
|
485
502
|
/** @type {any} */
|
|
486
503
|
this.__v = UNINITIALIZED;
|
|
487
504
|
}
|
|
@@ -696,7 +713,6 @@ export function track_async(fn, b, hash) {
|
|
|
696
713
|
// Set to pending before calling fn() in case it's sync.
|
|
697
714
|
if (t.__v !== SUSPENSE_PENDING) {
|
|
698
715
|
update_tracked_value_clock(t, SUSPENSE_PENDING);
|
|
699
|
-
schedule_update(t.b);
|
|
700
716
|
}
|
|
701
717
|
|
|
702
718
|
// Temporarily allow mutations so set() doesn't throw inside the pre-effect
|
|
@@ -715,7 +731,6 @@ export function track_async(fn, b, hash) {
|
|
|
715
731
|
while (dep !== null) {
|
|
716
732
|
if (dep.t.__v === SUSPENSE_REJECTED) {
|
|
717
733
|
update_tracked_value_clock(t, SUSPENSE_REJECTED);
|
|
718
|
-
schedule_update(t.b);
|
|
719
734
|
complete_deferred_boundaries(t, false);
|
|
720
735
|
if (request_id > 0 && boundary !== null) {
|
|
721
736
|
complete_boundary_request(boundary, request_id, false);
|
|
@@ -748,7 +763,6 @@ export function track_async(fn, b, hash) {
|
|
|
748
763
|
if (async_result === null) {
|
|
749
764
|
// Sync result
|
|
750
765
|
update_tracked_value_clock(t, result);
|
|
751
|
-
schedule_update(t.b);
|
|
752
766
|
if (request_id > 0 && boundary !== null) {
|
|
753
767
|
complete_boundary_request(boundary, request_id);
|
|
754
768
|
request_id = 0;
|
|
@@ -769,7 +783,6 @@ export function track_async(fn, b, hash) {
|
|
|
769
783
|
return;
|
|
770
784
|
}
|
|
771
785
|
update_tracked_value_clock(t, resolved);
|
|
772
|
-
schedule_update(t.b);
|
|
773
786
|
complete_deferred_boundaries(t);
|
|
774
787
|
if (request_id > 0 && boundary !== null) {
|
|
775
788
|
complete_boundary_request(boundary, request_id);
|
|
@@ -792,7 +805,6 @@ export function track_async(fn, b, hash) {
|
|
|
792
805
|
}
|
|
793
806
|
|
|
794
807
|
update_tracked_value_clock(t, SUSPENSE_REJECTED);
|
|
795
|
-
schedule_update(t.b);
|
|
796
808
|
complete_deferred_boundaries(t, false);
|
|
797
809
|
|
|
798
810
|
// Route error to catch boundary
|
|
@@ -865,16 +877,151 @@ function create_dependency(tracked) {
|
|
|
865
877
|
if (existing !== null) {
|
|
866
878
|
reaction.d = existing.n;
|
|
867
879
|
existing.c = tracked.c;
|
|
868
|
-
existing.t = tracked;
|
|
869
880
|
existing.n = null;
|
|
881
|
+
if (existing.t !== tracked) {
|
|
882
|
+
unlink_subscriber(existing);
|
|
883
|
+
existing.t = tracked;
|
|
884
|
+
link_subscriber(existing, tracked);
|
|
885
|
+
}
|
|
870
886
|
return existing;
|
|
871
887
|
}
|
|
872
888
|
|
|
873
|
-
|
|
889
|
+
/** @type {Dependency} */
|
|
890
|
+
var dependency = {
|
|
874
891
|
c: tracked.c,
|
|
875
892
|
t: tracked,
|
|
876
893
|
n: null,
|
|
894
|
+
r: reaction,
|
|
895
|
+
sp: null,
|
|
896
|
+
sn: null,
|
|
877
897
|
};
|
|
898
|
+
link_subscriber(dependency, tracked);
|
|
899
|
+
return dependency;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* @param {Dependency} dependency
|
|
904
|
+
* @param {Tracked | Derived} tracked
|
|
905
|
+
*/
|
|
906
|
+
function link_subscriber(dependency, tracked) {
|
|
907
|
+
var head = tracked.sb;
|
|
908
|
+
dependency.sn = head;
|
|
909
|
+
if (head !== null) {
|
|
910
|
+
head.sp = dependency;
|
|
911
|
+
}
|
|
912
|
+
tracked.sb = dependency;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* @param {Dependency} dependency
|
|
917
|
+
*/
|
|
918
|
+
export function unlink_subscriber(dependency) {
|
|
919
|
+
var prev = dependency.sp;
|
|
920
|
+
var next = dependency.sn;
|
|
921
|
+
if (prev !== null) {
|
|
922
|
+
prev.sn = next;
|
|
923
|
+
} else {
|
|
924
|
+
var tracked = dependency.t;
|
|
925
|
+
// Already unlinked (a destroyed block's dependency can be pruned by a
|
|
926
|
+
// write during its own teardown, then unlinked again by
|
|
927
|
+
// remove_dependencies); it must not be mistaken for the list head.
|
|
928
|
+
if (tracked.sb !== dependency) {
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
tracked.sb = next;
|
|
932
|
+
if (next === null && (tracked.f & SELECTOR) !== 0) {
|
|
933
|
+
// Last subscriber gone: release the selector's per-key entry.
|
|
934
|
+
/** @type {SelectorAccessors} */ (tracked.a).m.delete(tracked.h);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
if (next !== null) {
|
|
938
|
+
next.sp = prev;
|
|
939
|
+
}
|
|
940
|
+
dependency.sp = dependency.sn = null;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* @typedef {{ get: undefined; set: undefined; m: Map<any, Tracked> }} SelectorAccessors
|
|
945
|
+
*/
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* A tracked match flag for one selector key. The selector's shared accessor
|
|
949
|
+
* object carries the map and the key rides in the hash slot, so the entry can
|
|
950
|
+
* be released from `unlink_subscriber` without an object per key.
|
|
951
|
+
* @param {boolean} value
|
|
952
|
+
* @param {Block} block
|
|
953
|
+
* @param {SelectorAccessors} accessors
|
|
954
|
+
* @param {any} key
|
|
955
|
+
* @returns {Tracked}
|
|
956
|
+
*/
|
|
957
|
+
export function selector_tracked(value, block, accessors, key) {
|
|
958
|
+
var t = /** @type {Tracked} */ (new TrackedValue(value, block, accessors, key));
|
|
959
|
+
t.f |= SELECTOR;
|
|
960
|
+
return t;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* Install the dependency chain produced by a run, unsubscribing from any
|
|
965
|
+
* previous dependencies that were not read again.
|
|
966
|
+
* @param {Block | Derived} reaction
|
|
967
|
+
* @param {Dependency | null} dependencies
|
|
968
|
+
*/
|
|
969
|
+
function finish_dependencies(reaction, dependencies) {
|
|
970
|
+
var stale = reaction.d;
|
|
971
|
+
while (stale !== null) {
|
|
972
|
+
unlink_subscriber(stale);
|
|
973
|
+
stale = stale.n;
|
|
974
|
+
}
|
|
975
|
+
reaction.d = dependencies;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Unsubscribe a block from every tracked value it read. Dependencies on
|
|
980
|
+
* values owned by an already-destroyed block are dropped without unlinking,
|
|
981
|
+
* since the owner's subscriber list dies with it.
|
|
982
|
+
* @param {Block} block
|
|
983
|
+
*/
|
|
984
|
+
export function remove_dependencies(block) {
|
|
985
|
+
var dependency = block.d;
|
|
986
|
+
while (dependency !== null) {
|
|
987
|
+
var owner = dependency.t.b;
|
|
988
|
+
if (owner === null || (owner.f & DESTROYED) === 0) {
|
|
989
|
+
unlink_subscriber(dependency);
|
|
990
|
+
}
|
|
991
|
+
dependency = dependency.n;
|
|
992
|
+
}
|
|
993
|
+
block.d = null;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Schedule every reaction subscribed to `tracked`. Deriveds are lazy, so their
|
|
998
|
+
* own subscribers are marked instead; subscribers that were destroyed without
|
|
999
|
+
* unlinking are pruned here.
|
|
1000
|
+
* @param {Tracked | Derived} tracked
|
|
1001
|
+
*/
|
|
1002
|
+
function mark_subscribers(tracked) {
|
|
1003
|
+
var dependency = tracked.sb;
|
|
1004
|
+
while (dependency !== null) {
|
|
1005
|
+
var next = dependency.sn;
|
|
1006
|
+
var reaction = dependency.r;
|
|
1007
|
+
var flags = reaction.f;
|
|
1008
|
+
if ((flags & DERIVED) !== 0) {
|
|
1009
|
+
var derived = /** @type {Derived} */ (reaction);
|
|
1010
|
+
var derived_owner = derived.b;
|
|
1011
|
+
// A derived whose owner is gone and that nothing reads any more is
|
|
1012
|
+
// pruned; one still read elsewhere keeps forwarding notifications.
|
|
1013
|
+
if (derived_owner !== null && (derived_owner.f & DESTROYED) !== 0 && derived.sb === null) {
|
|
1014
|
+
unlink_subscriber(dependency);
|
|
1015
|
+
} else {
|
|
1016
|
+
mark_subscribers(derived);
|
|
1017
|
+
}
|
|
1018
|
+
} else if ((flags & DESTROYED) !== 0) {
|
|
1019
|
+
unlink_subscriber(dependency);
|
|
1020
|
+
} else {
|
|
1021
|
+
schedule_update(/** @type {Block} */ (reaction));
|
|
1022
|
+
}
|
|
1023
|
+
dependency = next;
|
|
1024
|
+
}
|
|
878
1025
|
}
|
|
879
1026
|
|
|
880
1027
|
/**
|
|
@@ -941,101 +1088,74 @@ function trigger_track_get(fn, v) {
|
|
|
941
1088
|
}
|
|
942
1089
|
|
|
943
1090
|
/**
|
|
944
|
-
* @param {Block}
|
|
1091
|
+
* @param {Block} a
|
|
1092
|
+
* @param {Block} b
|
|
1093
|
+
* @returns {number}
|
|
945
1094
|
*/
|
|
946
|
-
function
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
var pre_effects = [];
|
|
950
|
-
var other_blocks = [];
|
|
951
|
-
var effects = [];
|
|
952
|
-
// The nearest enclosing directly-scheduled block ("update source"). While it is
|
|
953
|
-
// non-null we are inside a source's subtree and scan every descendant. Above
|
|
954
|
-
// sources we only follow the CONTAINS_UPDATE routing path, so sibling subtrees
|
|
955
|
-
// that contain no update are skipped. When scoping is disabled we treat the
|
|
956
|
-
// whole root tree as one source — the original full-tree scan.
|
|
957
|
-
/** @type {Block | null} */
|
|
958
|
-
var scope_root = disable_scoped_flush ? root_block : null;
|
|
1095
|
+
function by_block_id(a, b) {
|
|
1096
|
+
return a.i - b.i;
|
|
1097
|
+
}
|
|
959
1098
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1099
|
+
/**
|
|
1100
|
+
* Run every scheduled block, in creation order (so a parent runs before its
|
|
1101
|
+
* descendants), in three phases: pre-effects, render blocks, then effects.
|
|
1102
|
+
* @param {Queue} pending
|
|
1103
|
+
*/
|
|
1104
|
+
function flush_queue(pending) {
|
|
1105
|
+
var blocks = pending.blocks;
|
|
963
1106
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1107
|
+
if (!pending.sorted) {
|
|
1108
|
+
blocks.sort(by_block_id);
|
|
1109
|
+
}
|
|
967
1110
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1111
|
+
/** @type {Block[]} */
|
|
1112
|
+
var pre_effects = [];
|
|
1113
|
+
/** @type {Block[]} */
|
|
1114
|
+
var other_blocks = [];
|
|
1115
|
+
/** @type {Block[]} */
|
|
1116
|
+
var effects = [];
|
|
974
1117
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
effects.push(current);
|
|
980
|
-
} else {
|
|
981
|
-
other_blocks.push(current);
|
|
982
|
-
}
|
|
983
|
-
/** @type {Block | null} */
|
|
984
|
-
var child = current.first;
|
|
1118
|
+
for (var i = 0; i < blocks.length; i++) {
|
|
1119
|
+
var block = blocks[i];
|
|
1120
|
+
var flags = block.f;
|
|
1121
|
+
block.f = flags & ~SCHEDULED;
|
|
985
1122
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
}
|
|
1123
|
+
// A paused block is re-checked when it resumes; a destroyed one is gone.
|
|
1124
|
+
if ((flags & (PAUSED | DESTROYED)) !== 0) {
|
|
1125
|
+
continue;
|
|
990
1126
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
if (parent === scope_root) {
|
|
998
|
-
scope_root = null;
|
|
999
|
-
}
|
|
1000
|
-
current = parent.next;
|
|
1001
|
-
parent = parent.p;
|
|
1127
|
+
if ((flags & PRE_EFFECT_BLOCK) !== 0) {
|
|
1128
|
+
pre_effects.push(block);
|
|
1129
|
+
} else if ((flags & EFFECT_BLOCK) !== 0) {
|
|
1130
|
+
effects.push(block);
|
|
1131
|
+
} else {
|
|
1132
|
+
other_blocks.push(block);
|
|
1002
1133
|
}
|
|
1003
1134
|
}
|
|
1004
1135
|
|
|
1005
|
-
|
|
1136
|
+
blocks.length = 0;
|
|
1137
|
+
pending.sorted = true;
|
|
1138
|
+
pending.last = 0;
|
|
1006
1139
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
var block = pre_effects[i];
|
|
1140
|
+
run_phase(pre_effects);
|
|
1141
|
+
run_phase(other_blocks);
|
|
1142
|
+
run_phase(effects);
|
|
1011
1143
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
handle_error(error, block);
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
// Phase 2: all other blocks except effects
|
|
1022
|
-
arr_length = other_blocks.length;
|
|
1023
|
-
for (var i = 0; i < arr_length; i++) {
|
|
1024
|
-
var block = other_blocks[i];
|
|
1025
|
-
|
|
1026
|
-
try {
|
|
1027
|
-
if ((block.f & (PAUSED | DESTROYED)) === 0 && is_block_dirty(block)) {
|
|
1028
|
-
run_block(block);
|
|
1029
|
-
}
|
|
1030
|
-
} catch (error) {
|
|
1031
|
-
handle_error(error, block);
|
|
1144
|
+
if (queued_post_block_flush.length > 0) {
|
|
1145
|
+
var callbacks = queued_post_block_flush;
|
|
1146
|
+
queued_post_block_flush = [];
|
|
1147
|
+
for (var j = 0; j < callbacks.length; j++) {
|
|
1148
|
+
callbacks[j]();
|
|
1032
1149
|
}
|
|
1033
1150
|
}
|
|
1151
|
+
}
|
|
1034
1152
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1153
|
+
/**
|
|
1154
|
+
* @param {Block[]} blocks
|
|
1155
|
+
*/
|
|
1156
|
+
function run_phase(blocks) {
|
|
1157
|
+
for (var i = 0; i < blocks.length; i++) {
|
|
1158
|
+
var block = blocks[i];
|
|
1039
1159
|
|
|
1040
1160
|
try {
|
|
1041
1161
|
if ((block.f & (PAUSED | DESTROYED)) === 0 && is_block_dirty(block)) {
|
|
@@ -1047,23 +1167,6 @@ function flush_updates(root_block) {
|
|
|
1047
1167
|
}
|
|
1048
1168
|
}
|
|
1049
1169
|
|
|
1050
|
-
/**
|
|
1051
|
-
* @param {Block[]} root_blocks
|
|
1052
|
-
*/
|
|
1053
|
-
function flush_queued_root_blocks(root_blocks) {
|
|
1054
|
-
for (let i = 0; i < root_blocks.length; i++) {
|
|
1055
|
-
flush_updates(root_blocks[i]);
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
if (queued_post_block_flush.length > 0) {
|
|
1059
|
-
var callbacks = queued_post_block_flush;
|
|
1060
|
-
queued_post_block_flush = [];
|
|
1061
|
-
for (var j = 0; j < callbacks.length; j++) {
|
|
1062
|
-
callbacks[j]();
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
1170
|
/**
|
|
1068
1171
|
* @returns {Promise<void>}
|
|
1069
1172
|
*/
|
|
@@ -1091,9 +1194,9 @@ function flush_microtasks() {
|
|
|
1091
1194
|
'Maximum update depth exceeded. This typically indicates that an effect reads and writes the same piece of state.',
|
|
1092
1195
|
);
|
|
1093
1196
|
}
|
|
1094
|
-
var
|
|
1095
|
-
|
|
1096
|
-
|
|
1197
|
+
var pending = queue;
|
|
1198
|
+
queue = create_queue();
|
|
1199
|
+
flush_queue(pending);
|
|
1097
1200
|
|
|
1098
1201
|
if (!is_micro_task_queued) {
|
|
1099
1202
|
flush_count = 0;
|
|
@@ -1128,64 +1231,27 @@ export function queue_post_block_flush_callback(fn) {
|
|
|
1128
1231
|
* @param {Block} block
|
|
1129
1232
|
*/
|
|
1130
1233
|
export function schedule_update(block) {
|
|
1234
|
+
if ((block.f & SCHEDULED) !== 0) {
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
block.f |= SCHEDULED;
|
|
1238
|
+
|
|
1131
1239
|
if (scheduler_mode === FLUSH_MICROTASK) {
|
|
1132
1240
|
queue_microtask();
|
|
1133
1241
|
}
|
|
1134
|
-
// The scheduled block roots the subtree that flush_updates scans for dirty
|
|
1135
|
-
// subscribers. Mark it even if it (or an ancestor) is already on a routing
|
|
1136
|
-
// path, so the early return below still records it as a scan root.
|
|
1137
|
-
block.f |= UPDATE_SOURCE;
|
|
1138
|
-
let current = block;
|
|
1139
|
-
|
|
1140
|
-
while (current !== null) {
|
|
1141
|
-
var flags = current.f;
|
|
1142
|
-
if ((flags & CONTAINS_UPDATE) !== 0) return;
|
|
1143
|
-
current.f ^= CONTAINS_UPDATE;
|
|
1144
|
-
if ((flags & ROOT_BLOCK) !== 0) {
|
|
1145
|
-
break;
|
|
1146
|
-
}
|
|
1147
|
-
current = /** @type {Block} */ (current.p);
|
|
1148
|
-
}
|
|
1149
1242
|
|
|
1150
|
-
|
|
1243
|
+
var id = block.i;
|
|
1244
|
+
if (id < queue.last) {
|
|
1245
|
+
queue.sorted = false;
|
|
1246
|
+
}
|
|
1247
|
+
queue.last = id;
|
|
1248
|
+
queue.blocks.push(block);
|
|
1151
1249
|
}
|
|
1152
1250
|
|
|
1153
1251
|
/**
|
|
1154
1252
|
* @param {Tracked | Derived} tracked
|
|
1155
1253
|
*/
|
|
1156
1254
|
function register_dependency(tracked) {
|
|
1157
|
-
if (!disable_scoped_flush && active_block !== null && active_block !== tracked.b) {
|
|
1158
|
-
// Scoped flush only scans the owner's subtree for dirty subscribers, valid
|
|
1159
|
-
// only while every subscriber lives inside it. The subscriber↔owner
|
|
1160
|
-
// ancestry is structural and stable for a block's lifetime (its `.p` chain
|
|
1161
|
-
// never changes — DOM moves don't reparent blocks), so we only need to
|
|
1162
|
-
// verify *new* dependency edges: if this reaction already depended on
|
|
1163
|
-
// `tracked` on its previous run (it's in the prior chain), the ancestry
|
|
1164
|
-
// walk was already done — skip it. This keeps the (possibly deep) walk off
|
|
1165
|
-
// the hot path for the common case of stable subscriptions.
|
|
1166
|
-
var already_seen = false;
|
|
1167
|
-
var prev_dep = active_reaction === null ? null : active_reaction.d;
|
|
1168
|
-
while (prev_dep !== null) {
|
|
1169
|
-
if (prev_dep.t === tracked) {
|
|
1170
|
-
already_seen = true;
|
|
1171
|
-
break;
|
|
1172
|
-
}
|
|
1173
|
-
prev_dep = prev_dep.n;
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
if (!already_seen) {
|
|
1177
|
-
var owner = tracked.b;
|
|
1178
|
-
/** @type {Block | null} */
|
|
1179
|
-
var node = active_block;
|
|
1180
|
-
while (node !== null && node !== owner) {
|
|
1181
|
-
node = node.p;
|
|
1182
|
-
}
|
|
1183
|
-
if (node === null) {
|
|
1184
|
-
disable_scoped_flush = true;
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
1255
|
var dependency = active_dependency;
|
|
1190
1256
|
|
|
1191
1257
|
if (dependency === null) {
|
|
@@ -1406,7 +1472,7 @@ export function set(tracked, value) {
|
|
|
1406
1472
|
|
|
1407
1473
|
tracked.__v = value;
|
|
1408
1474
|
tracked.c = increment_clock();
|
|
1409
|
-
|
|
1475
|
+
mark_subscribers(tracked);
|
|
1410
1476
|
}
|
|
1411
1477
|
}
|
|
1412
1478
|
|
|
@@ -1435,21 +1501,19 @@ export function untrack(fn) {
|
|
|
1435
1501
|
*/
|
|
1436
1502
|
export function flush_sync(fn) {
|
|
1437
1503
|
var previous_scheduler_mode = scheduler_mode;
|
|
1438
|
-
var
|
|
1504
|
+
var previous_queue = queue;
|
|
1439
1505
|
|
|
1440
1506
|
try {
|
|
1441
|
-
/** @type {Block[]} */
|
|
1442
|
-
var root_blocks = [];
|
|
1443
|
-
|
|
1444
1507
|
scheduler_mode = FLUSH_SYNC;
|
|
1445
|
-
|
|
1508
|
+
queue = create_queue();
|
|
1446
1509
|
is_micro_task_queued = false;
|
|
1447
1510
|
|
|
1448
|
-
|
|
1511
|
+
// Drains previous_queue, which then stays empty for the restore below.
|
|
1512
|
+
flush_queue(previous_queue);
|
|
1449
1513
|
|
|
1450
1514
|
var result = fn?.();
|
|
1451
1515
|
|
|
1452
|
-
if (
|
|
1516
|
+
if (queue.blocks.length > 0) {
|
|
1453
1517
|
flush_sync();
|
|
1454
1518
|
}
|
|
1455
1519
|
|
|
@@ -1458,7 +1522,7 @@ export function flush_sync(fn) {
|
|
|
1458
1522
|
return /** @type {T} */ (result);
|
|
1459
1523
|
} finally {
|
|
1460
1524
|
scheduler_mode = previous_scheduler_mode;
|
|
1461
|
-
|
|
1525
|
+
queue = previous_queue;
|
|
1462
1526
|
}
|
|
1463
1527
|
}
|
|
1464
1528
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** @import { Block, Tracked } from '#client' */
|
|
2
|
+
|
|
3
|
+
import { render } from './blocks.js';
|
|
4
|
+
import { UNINITIALIZED } from './constants.js';
|
|
5
|
+
import { get_tracked, selector_tracked, set } from './runtime.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {{ v: any; m: Map<any, Tracked>; b: Block; a: import('./runtime.js').SelectorAccessors }} Selector
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Shares one reactive source across many `source === key` comparisons: each
|
|
13
|
+
* key subscribes to its own match flag, so a change of the source only
|
|
14
|
+
* notifies the blocks for the previous and next key rather than every block
|
|
15
|
+
* that compares against it. Used by the compiler for comparisons between a
|
|
16
|
+
* loop item and outer state inside `@for` templates.
|
|
17
|
+
* @param {() => any} get_source
|
|
18
|
+
* @returns {Selector}
|
|
19
|
+
*/
|
|
20
|
+
export function selector(get_source) {
|
|
21
|
+
var m = new Map();
|
|
22
|
+
/** @type {Selector} */
|
|
23
|
+
var s = {
|
|
24
|
+
v: UNINITIALIZED,
|
|
25
|
+
m,
|
|
26
|
+
b: /** @type {Block} */ (/** @type {unknown} */ (null)),
|
|
27
|
+
a: { get: undefined, set: undefined, m },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
s.b = render(() => {
|
|
31
|
+
var next = get_source();
|
|
32
|
+
var prev = s.v;
|
|
33
|
+
if (next === prev) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
s.v = next;
|
|
37
|
+
var m = s.m;
|
|
38
|
+
if (m.size === 0) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// NaN never matches with ===, so it never toggles a flag.
|
|
42
|
+
if (prev === prev) {
|
|
43
|
+
var previous_match = m.get(prev);
|
|
44
|
+
if (previous_match !== undefined) {
|
|
45
|
+
set(previous_match, false);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (next === next) {
|
|
49
|
+
var next_match = m.get(next);
|
|
50
|
+
if (next_match !== undefined) {
|
|
51
|
+
set(next_match, true);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return s;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* `source === key`, read inside a render block so the block re-runs only when
|
|
61
|
+
* the match for this key changes.
|
|
62
|
+
* @param {Selector} s
|
|
63
|
+
* @param {any} key
|
|
64
|
+
* @returns {boolean}
|
|
65
|
+
*/
|
|
66
|
+
export function selector_match(s, key) {
|
|
67
|
+
var m = s.m;
|
|
68
|
+
var match = m.get(key);
|
|
69
|
+
if (match === undefined) {
|
|
70
|
+
match = selector_tracked(key === s.v, s.b, s.a, key);
|
|
71
|
+
m.set(key, match);
|
|
72
|
+
}
|
|
73
|
+
return get_tracked(match);
|
|
74
|
+
}
|