ripple 0.3.24 → 0.3.26
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 +45 -0
- package/package.json +5 -5
- package/src/runtime/index-client.js +4 -0
- package/src/runtime/internal/client/hmr.js +1 -1
- package/src/runtime/internal/client/hydration.js +14 -0
- package/src/runtime/internal/client/runtime.js +127 -31
- package/src/runtime/internal/client/types.d.ts +3 -33
- package/src/runtime/internal/server/blocks.js +21 -1
- package/src/runtime/internal/server/index.js +299 -34
- package/src/runtime/internal/server/types.d.ts +3 -31
- package/src/runtime/reactive-value.js +1 -0
- package/src/utils/escaping.js +11 -0
- package/src/utils/track-async-serialization.js +9 -0
- package/tests/client/async-suspend.test.tsrx +11 -1
- package/tests/client/compiler/compiler.basic.test.tsrx +18 -3
- package/tests/client/track-async-hydration.test.tsrx +54 -0
- package/tests/hydration/compiled/client/basic.js +1 -1
- package/tests/hydration/compiled/client/events.js +8 -8
- package/tests/hydration/compiled/client/for.js +22 -24
- package/tests/hydration/compiled/client/head.js +6 -6
- package/tests/hydration/compiled/client/hmr.js +1 -1
- package/tests/hydration/compiled/client/html.js +1 -1
- package/tests/hydration/compiled/client/if-children.js +7 -7
- package/tests/hydration/compiled/client/if.js +5 -5
- package/tests/hydration/compiled/client/mixed-control-flow.js +4 -4
- package/tests/hydration/compiled/client/portal.js +1 -1
- package/tests/hydration/compiled/client/reactivity.js +9 -9
- package/tests/hydration/compiled/client/return.js +23 -23
- package/tests/hydration/compiled/client/switch.js +4 -4
- package/tests/hydration/compiled/client/track-async-serialization.js +390 -0
- package/tests/hydration/compiled/client/try.js +2 -2
- package/tests/hydration/compiled/server/basic.js +1 -1
- package/tests/hydration/compiled/server/events.js +8 -8
- package/tests/hydration/compiled/server/for.js +34 -28
- package/tests/hydration/compiled/server/head.js +6 -6
- package/tests/hydration/compiled/server/hmr.js +1 -1
- package/tests/hydration/compiled/server/html.js +1 -1
- package/tests/hydration/compiled/server/if-children.js +7 -7
- package/tests/hydration/compiled/server/if.js +5 -5
- package/tests/hydration/compiled/server/mixed-control-flow.js +4 -4
- package/tests/hydration/compiled/server/portal.js +1 -1
- package/tests/hydration/compiled/server/reactivity.js +9 -9
- package/tests/hydration/compiled/server/return.js +11 -11
- package/tests/hydration/compiled/server/switch.js +4 -4
- package/tests/hydration/compiled/server/track-async-serialization.js +502 -0
- package/tests/hydration/compiled/server/try.js +2 -2
- package/tests/hydration/components/track-async-serialization.tsrx +116 -0
- package/tests/hydration/track-async-serialization.test.js +127 -0
- package/tests/server/track-async-serialization.test.tsrx +185 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import { Component, Dependency,
|
|
2
|
+
* @import { Component, Dependency, Block, TryBlockWithCatch } from '#server';
|
|
3
3
|
* @import { NestedArray } from '#helpers';
|
|
4
4
|
* @import { Props } from '#public';
|
|
5
5
|
* @import { RenderResult, BaseRenderOptions, RenderStreamResult, Stream, StreamSink } from 'ripple/server';
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
/** @typedef {{ tag: string; parent: undefined | ElementContext; filename: undefined | string; line: number; column: number; }} ElementContext */
|
|
14
14
|
/** @typedef {{ cancel: () => void }} RegisteredAsyncOperation */
|
|
15
15
|
|
|
16
|
+
// Both
|
|
17
|
+
/** @typedef {TrackedValue} Tracked */
|
|
18
|
+
/** @typedef {DerivedValue} Derived */
|
|
19
|
+
|
|
16
20
|
import {
|
|
17
21
|
DERIVED,
|
|
18
22
|
UNINITIALIZED,
|
|
@@ -22,8 +26,9 @@ import {
|
|
|
22
26
|
ASYNC_DERIVED_READ_THROWN,
|
|
23
27
|
DERIVED_UPDATED,
|
|
24
28
|
} from '../client/constants.js';
|
|
29
|
+
import { DEV } from 'esm-env';
|
|
25
30
|
import { is_ripple_object, array_slice } from '../client/utils.js';
|
|
26
|
-
import { escape } from '../../../utils/escaping.js';
|
|
31
|
+
import { escape, escape_script } from '../../../utils/escaping.js';
|
|
27
32
|
import { is_boolean_attribute } from '../../../utils/attributes.js';
|
|
28
33
|
import { clsx } from 'clsx';
|
|
29
34
|
import { normalize_css_property_name } from '../../../utils/normalize_css_property_name.js';
|
|
@@ -34,6 +39,8 @@ import {
|
|
|
34
39
|
is_tag_valid_with_ancestor,
|
|
35
40
|
} from '../../../html-tree-validation.js';
|
|
36
41
|
import { get_async_track_result } from '../../../utils/async.js';
|
|
42
|
+
import { get_track_async_script_id } from '../../../utils/track-async-serialization.js';
|
|
43
|
+
import * as devalue from 'devalue';
|
|
37
44
|
import {
|
|
38
45
|
cancel_async_operations,
|
|
39
46
|
component_block,
|
|
@@ -50,6 +57,24 @@ export { try_block, component_block, regular_block } from './blocks.js';
|
|
|
50
57
|
export { array_slice };
|
|
51
58
|
export { tsrx_element, normalize_children };
|
|
52
59
|
|
|
60
|
+
/** @extends Error */
|
|
61
|
+
export class TrackAsyncRunError extends Error {
|
|
62
|
+
/** @type {Tracked} */
|
|
63
|
+
tracked;
|
|
64
|
+
/** @type {Error} */
|
|
65
|
+
cause;
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} message
|
|
68
|
+
* @param {{tracked: Tracked, cause: Error}} options
|
|
69
|
+
*/
|
|
70
|
+
constructor(message, options) {
|
|
71
|
+
super(message);
|
|
72
|
+
this.name = 'TrackAsyncRunError';
|
|
73
|
+
this.tracked = options.tracked;
|
|
74
|
+
this.cause = options.cause;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
53
78
|
export function noop() {}
|
|
54
79
|
|
|
55
80
|
/**
|
|
@@ -400,9 +425,11 @@ export class Output {
|
|
|
400
425
|
|
|
401
426
|
/**
|
|
402
427
|
* @param {string} str
|
|
428
|
+
* @param {boolean} [is_root=false]
|
|
429
|
+
* @param {boolean} [is_prepend=false]
|
|
403
430
|
* @returns {void}
|
|
404
431
|
*/
|
|
405
|
-
push(str) {
|
|
432
|
+
#push(str, is_root = false, is_prepend = false) {
|
|
406
433
|
if (this.isStreamMode() && !this.isSyncRun()) {
|
|
407
434
|
// TODO - we need to wrap the resulting block output into something that
|
|
408
435
|
// the client-side can understand and append them appropriately,
|
|
@@ -413,12 +440,49 @@ export class Output {
|
|
|
413
440
|
return;
|
|
414
441
|
}
|
|
415
442
|
|
|
416
|
-
|
|
417
|
-
|
|
443
|
+
var instance = is_root ? this.#root : this;
|
|
444
|
+
|
|
445
|
+
// we never write to `head` in the root instance
|
|
446
|
+
if (instance !== this.#root && instance.target === 'head') {
|
|
447
|
+
if (is_prepend) {
|
|
448
|
+
instance.#head.unshift(str);
|
|
449
|
+
} else {
|
|
450
|
+
instance.#head.push(str);
|
|
451
|
+
}
|
|
418
452
|
return;
|
|
419
453
|
}
|
|
420
454
|
|
|
421
|
-
|
|
455
|
+
if (is_prepend) {
|
|
456
|
+
instance.#body.unshift(str);
|
|
457
|
+
} else {
|
|
458
|
+
instance.#body.push(str);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* @param {string} str
|
|
464
|
+
* @returns {void}
|
|
465
|
+
*/
|
|
466
|
+
push(str) {
|
|
467
|
+
this.#push(str);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* @param {string} str
|
|
472
|
+
* @returns {void}
|
|
473
|
+
*/
|
|
474
|
+
push_serialized_error(str) {
|
|
475
|
+
// prepend to the root block to avoid messing up the hydration markers
|
|
476
|
+
// writing to the root to avoid being cleared in the local instance when an error occurs
|
|
477
|
+
this.#push(str, true, true);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* @param {string} str
|
|
482
|
+
* @returns {void}
|
|
483
|
+
*/
|
|
484
|
+
push_serialized_result(str) {
|
|
485
|
+
this.#push(str);
|
|
422
486
|
}
|
|
423
487
|
|
|
424
488
|
clear() {
|
|
@@ -674,6 +738,14 @@ export function output_push(str) {
|
|
|
674
738
|
/** @type {Block} */ (active_block).o.push(str);
|
|
675
739
|
}
|
|
676
740
|
|
|
741
|
+
/**
|
|
742
|
+
* @param {string} str
|
|
743
|
+
* @returns {void}
|
|
744
|
+
*/
|
|
745
|
+
export function output_push_serialized_error(str) {
|
|
746
|
+
/** @type {Block} */ (active_block).o.push_serialized_error(str);
|
|
747
|
+
}
|
|
748
|
+
|
|
677
749
|
/**
|
|
678
750
|
* @param {Output['target']} target
|
|
679
751
|
*/
|
|
@@ -958,31 +1030,43 @@ export function spread_attrs(attrs, css_hash) {
|
|
|
958
1030
|
|
|
959
1031
|
var empty_get_set = { get: undefined, set: undefined };
|
|
960
1032
|
|
|
961
|
-
/** @type {Tracked} */
|
|
962
1033
|
class TrackedValue {
|
|
963
1034
|
/**
|
|
964
1035
|
* @param {any} v
|
|
965
1036
|
* @param {{ get?: Function; set?: Function }} a
|
|
1037
|
+
* @param {string} hash
|
|
966
1038
|
*/
|
|
967
|
-
constructor(v, a) {
|
|
1039
|
+
constructor(v, a, hash) {
|
|
1040
|
+
/** @type {{ get?: Function; set?: Function }} */
|
|
968
1041
|
this.a = a;
|
|
969
1042
|
/** @type {AbortController | null} */
|
|
970
1043
|
this.aa = null;
|
|
971
1044
|
/** @type {PromiseLike<any> | null} */
|
|
972
1045
|
this.ap = null;
|
|
1046
|
+
/** @type {Block} */
|
|
1047
|
+
this.b = /** @type {Block} */ (active_block);
|
|
1048
|
+
/** @type {number} */
|
|
973
1049
|
this.c = 0;
|
|
1050
|
+
/** @type {number} */
|
|
974
1051
|
this.f = TRACKED;
|
|
1052
|
+
/** @type {string} */
|
|
1053
|
+
this.h = hash;
|
|
1054
|
+
/** @type {any} */
|
|
975
1055
|
this.v = v;
|
|
976
1056
|
}
|
|
1057
|
+
/** @returns {any} */
|
|
977
1058
|
get [0]() {
|
|
978
1059
|
return get(/** @type {Tracked} */ (this));
|
|
979
1060
|
}
|
|
1061
|
+
/** @param {any} v */
|
|
980
1062
|
set [0](v) {
|
|
981
1063
|
set(/** @type {Tracked} */ (this), v);
|
|
982
1064
|
}
|
|
1065
|
+
/** @returns {Tracked} */
|
|
983
1066
|
get [1]() {
|
|
984
|
-
return this;
|
|
1067
|
+
return /** @type {Tracked} */ (this);
|
|
985
1068
|
}
|
|
1069
|
+
/** @returns {any} */
|
|
986
1070
|
get value() {
|
|
987
1071
|
return get(/** @type {Tracked} */ (this));
|
|
988
1072
|
}
|
|
@@ -994,40 +1078,53 @@ class TrackedValue {
|
|
|
994
1078
|
get length() {
|
|
995
1079
|
return 2;
|
|
996
1080
|
}
|
|
1081
|
+
/** @returns {Iterator<any | Tracked>} */
|
|
997
1082
|
*[Symbol.iterator]() {
|
|
998
1083
|
yield get(/** @type {Tracked} */ (this));
|
|
999
1084
|
yield this;
|
|
1000
1085
|
}
|
|
1001
1086
|
}
|
|
1002
1087
|
|
|
1003
|
-
/** @type {Derived} */
|
|
1004
1088
|
class DerivedValue {
|
|
1005
1089
|
/**
|
|
1006
1090
|
* @param {Function} fn
|
|
1007
1091
|
* @param {{ get?: Function; set?: Function }} a
|
|
1092
|
+
* @param {string} hash
|
|
1008
1093
|
*/
|
|
1009
|
-
constructor(fn, a) {
|
|
1094
|
+
constructor(fn, a, hash) {
|
|
1095
|
+
/** @type {{ get?: Function; set?: Function }} */
|
|
1010
1096
|
this.a = a;
|
|
1011
1097
|
// we always should have an active block
|
|
1012
|
-
|
|
1098
|
+
/** @type {Block} */
|
|
1013
1099
|
this.b = /** @type {Block} */ (active_block);
|
|
1100
|
+
/** @type {number} */
|
|
1014
1101
|
this.c = 0;
|
|
1102
|
+
/** @type {Component | null} */
|
|
1015
1103
|
this.co = active_component;
|
|
1016
1104
|
/** @type {Dependency | null} */
|
|
1017
1105
|
this.d = null;
|
|
1106
|
+
/** @type {number} */
|
|
1018
1107
|
this.f = DERIVED;
|
|
1108
|
+
/** @type {Function} */
|
|
1019
1109
|
this.fn = fn;
|
|
1110
|
+
/** @type {string} */
|
|
1111
|
+
this.h = hash;
|
|
1112
|
+
/** @type {any} */
|
|
1020
1113
|
this.v = UNINITIALIZED;
|
|
1021
1114
|
}
|
|
1115
|
+
/** @returns {any} */
|
|
1022
1116
|
get [0]() {
|
|
1023
1117
|
return get(/** @type {Derived} */ (this));
|
|
1024
1118
|
}
|
|
1119
|
+
/** @param {any} v */
|
|
1025
1120
|
set [0](v) {
|
|
1026
1121
|
set(/** @type {Derived} */ (this), v);
|
|
1027
1122
|
}
|
|
1123
|
+
/** @returns {Derived} */
|
|
1028
1124
|
get [1]() {
|
|
1029
|
-
return this;
|
|
1125
|
+
return /** @type {Derived} */ (this);
|
|
1030
1126
|
}
|
|
1127
|
+
/** @returns {any} */
|
|
1031
1128
|
get value() {
|
|
1032
1129
|
return get(/** @type {Derived} */ (this));
|
|
1033
1130
|
}
|
|
@@ -1039,6 +1136,7 @@ class DerivedValue {
|
|
|
1039
1136
|
get length() {
|
|
1040
1137
|
return 2;
|
|
1041
1138
|
}
|
|
1139
|
+
/** @returns {Iterator<any | Derived>} */
|
|
1042
1140
|
*[Symbol.iterator]() {
|
|
1043
1141
|
yield get(/** @type {Derived} */ (this));
|
|
1044
1142
|
yield this;
|
|
@@ -1047,12 +1145,15 @@ class DerivedValue {
|
|
|
1047
1145
|
|
|
1048
1146
|
/**
|
|
1049
1147
|
* @param {any} v
|
|
1148
|
+
* @param {string} hash
|
|
1050
1149
|
* @param {(value: any) => any} [get]
|
|
1051
1150
|
* @param {(next: any, prev: any) => any} [set]
|
|
1052
1151
|
* @returns {Tracked}
|
|
1053
1152
|
*/
|
|
1054
|
-
function tracked(v, get, set) {
|
|
1055
|
-
return /** @type {Tracked} */ (
|
|
1153
|
+
function tracked(v, hash, get, set) {
|
|
1154
|
+
return /** @type {Tracked} */ (
|
|
1155
|
+
new TrackedValue(v, get || set ? { get, set } : empty_get_set, hash)
|
|
1156
|
+
);
|
|
1056
1157
|
}
|
|
1057
1158
|
|
|
1058
1159
|
/**
|
|
@@ -1075,21 +1176,25 @@ export function exclude_from_object(obj, exclude_keys) {
|
|
|
1075
1176
|
|
|
1076
1177
|
/**
|
|
1077
1178
|
* @param {any} v
|
|
1179
|
+
* @param {string} hash
|
|
1078
1180
|
* @param {(value: any) => any} [get]
|
|
1079
1181
|
* @param {(next: any, prev: any) => any} [set]
|
|
1080
1182
|
* @returns {Derived}
|
|
1081
1183
|
*/
|
|
1082
|
-
function derived(v, get, set) {
|
|
1083
|
-
return /** @type {Derived} */ (
|
|
1184
|
+
function derived(v, hash, get, set) {
|
|
1185
|
+
return /** @type {Derived} */ (
|
|
1186
|
+
new DerivedValue(v, get || set ? { get, set } : empty_get_set, hash)
|
|
1187
|
+
);
|
|
1084
1188
|
}
|
|
1085
1189
|
|
|
1086
1190
|
/**
|
|
1087
1191
|
* @param {any} v
|
|
1192
|
+
* @param {string} hash
|
|
1088
1193
|
* @param {(value: any) => any} [get]
|
|
1089
1194
|
* @param {(next: any, prev: any) => any} [set]
|
|
1090
1195
|
* @returns {Tracked | Derived}
|
|
1091
1196
|
*/
|
|
1092
|
-
export function track(v, get, set) {
|
|
1197
|
+
export function track(v, hash, get, set) {
|
|
1093
1198
|
var is_tracked = is_ripple_object(v);
|
|
1094
1199
|
|
|
1095
1200
|
if (is_tracked) {
|
|
@@ -1097,10 +1202,118 @@ export function track(v, get, set) {
|
|
|
1097
1202
|
}
|
|
1098
1203
|
|
|
1099
1204
|
if (typeof v === 'function') {
|
|
1100
|
-
return derived(v, get, set);
|
|
1205
|
+
return derived(v, hash, get, set);
|
|
1101
1206
|
}
|
|
1102
1207
|
|
|
1103
|
-
return tracked(v, get, set);
|
|
1208
|
+
return tracked(v, hash, get, set);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Serializes a resolved trackAsync result as a script tag for hydration.
|
|
1213
|
+
* @param {OutputInterface} output - The output push function captured at call time
|
|
1214
|
+
* @param {string} hash - The unique hash for this trackAsync call
|
|
1215
|
+
* @param {any} value - The resolved value
|
|
1216
|
+
* @param {string[] | null} [deps] - Hashes of direct reactive dependencies read by fn()
|
|
1217
|
+
* @returns {void}
|
|
1218
|
+
*/
|
|
1219
|
+
function serialize_track_async_result(output, hash, value, deps) {
|
|
1220
|
+
/** @type {{ ok: true, payload: string, deps?: string[] }} */
|
|
1221
|
+
var envelope = { ok: true, payload: devalue.stringify(value) };
|
|
1222
|
+
if (deps && deps.length > 0) {
|
|
1223
|
+
envelope.deps = deps;
|
|
1224
|
+
}
|
|
1225
|
+
push_script_for_hydration((str) => output.push_serialized_result(str), hash, envelope);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* Serializes a rejected trackAsync error as a script tag for hydration.
|
|
1230
|
+
* Must be called after route_error_to_catch_block so active_block is the catch block.
|
|
1231
|
+
* @param {string} hash
|
|
1232
|
+
* @param {any} error
|
|
1233
|
+
* @returns {void}
|
|
1234
|
+
*/
|
|
1235
|
+
export function serialize_track_async_error(hash, error) {
|
|
1236
|
+
var error_message = get_public_track_async_error_message(error);
|
|
1237
|
+
|
|
1238
|
+
// we can just use the output_push_serialized directly so it's added to the root block
|
|
1239
|
+
// if we here then the try's block failed to render and the output was cleared
|
|
1240
|
+
// so we're writing to the root otherwise it will be cleared in the local output
|
|
1241
|
+
push_script_for_hydration(output_push_serialized_error, hash, {
|
|
1242
|
+
ok: false,
|
|
1243
|
+
error: { message: error_message },
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
/**
|
|
1248
|
+
* @param {string} hash
|
|
1249
|
+
* @param {any} error
|
|
1250
|
+
* @returns {void}
|
|
1251
|
+
*/
|
|
1252
|
+
export function route_track_async_error_to_catch_block(hash, error) {
|
|
1253
|
+
route_track_async_error_to_catch_block_with_boundary(
|
|
1254
|
+
get_closest_catch_block(/** @type {Block} */ (active_block)),
|
|
1255
|
+
hash,
|
|
1256
|
+
error,
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
/**
|
|
1261
|
+
* @param {any} error
|
|
1262
|
+
* @returns {any}
|
|
1263
|
+
*/
|
|
1264
|
+
export function create_public_track_async_error(error) {
|
|
1265
|
+
if (DEV) {
|
|
1266
|
+
return error;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
return new Error(get_public_track_async_error_message(error));
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/**
|
|
1273
|
+
* We avoid leaking arbitrary server errors in production while still keeping
|
|
1274
|
+
* rich error messages in development and tests.
|
|
1275
|
+
* @param {any} error
|
|
1276
|
+
* @returns {string}
|
|
1277
|
+
*/
|
|
1278
|
+
function get_public_track_async_error_message(error) {
|
|
1279
|
+
if (DEV) {
|
|
1280
|
+
return error?.message ?? String(error);
|
|
1281
|
+
}
|
|
1282
|
+
return 'An error occurred during async rendering';
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/**
|
|
1286
|
+
* Routes trackAsync errors to a catch boundary and serializes the same
|
|
1287
|
+
* public error for hydration, preventing SSR/hydration message mismatches.
|
|
1288
|
+
* @param {TryBlockWithCatch} catch_block
|
|
1289
|
+
* @param {string} hash
|
|
1290
|
+
* @param {any} error
|
|
1291
|
+
* @returns {void}
|
|
1292
|
+
*/
|
|
1293
|
+
function route_track_async_error_to_catch_block_with_boundary(catch_block, hash, error) {
|
|
1294
|
+
var public_error = create_public_track_async_error(error);
|
|
1295
|
+
route_error_to_catch_block(catch_block, public_error);
|
|
1296
|
+
// has to run after routing as it sets the active_block to the catch block
|
|
1297
|
+
serialize_track_async_error(hash, public_error);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
/**
|
|
1301
|
+
* @param {(str: string) => void} push_fn
|
|
1302
|
+
* @param {string} hash
|
|
1303
|
+
* @param {object} envelope - The envelope containing the serialized data
|
|
1304
|
+
* @envelope {ok: boolean, payload?: any, error?: { message: string } }
|
|
1305
|
+
* @returns {void}
|
|
1306
|
+
*/
|
|
1307
|
+
function push_script_for_hydration(push_fn, hash, envelope) {
|
|
1308
|
+
var serialized_envelope = escape_script(JSON.stringify(envelope));
|
|
1309
|
+
|
|
1310
|
+
push_fn(
|
|
1311
|
+
'<script id="' +
|
|
1312
|
+
get_track_async_script_id(hash) +
|
|
1313
|
+
'" type="application/json">' +
|
|
1314
|
+
serialized_envelope +
|
|
1315
|
+
'</script>',
|
|
1316
|
+
);
|
|
1104
1317
|
}
|
|
1105
1318
|
|
|
1106
1319
|
/**
|
|
@@ -1123,16 +1336,22 @@ function run_track_async(t, fn, block, dr, dj) {
|
|
|
1123
1336
|
var result;
|
|
1124
1337
|
/** @type {Dependency | null} */
|
|
1125
1338
|
var caught_dep = null;
|
|
1339
|
+
/** @type {Dependency | null} */
|
|
1340
|
+
var direct_deps = null;
|
|
1126
1341
|
var caught = false;
|
|
1127
1342
|
|
|
1128
1343
|
try {
|
|
1129
1344
|
result = fn();
|
|
1345
|
+
direct_deps = active_dependency;
|
|
1130
1346
|
} catch (error) {
|
|
1131
1347
|
caught_dep = active_dependency;
|
|
1132
1348
|
caught = true;
|
|
1133
1349
|
|
|
1134
1350
|
if (error !== ASYNC_DERIVED_READ_THROWN) {
|
|
1135
|
-
throw
|
|
1351
|
+
throw new TrackAsyncRunError('Error thrown during trackAsync execution', {
|
|
1352
|
+
cause: /** @type {Error} */ (error),
|
|
1353
|
+
tracked: t,
|
|
1354
|
+
});
|
|
1136
1355
|
}
|
|
1137
1356
|
} finally {
|
|
1138
1357
|
tracking = previous_tracking;
|
|
@@ -1175,7 +1394,11 @@ function run_track_async(t, fn, block, dr, dj) {
|
|
|
1175
1394
|
if (dj) {
|
|
1176
1395
|
dj(error);
|
|
1177
1396
|
}
|
|
1178
|
-
|
|
1397
|
+
route_track_async_error_to_catch_block_with_boundary(
|
|
1398
|
+
get_closest_catch_block(block),
|
|
1399
|
+
t.h,
|
|
1400
|
+
error,
|
|
1401
|
+
);
|
|
1179
1402
|
},
|
|
1180
1403
|
);
|
|
1181
1404
|
return;
|
|
@@ -1185,12 +1408,15 @@ function run_track_async(t, fn, block, dr, dj) {
|
|
|
1185
1408
|
return;
|
|
1186
1409
|
}
|
|
1187
1410
|
|
|
1411
|
+
var dep_hashes = collect_dep_hashes(direct_deps);
|
|
1412
|
+
|
|
1188
1413
|
// Handle the result
|
|
1189
1414
|
var async_result = get_async_track_result(result);
|
|
1190
1415
|
|
|
1191
1416
|
if (async_result === null) {
|
|
1192
1417
|
// Sync result
|
|
1193
1418
|
update_tracked_value_clock(t, result);
|
|
1419
|
+
serialize_track_async_result(t.b.o, t.h, result, dep_hashes);
|
|
1194
1420
|
if (dr) {
|
|
1195
1421
|
dr(result);
|
|
1196
1422
|
}
|
|
@@ -1207,6 +1433,7 @@ function run_track_async(t, fn, block, dr, dj) {
|
|
|
1207
1433
|
async_result.promise.then(
|
|
1208
1434
|
(resolved) => {
|
|
1209
1435
|
update_tracked_value_clock(t, resolved);
|
|
1436
|
+
serialize_track_async_result(t.b.o, t.h, resolved, dep_hashes);
|
|
1210
1437
|
if (dr) {
|
|
1211
1438
|
dr(resolved);
|
|
1212
1439
|
}
|
|
@@ -1216,16 +1443,42 @@ function run_track_async(t, fn, block, dr, dj) {
|
|
|
1216
1443
|
if (dj) {
|
|
1217
1444
|
dj(error);
|
|
1218
1445
|
}
|
|
1219
|
-
|
|
1446
|
+
route_track_async_error_to_catch_block_with_boundary(
|
|
1447
|
+
get_closest_catch_block(block),
|
|
1448
|
+
t.h,
|
|
1449
|
+
error,
|
|
1450
|
+
);
|
|
1220
1451
|
},
|
|
1221
1452
|
);
|
|
1222
1453
|
}
|
|
1223
1454
|
|
|
1455
|
+
/**
|
|
1456
|
+
* Walks a dependency chain and collects the hashes of dependencies that have
|
|
1457
|
+
* one (i.e. were created from a compile-time track/trackAsync call).
|
|
1458
|
+
* @param {Dependency | null} head
|
|
1459
|
+
* @returns {string[] | null}
|
|
1460
|
+
*/
|
|
1461
|
+
function collect_dep_hashes(head) {
|
|
1462
|
+
/** @type {string[] | null} */
|
|
1463
|
+
var hashes = null;
|
|
1464
|
+
var dep = head;
|
|
1465
|
+
while (dep !== null) {
|
|
1466
|
+
var h = /** @type {{ h?: string }} */ (dep.t).h;
|
|
1467
|
+
if (h !== undefined) {
|
|
1468
|
+
if (hashes === null) hashes = [];
|
|
1469
|
+
hashes.push(h);
|
|
1470
|
+
}
|
|
1471
|
+
dep = dep.n;
|
|
1472
|
+
}
|
|
1473
|
+
return hashes;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1224
1476
|
/**
|
|
1225
1477
|
* @param {any} v
|
|
1478
|
+
* @param {string} hash - Unique hash for SSR serialization/hydration
|
|
1226
1479
|
* @returns {Tracked | void}
|
|
1227
1480
|
*/
|
|
1228
|
-
export function track_async(v) {
|
|
1481
|
+
export function track_async(v, hash) {
|
|
1229
1482
|
if (is_ripple_object(v)) {
|
|
1230
1483
|
return v;
|
|
1231
1484
|
}
|
|
@@ -1236,22 +1489,21 @@ export function track_async(v) {
|
|
|
1236
1489
|
);
|
|
1237
1490
|
}
|
|
1238
1491
|
|
|
1239
|
-
var t = tracked(SUSPENSE_PENDING);
|
|
1240
|
-
|
|
1241
|
-
run_track_async(t, v, block, null, null);
|
|
1492
|
+
var t = tracked(SUSPENSE_PENDING, hash);
|
|
1493
|
+
run_track_async(t, v, t.b, null, null);
|
|
1242
1494
|
return t;
|
|
1243
1495
|
}
|
|
1244
1496
|
|
|
1245
1497
|
/**
|
|
1246
|
-
* @param {(Derived | Tracked) | (() => any)}
|
|
1498
|
+
* @param {(Derived | Tracked) | (() => any)} t
|
|
1247
1499
|
* @returns {boolean}
|
|
1248
1500
|
*/
|
|
1249
|
-
export function is_tracked_pending(
|
|
1501
|
+
export function is_tracked_pending(t) {
|
|
1250
1502
|
try {
|
|
1251
|
-
if (typeof
|
|
1252
|
-
|
|
1503
|
+
if (typeof t === 'function') {
|
|
1504
|
+
t();
|
|
1253
1505
|
} else {
|
|
1254
|
-
get(
|
|
1506
|
+
get(t);
|
|
1255
1507
|
}
|
|
1256
1508
|
return false;
|
|
1257
1509
|
} catch (error) {
|
|
@@ -1332,14 +1584,27 @@ function register_block_rerun(block) {
|
|
|
1332
1584
|
run_block(block);
|
|
1333
1585
|
try_catch_block.o.resolveAsync(operation);
|
|
1334
1586
|
} catch (error) {
|
|
1335
|
-
|
|
1587
|
+
if (error instanceof TrackAsyncRunError) {
|
|
1588
|
+
var {
|
|
1589
|
+
cause,
|
|
1590
|
+
tracked: { h: hash },
|
|
1591
|
+
} = /** @type {InstanceType<typeof TrackAsyncRunError>} */ (error);
|
|
1592
|
+
error = cause;
|
|
1593
|
+
route_track_async_error_to_catch_block_with_boundary(try_catch_block, hash, error);
|
|
1594
|
+
} else {
|
|
1595
|
+
route_error_to_catch_block(try_catch_block, error);
|
|
1596
|
+
}
|
|
1336
1597
|
}
|
|
1337
1598
|
},
|
|
1338
1599
|
(error) => {
|
|
1339
1600
|
if (cancelled) {
|
|
1340
1601
|
return;
|
|
1341
1602
|
}
|
|
1342
|
-
|
|
1603
|
+
route_track_async_error_to_catch_block_with_boundary(
|
|
1604
|
+
try_catch_block,
|
|
1605
|
+
/** @type {Tracked} */ (t).h,
|
|
1606
|
+
error,
|
|
1607
|
+
);
|
|
1343
1608
|
},
|
|
1344
1609
|
);
|
|
1345
1610
|
// clear all output buffers as we'll rerun the block rendering
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { Context } from './context.js';
|
|
2
|
-
import type { OutputInterface } from './index.js';
|
|
2
|
+
import type { OutputInterface, Derived, Tracked } from './index.js';
|
|
3
3
|
import type { BlockFunction, CatchFunction, PendingFunction } from './blocks.js';
|
|
4
4
|
|
|
5
|
+
export { Tracked, Derived };
|
|
6
|
+
|
|
5
7
|
export type Component = {
|
|
6
8
|
c: null | Map<Context<any>, any>;
|
|
7
9
|
p: null | Component;
|
|
@@ -13,36 +15,6 @@ export type Dependency = {
|
|
|
13
15
|
n: null | Dependency;
|
|
14
16
|
};
|
|
15
17
|
|
|
16
|
-
export type Derived = {
|
|
17
|
-
a: { get?: Function; set?: Function };
|
|
18
|
-
b: Block;
|
|
19
|
-
c: number;
|
|
20
|
-
co: null | Component;
|
|
21
|
-
d: null | Dependency;
|
|
22
|
-
f: number;
|
|
23
|
-
fn: Function;
|
|
24
|
-
v: any;
|
|
25
|
-
readonly [0]: any;
|
|
26
|
-
[1]: Derived;
|
|
27
|
-
value: any;
|
|
28
|
-
readonly length: 2;
|
|
29
|
-
[Symbol.iterator](): Iterator<any | Derived>;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export type Tracked = {
|
|
33
|
-
a: { get?: Function; set?: Function };
|
|
34
|
-
c: number;
|
|
35
|
-
f: number;
|
|
36
|
-
v: any;
|
|
37
|
-
aa: AbortController | null;
|
|
38
|
-
ap: PromiseLike<any> | null;
|
|
39
|
-
readonly [0]: any;
|
|
40
|
-
[1]: Tracked;
|
|
41
|
-
value: any;
|
|
42
|
-
readonly length: 2;
|
|
43
|
-
[Symbol.iterator](): Iterator<any | Tracked>;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
18
|
export type Block = {
|
|
47
19
|
co: Component | null;
|
|
48
20
|
f: number;
|
package/src/utils/escaping.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const ATTR_REGEX = /[&"<]/g;
|
|
2
2
|
const CONTENT_REGEX = /[&<]/g;
|
|
3
|
+
const OPEN_TAG_REGEX = /</g;
|
|
4
|
+
const CLOSE_TAG_REGEX = />/g;
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* @template V
|
|
@@ -24,3 +26,12 @@ export function escape(value, is_attr) {
|
|
|
24
26
|
|
|
25
27
|
return escaped + str.substring(last);
|
|
26
28
|
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Escapes characters that can prematurely terminate inline script tags.
|
|
32
|
+
* @param {string} str
|
|
33
|
+
* @returns {string}
|
|
34
|
+
*/
|
|
35
|
+
export function escape_script(str) {
|
|
36
|
+
return str.replace(OPEN_TAG_REGEX, '\\u003c').replace(CLOSE_TAG_REGEX, '\\u003e');
|
|
37
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the script element ID used for serializing/hydrating trackAsync results.
|
|
3
|
+
* Used by both server and client runtimes.
|
|
4
|
+
* @param {string} hash - The unique hash for this trackAsync call
|
|
5
|
+
* @returns {string}
|
|
6
|
+
*/
|
|
7
|
+
export function get_track_async_script_id(hash) {
|
|
8
|
+
return `__tsrx_ta_${hash}`;
|
|
9
|
+
}
|
|
@@ -562,7 +562,17 @@ describe('async suspense', () => {
|
|
|
562
562
|
|
|
563
563
|
render(App);
|
|
564
564
|
|
|
565
|
-
|
|
565
|
+
for (let i = 0; i < 50; i++) {
|
|
566
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
567
|
+
flushSync();
|
|
568
|
+
if (
|
|
569
|
+
!container.innerHTML.includes('Loading...') &&
|
|
570
|
+
container.querySelector('.double')?.textContent === '0' &&
|
|
571
|
+
container.querySelector('.quadruple')?.textContent === '0'
|
|
572
|
+
) {
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
566
576
|
|
|
567
577
|
expect(container.innerHTML).not.toContain('Loading...');
|
|
568
578
|
expect(container.querySelector('.double')!.textContent).toBe('0');
|