ripple 0.4.1 → 0.4.3
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 +162 -0
- package/package.json +9 -3
- package/src/constants.js +4 -0
- package/src/jsx-runtime.d.ts +2 -1
- package/src/runtime/array.js +15 -9
- package/src/runtime/index-client.js +25 -7
- package/src/runtime/index-server.js +1 -0
- package/src/runtime/internal/client/attributes.js +313 -0
- package/src/runtime/internal/client/blocks.js +4 -14
- package/src/runtime/internal/client/component.js +2 -5
- package/src/runtime/internal/client/composite.js +14 -6
- package/src/runtime/internal/client/constants.js +12 -6
- package/src/runtime/internal/client/context.js +3 -2
- package/src/runtime/internal/client/css.js +11 -26
- package/src/runtime/internal/client/errors.js +188 -0
- package/src/runtime/internal/client/events.js +79 -34
- package/src/runtime/internal/client/expression.js +21 -116
- package/src/runtime/internal/client/for.js +272 -222
- package/src/runtime/internal/client/head.js +4 -3
- package/src/runtime/internal/client/html.js +5 -3
- package/src/runtime/internal/client/hydrate.js +484 -0
- package/src/runtime/internal/client/hydration-enabled.js +7 -0
- package/src/runtime/internal/client/hydration.js +16 -97
- package/src/runtime/internal/client/if.js +13 -9
- package/src/runtime/internal/client/index.js +16 -8
- package/src/runtime/internal/client/operations.js +12 -9
- package/src/runtime/internal/client/portal.js +3 -2
- package/src/runtime/internal/client/render.js +75 -321
- package/src/runtime/internal/client/root-boundary-enabled.js +7 -0
- package/src/runtime/internal/client/runtime.js +129 -362
- package/src/runtime/internal/client/template-ns.js +47 -0
- package/src/runtime/internal/client/template.js +63 -140
- package/src/runtime/internal/client/track-async.js +356 -0
- package/src/runtime/internal/client/try.js +133 -319
- package/src/runtime/internal/client/types.d.ts +48 -24
- package/src/runtime/internal/server/index.js +38 -33
- package/src/runtime/internal/server/prerender.js +27 -0
- package/src/server/index.js +1 -0
- package/src/utils/class-name.js +28 -0
- package/tests/client/basic/basic.errors.test.tsrx +3 -3
- package/tests/client/compiler/__snapshots__/compiler.typescript.test.tsrx.snap +0 -2
- package/tests/client/compiler/compiler.basic.test.tsrx +4 -4
- package/tests/client/compiler/compiler.tracked-access.test.tsrx +3 -3
- package/tests/client/composite/composite.props.test.tsrx +10 -10
- package/tests/client/derived-release.test.tsrx +3 -3
- package/tests/client/for-item.test.tsrx +443 -0
- package/tests/client/for-selector.test.tsrx +95 -0
- package/tests/hydration/compiled/client/basic.js +97 -123
- package/tests/hydration/compiled/client/composite.js +5 -5
- package/tests/hydration/compiled/client/events.js +84 -79
- package/tests/hydration/compiled/client/for.js +229 -233
- package/tests/hydration/compiled/client/fragment-cursor.js +781 -481
- package/tests/hydration/compiled/client/head.js +23 -26
- package/tests/hydration/compiled/client/hmr.js +4 -4
- package/tests/hydration/compiled/client/html-in-template.js +5 -5
- package/tests/hydration/compiled/client/html.js +174 -208
- package/tests/hydration/compiled/client/if-children.js +71 -70
- package/tests/hydration/compiled/client/if-fragment-controlflow.js +46 -46
- package/tests/hydration/compiled/client/if.js +26 -26
- package/tests/hydration/compiled/client/mixed-control-flow.js +63 -105
- package/tests/hydration/compiled/client/nested-control-flow.js +170 -390
- package/tests/hydration/compiled/client/portal.js +10 -10
- package/tests/hydration/compiled/client/reactivity.js +36 -50
- package/tests/hydration/compiled/client/return.js +2 -2
- package/tests/hydration/compiled/client/streaming.js +34 -40
- package/tests/hydration/compiled/client/switch.js +25 -25
- package/tests/hydration/compiled/client/track-async-serialization.js +41 -41
- package/tests/hydration/compiled/client/try.js +15 -15
- package/tests/hydration/compiled/client/typed-text.js +8 -19
- package/tests/hydration/compiled/server/basic.js +6 -6
- package/tests/hydration/compiled/server/events.js +13 -61
- package/tests/hydration/compiled/server/for.js +23 -31
- package/tests/hydration/compiled/server/fragment-cursor.js +214 -366
- 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 +9 -9
- 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/streaming.js +9 -9
- package/tests/hydration/compiled/server/switch.js +4 -4
- package/tests/hydration/compiled/server/track-async-serialization.js +16 -16
- package/tests/hydration/compiled/server/try.js +4 -4
- package/tests/hydration/compiled/server/typed-text.js +1 -1
- package/tests/hydration/components/fragment-cursor.tsrx +60 -0
- package/tests/hydration/components/track-async-serialization.tsrx +2 -2
- package/tests/hydration/fragment-cursor.test.js +34 -1
- package/tests/hydration/track-async-serialization.test.js +1 -1
- package/tests/server/basic.components.test.tsrx +3 -3
- package/tests/server/basic.test.tsrx +23 -0
- package/tests/server/track-async-serialization.test.tsrx +6 -6
- package/tests/utils/tracked-types.test.js +3 -3
- package/types/index.d.ts +8 -8
- package/types/server.d.ts +17 -0
|
@@ -6,9 +6,10 @@ import {
|
|
|
6
6
|
TEMPLATE_SVG_NAMESPACE,
|
|
7
7
|
TEMPLATE_MATHML_NAMESPACE,
|
|
8
8
|
} from '../../../constants.js';
|
|
9
|
-
import {
|
|
10
|
-
import { create_text, get_first_child,
|
|
9
|
+
import { H, hydrate_node, hydrating } from './hydration.js';
|
|
10
|
+
import { create_text, get_first_child, is_firefox } from './operations.js';
|
|
11
11
|
import { active_block, active_namespace } from './runtime.js';
|
|
12
|
+
import { HYDRATION } from 'ripple/internal/client/hydration-enabled';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Assigns start and end nodes to the active block's state.
|
|
@@ -30,92 +31,46 @@ export function assign_nodes(start, end) {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* @
|
|
37
|
-
* @returns {DocumentFragment}
|
|
34
|
+
* Parses content that belongs in the SVG or MathML namespace. Installed by
|
|
35
|
+
* `template-ns.js` when a template or a dynamic element needs it, so an app
|
|
36
|
+
* with HTML templates only ships neither.
|
|
37
|
+
* @type {((html: string, ns: 'svg' | 'math') => DocumentFragment) | null}
|
|
38
38
|
*/
|
|
39
|
-
|
|
40
|
-
html,
|
|
41
|
-
use_svg_namespace = false,
|
|
42
|
-
use_mathml_namespace = false,
|
|
43
|
-
) {
|
|
44
|
-
if (use_svg_namespace) {
|
|
45
|
-
return from_namespace(html, 'svg');
|
|
46
|
-
}
|
|
47
|
-
if (use_mathml_namespace) {
|
|
48
|
-
return from_namespace(html, 'math');
|
|
49
|
-
}
|
|
50
|
-
var elem = document.createElement('template');
|
|
51
|
-
elem.innerHTML = html;
|
|
52
|
-
return elem.content;
|
|
53
|
-
}
|
|
39
|
+
var parse_ns = null;
|
|
54
40
|
|
|
55
41
|
/**
|
|
56
|
-
*
|
|
57
|
-
* at the cursor instead of cloning. Kept out of the clone path so a client-only
|
|
58
|
-
* mount never compiles it.
|
|
59
|
-
* @param {boolean} is_fragment
|
|
60
|
-
* @param {number} count
|
|
61
|
-
* @returns {Node}
|
|
42
|
+
* @param {(html: string, ns: 'svg' | 'math') => DocumentFragment} fn
|
|
62
43
|
*/
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
var end = is_fragment ? hydrate_fragment_end(node, count) : node;
|
|
66
|
-
// assign_nodes, inline: the per-node hydration path stays free of helpers.
|
|
67
|
-
var block = /** @type {Block} */ (active_block);
|
|
68
|
-
var s = block.s;
|
|
69
|
-
if (s === null) {
|
|
70
|
-
block.s = { start: node, end };
|
|
71
|
-
} else if (s.start === null) {
|
|
72
|
-
s.start = node;
|
|
73
|
-
s.end = end;
|
|
74
|
-
}
|
|
75
|
-
return node;
|
|
44
|
+
export function set_ns_parser(fn) {
|
|
45
|
+
parse_ns = fn;
|
|
76
46
|
}
|
|
77
47
|
|
|
78
48
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* @param {
|
|
82
|
-
* @
|
|
83
|
-
* @returns {Node}
|
|
49
|
+
* Creates a DocumentFragment from an HTML string.
|
|
50
|
+
* @param {string} html - The HTML string.
|
|
51
|
+
* @param {string} ns - `'svg'`, `'math'`, or `''` for HTML.
|
|
52
|
+
* @returns {DocumentFragment}
|
|
84
53
|
*/
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
while (next !== null && next.nodeType === Node.COMMENT_NODE) {
|
|
92
|
-
next = get_next_sibling(next);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (next === null) {
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
end = next;
|
|
54
|
+
export function create_fragment_from_html(html, ns = '') {
|
|
55
|
+
if (ns !== '') {
|
|
56
|
+
return /** @type {NonNullable<typeof parse_ns>} */ (parse_ns)(
|
|
57
|
+
html,
|
|
58
|
+
/** @type {'svg' | 'math'} */ (ns),
|
|
59
|
+
);
|
|
100
60
|
}
|
|
101
|
-
|
|
102
|
-
|
|
61
|
+
var elem = document.createElement('template');
|
|
62
|
+
elem.innerHTML = html;
|
|
63
|
+
return elem.content;
|
|
103
64
|
}
|
|
104
65
|
|
|
105
66
|
/**
|
|
106
|
-
* A template's
|
|
107
|
-
*
|
|
67
|
+
* A template's content and flags, shared by every clone of one `template()`
|
|
68
|
+
* instance, with the parsed node cached for the namespace it was parsed in.
|
|
108
69
|
* @typedef {{
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* use_mathml_namespace: boolean;
|
|
114
|
-
* is_comment: boolean;
|
|
115
|
-
* has_start: boolean;
|
|
116
|
-
* node: Node | DocumentFragment | undefined;
|
|
117
|
-
* node_svg: boolean;
|
|
118
|
-
* node_mathml: boolean;
|
|
70
|
+
* c: string;
|
|
71
|
+
* f: number;
|
|
72
|
+
* n: Node | DocumentFragment | undefined;
|
|
73
|
+
* ns: string | undefined;
|
|
119
74
|
* }} TemplateState
|
|
120
75
|
*/
|
|
121
76
|
|
|
@@ -126,24 +81,33 @@ function hydrate_fragment_end(start, count) {
|
|
|
126
81
|
* @returns {Node}
|
|
127
82
|
*/
|
|
128
83
|
function clone_template(t) {
|
|
129
|
-
var
|
|
130
|
-
var
|
|
131
|
-
var
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
var
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
84
|
+
var flags = t.f;
|
|
85
|
+
var content = t.c;
|
|
86
|
+
var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0;
|
|
87
|
+
// The namespace the content parses in: a static one from the flags, else
|
|
88
|
+
// the active namespace (a comment placeholder needs none).
|
|
89
|
+
var ns =
|
|
90
|
+
content === '<!>'
|
|
91
|
+
? ''
|
|
92
|
+
: (flags & TEMPLATE_SVG_NAMESPACE) !== 0 || active_namespace === 'svg'
|
|
93
|
+
? 'svg'
|
|
94
|
+
: (flags & TEMPLATE_MATHML_NAMESPACE) !== 0 || active_namespace === 'mathml'
|
|
95
|
+
? 'math'
|
|
96
|
+
: '';
|
|
97
|
+
var node = t.n;
|
|
98
|
+
|
|
99
|
+
if (node === undefined || t.ns !== ns) {
|
|
100
|
+
// Content that opens with a placeholder comment gets one more in
|
|
101
|
+
// front, so the first child the compiler navigates to is the element.
|
|
102
|
+
node = create_fragment_from_html(content.startsWith('<!>') ? '<!>' + content : content, ns);
|
|
138
103
|
if (!is_fragment) node = /** @type {Node} */ (get_first_child(node));
|
|
139
|
-
t.
|
|
140
|
-
t.
|
|
141
|
-
t.node_mathml = mathml;
|
|
104
|
+
t.n = node;
|
|
105
|
+
t.ns = ns;
|
|
142
106
|
}
|
|
143
107
|
|
|
144
108
|
/** @type {DocumentFragment | Node} */
|
|
145
109
|
var clone =
|
|
146
|
-
|
|
110
|
+
(flags & TEMPLATE_USE_IMPORT_NODE) !== 0 || is_firefox
|
|
147
111
|
? document.importNode(/** @type {Node} */ (node), true)
|
|
148
112
|
: /** @type {Node} */ (node).cloneNode(true);
|
|
149
113
|
|
|
@@ -155,15 +119,7 @@ function clone_template(t) {
|
|
|
155
119
|
end = /** @type {Node} */ (/** @type {DocumentFragment} */ (clone).lastChild);
|
|
156
120
|
}
|
|
157
121
|
|
|
158
|
-
|
|
159
|
-
var block = /** @type {Block} */ (active_block);
|
|
160
|
-
var s = block.s;
|
|
161
|
-
if (s === null) {
|
|
162
|
-
block.s = { start, end };
|
|
163
|
-
} else if (s.start === null) {
|
|
164
|
-
s.start = start;
|
|
165
|
-
s.end = end;
|
|
166
|
-
}
|
|
122
|
+
assign_nodes(start, end);
|
|
167
123
|
|
|
168
124
|
return clone;
|
|
169
125
|
}
|
|
@@ -175,24 +131,15 @@ function clone_template(t) {
|
|
|
175
131
|
* @param {number} [count] - Pre-calculated count of top-level nodes (for fragments). When provided, avoids runtime parsing.
|
|
176
132
|
* @returns {() => Node}
|
|
177
133
|
*/
|
|
178
|
-
export function template(content, flags, count = 1) {
|
|
134
|
+
export function template(content, flags = 0, count = 1) {
|
|
179
135
|
var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0;
|
|
180
|
-
var is_comment = content === '<!>';
|
|
181
136
|
/** @type {TemplateState} */
|
|
182
|
-
var t = {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
is_comment,
|
|
189
|
-
has_start: !is_comment && !content.startsWith('<!>'),
|
|
190
|
-
node: undefined,
|
|
191
|
-
node_svg: false,
|
|
192
|
-
node_mathml: false,
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
return () => (hydrating ? hydrate_template(is_fragment, count) : clone_template(t));
|
|
137
|
+
var t = { c: content, f: flags, n: undefined, ns: undefined };
|
|
138
|
+
|
|
139
|
+
return () =>
|
|
140
|
+
HYDRATION && hydrating
|
|
141
|
+
? /** @type {import('./hydrate.js').HydrationRuntime} */ (H).t(is_fragment, count)
|
|
142
|
+
: clone_template(t);
|
|
196
143
|
}
|
|
197
144
|
|
|
198
145
|
/**
|
|
@@ -201,8 +148,8 @@ export function template(content, flags, count = 1) {
|
|
|
201
148
|
* @param {Node} dom - The DOM node to append.
|
|
202
149
|
*/
|
|
203
150
|
export function append(anchor, dom) {
|
|
204
|
-
if (hydrating) {
|
|
205
|
-
|
|
151
|
+
if (HYDRATION && hydrating) {
|
|
152
|
+
/** @type {import('./hydrate.js').HydrationRuntime} */ (H).a(anchor, dom);
|
|
206
153
|
return;
|
|
207
154
|
}
|
|
208
155
|
if (/** @type {AppendIntoAnchor} */ (anchor).into === true) {
|
|
@@ -217,7 +164,7 @@ export function append(anchor, dom) {
|
|
|
217
164
|
}
|
|
218
165
|
|
|
219
166
|
export function text(data = '') {
|
|
220
|
-
if (hydrating) {
|
|
167
|
+
if (HYDRATION && hydrating) {
|
|
221
168
|
assign_nodes(/** @type {Node} */ (hydrate_node), /** @type {Node} */ (hydrate_node));
|
|
222
169
|
return /** @type {Node} */ (hydrate_node);
|
|
223
170
|
}
|
|
@@ -225,27 +172,3 @@ export function text(data = '') {
|
|
|
225
172
|
assign_nodes(node, node);
|
|
226
173
|
return node;
|
|
227
174
|
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Create fragment with proper namespace using Svelte's wrapping approach
|
|
231
|
-
* @param {string} content
|
|
232
|
-
* @param {'svg' | 'math'} ns
|
|
233
|
-
* @returns {DocumentFragment}
|
|
234
|
-
*/
|
|
235
|
-
function from_namespace(content, ns = 'svg') {
|
|
236
|
-
var wrapped = `<${ns}>${content}</${ns}>`;
|
|
237
|
-
|
|
238
|
-
var elem = document.createElement('template');
|
|
239
|
-
elem.innerHTML = wrapped;
|
|
240
|
-
var fragment = elem.content;
|
|
241
|
-
|
|
242
|
-
var root = /** @type {Element} */ (get_first_child(fragment));
|
|
243
|
-
var result = document.createDocumentFragment();
|
|
244
|
-
|
|
245
|
-
var first;
|
|
246
|
-
while ((first = get_first_child(root))) {
|
|
247
|
-
result.appendChild(/** @type {Node} */ (first));
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return result;
|
|
251
|
-
}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/** @import { Block, DeferredTrackedEntry, Tracked } from '#client' */
|
|
2
|
+
|
|
3
|
+
import { pause_block, pre_effect } from './blocks.js';
|
|
4
|
+
import {
|
|
5
|
+
ASYNC_DERIVED_READ_THROWN,
|
|
6
|
+
DIRECT_CHILD_BLOCK,
|
|
7
|
+
SUSPENSE_PENDING,
|
|
8
|
+
SUSPENSE_REJECTED,
|
|
9
|
+
TRACKED,
|
|
10
|
+
TRACKED_UPDATED,
|
|
11
|
+
TRY_BLOCK,
|
|
12
|
+
} from './constants.js';
|
|
13
|
+
import { hydrating, track_hash_reference } from './hydration.js';
|
|
14
|
+
import { HYDRATION } from 'ripple/internal/client/hydration-enabled';
|
|
15
|
+
import {
|
|
16
|
+
active_block,
|
|
17
|
+
active_component,
|
|
18
|
+
active_dependency,
|
|
19
|
+
get,
|
|
20
|
+
is_mutating_allowed,
|
|
21
|
+
set_mutating_allowed,
|
|
22
|
+
set_pending_read_handler,
|
|
23
|
+
set_tracking,
|
|
24
|
+
tracked,
|
|
25
|
+
tracking,
|
|
26
|
+
update_tracked_value_clock,
|
|
27
|
+
} from './runtime.js';
|
|
28
|
+
import { is_ripple_object } from './utils.js';
|
|
29
|
+
import {
|
|
30
|
+
begin_boundary_request,
|
|
31
|
+
complete_boundary_request,
|
|
32
|
+
get_boundary_with_catch,
|
|
33
|
+
get_pending_boundary,
|
|
34
|
+
handle_boundary_error,
|
|
35
|
+
register_boundary_deferred,
|
|
36
|
+
register_boundary_paused_block,
|
|
37
|
+
replace_boundary_request,
|
|
38
|
+
} from './try.js';
|
|
39
|
+
import { get_async_track_result } from '../../../utils/async.js';
|
|
40
|
+
import { get_track_async_script_id } from '../../../utils/track-async-serialization.js';
|
|
41
|
+
import { revive } from './transport.js';
|
|
42
|
+
import {
|
|
43
|
+
pending_read_direct,
|
|
44
|
+
track_async_argument,
|
|
45
|
+
track_async_boundary,
|
|
46
|
+
track_async_orphan,
|
|
47
|
+
} from './errors.js';
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* `trackAsync()` and everything a pending async read sets in motion: boundary
|
|
51
|
+
* requests, paused blocks, deferred rejections. The runtime reaches it through
|
|
52
|
+
* a handler the first `trackAsync()` installs, so an application without one
|
|
53
|
+
* ships none of it.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
var installed = false;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Complete all deferred boundary requests registered on a tracked value.
|
|
60
|
+
* @param {Tracked} t
|
|
61
|
+
* @param {boolean} [show_resolved=true]
|
|
62
|
+
*/
|
|
63
|
+
function complete_deferred_boundaries(t, show_resolved = true) {
|
|
64
|
+
if (t.d !== null) {
|
|
65
|
+
for (var i = 0; i < t.d.length; i++) {
|
|
66
|
+
var entry = t.d[i];
|
|
67
|
+
complete_boundary_request(entry.b, entry.r, show_resolved);
|
|
68
|
+
}
|
|
69
|
+
t.d = null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A block's run read a pending async value (it threw
|
|
75
|
+
* `ASYNC_DERIVED_READ_THROWN`, and the read was registered as a dependency):
|
|
76
|
+
* the block pauses under its pending boundary until the value settles, and
|
|
77
|
+
* a read straight from a component or boundary body is an error.
|
|
78
|
+
* @param {Block} block
|
|
79
|
+
*/
|
|
80
|
+
function handle_pending_read(block) {
|
|
81
|
+
var is_component_direct = active_component?.b === block;
|
|
82
|
+
var is_try_fn_block =
|
|
83
|
+
block.p !== null && (block.p.f & TRY_BLOCK) !== 0 && (block.f & DIRECT_CHILD_BLOCK) !== 0;
|
|
84
|
+
|
|
85
|
+
if (is_component_direct || is_try_fn_block) {
|
|
86
|
+
pending_read_direct(is_component_direct);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
var boundary = get_pending_boundary(block);
|
|
90
|
+
if (boundary === null) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
pause_block(block);
|
|
94
|
+
register_boundary_paused_block(boundary, block);
|
|
95
|
+
|
|
96
|
+
// Register deferred boundary completions for async tracked deps.
|
|
97
|
+
// This handles the case where a child boundary reads a tracked value
|
|
98
|
+
// whose resolution is managed by a different (parent) boundary.
|
|
99
|
+
var dep = block.d;
|
|
100
|
+
while (dep !== null) {
|
|
101
|
+
var dep_tracked = /** @type {Tracked} */ (dep.t);
|
|
102
|
+
if (
|
|
103
|
+
(dep_tracked.__v === SUSPENSE_PENDING || dep_tracked.__v === SUSPENSE_REJECTED) &&
|
|
104
|
+
(dep_tracked.f & TRACKED) !== 0
|
|
105
|
+
) {
|
|
106
|
+
var deferred_req = begin_boundary_request(boundary);
|
|
107
|
+
var entry = /** @type {DeferredTrackedEntry} */ ({ b: boundary, r: deferred_req });
|
|
108
|
+
if (dep_tracked.d === null) {
|
|
109
|
+
dep_tracked.d = [entry];
|
|
110
|
+
} else {
|
|
111
|
+
dep_tracked.d.push(entry);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
dep = dep.n;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @param {any} fn
|
|
120
|
+
* @param {Block} b
|
|
121
|
+
* @param {string} hash - Unique hash for SSR serialization/hydration
|
|
122
|
+
* @returns {Tracked | void}
|
|
123
|
+
*/
|
|
124
|
+
export function track_async(fn, b, hash) {
|
|
125
|
+
if (!installed) {
|
|
126
|
+
installed = true;
|
|
127
|
+
set_pending_read_handler(handle_pending_read);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (is_ripple_object(fn)) {
|
|
131
|
+
return fn;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
var target_block = b || active_block;
|
|
135
|
+
if (target_block === null) {
|
|
136
|
+
track_async_orphan();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (typeof fn !== 'function') {
|
|
140
|
+
track_async_argument();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// During hydration, attempt to read serialized data from SSR
|
|
144
|
+
var had_hydration_data = false;
|
|
145
|
+
var hydration_value;
|
|
146
|
+
/** @type {string[] | undefined} */
|
|
147
|
+
var hydration_deps;
|
|
148
|
+
|
|
149
|
+
if (HYDRATION && hydrating) {
|
|
150
|
+
var script_id = get_track_async_script_id(hash);
|
|
151
|
+
var script_el = document.getElementById(script_id);
|
|
152
|
+
if (script_el) {
|
|
153
|
+
var envelope = JSON.parse(/** @type {string} */ (script_el.textContent));
|
|
154
|
+
script_el.remove();
|
|
155
|
+
|
|
156
|
+
if (envelope.ok) {
|
|
157
|
+
had_hydration_data = true;
|
|
158
|
+
hydration_value =
|
|
159
|
+
envelope.payload === undefined ? envelope.value : revive(envelope.payload);
|
|
160
|
+
hydration_deps = envelope.deps;
|
|
161
|
+
} else {
|
|
162
|
+
// trigger the catch block
|
|
163
|
+
throw new Error(envelope.error?.message ?? 'Unknown server error');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
var t = tracked(had_hydration_data ? hydration_value : SUSPENSE_PENDING, target_block, hash);
|
|
169
|
+
|
|
170
|
+
// Capture the call-site block for boundary lookups. target_block is the
|
|
171
|
+
// component's block (passed by compiler), but the actual try/pending/catch
|
|
172
|
+
// boundary is an ancestor of active_block (the block executing trackAsync).
|
|
173
|
+
var call_site_block = /** @type {Block} */ (active_block);
|
|
174
|
+
|
|
175
|
+
var version = 0;
|
|
176
|
+
/** @type {AbortController | null} */
|
|
177
|
+
var abort_controller = null;
|
|
178
|
+
var request_id = 0;
|
|
179
|
+
/** @type {Block | null} */
|
|
180
|
+
var boundary = null;
|
|
181
|
+
|
|
182
|
+
// TODO: decide if instead of insisting on pending, we create our own boundary
|
|
183
|
+
// we currently require a pending block upstream but we could also
|
|
184
|
+
// create a try/pending/catch boundary at mount and hydration like
|
|
185
|
+
// we do on the server so that there is always a boundary present.
|
|
186
|
+
// It can handle global pending when none were provided.
|
|
187
|
+
// Not sure about the catch boundary because if none were provided,
|
|
188
|
+
// the whole app for any error will be unmounted with the catch block rendered
|
|
189
|
+
|
|
190
|
+
// Find boundary from the call-site block.
|
|
191
|
+
boundary = get_pending_boundary(active_block);
|
|
192
|
+
if (boundary === null) {
|
|
193
|
+
track_async_boundary();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// If we hydrated with resolved data, the SSR already completed this request.
|
|
197
|
+
// Otherwise mark a pending request on the boundary for the client-side run.
|
|
198
|
+
if (!had_hydration_data) {
|
|
199
|
+
request_id = begin_boundary_request(boundary);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
pre_effect(() => {
|
|
203
|
+
if (had_hydration_data) {
|
|
204
|
+
// First run after hydration: skip fn() entirely (the SSR already
|
|
205
|
+
// produced the resolved value) and instead register the direct
|
|
206
|
+
// dependencies from the serialized deps list so future dep changes
|
|
207
|
+
// trigger a re-run via the normal async path.
|
|
208
|
+
had_hydration_data = false;
|
|
209
|
+
if (hydration_deps !== undefined) {
|
|
210
|
+
for (var i = 0; i < hydration_deps.length; i++) {
|
|
211
|
+
var dep_ref = track_hash_reference.get(hydration_deps[i]);
|
|
212
|
+
if (dep_ref !== undefined) {
|
|
213
|
+
get(dep_ref);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
var current_version = ++version;
|
|
221
|
+
|
|
222
|
+
// Abort previous in-flight request
|
|
223
|
+
if (abort_controller !== null && abort_controller.signal.aborted === false) {
|
|
224
|
+
abort_controller.abort(TRACKED_UPDATED);
|
|
225
|
+
}
|
|
226
|
+
abort_controller = null;
|
|
227
|
+
|
|
228
|
+
// Manage boundary request: replace if in-flight, or begin new if previous completed
|
|
229
|
+
if (request_id > 0 && boundary !== null) {
|
|
230
|
+
request_id = replace_boundary_request(boundary, request_id);
|
|
231
|
+
} else if (boundary !== null) {
|
|
232
|
+
request_id = begin_boundary_request(boundary);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Set to pending before calling fn() in case it's sync.
|
|
236
|
+
if (t.__v !== SUSPENSE_PENDING) {
|
|
237
|
+
update_tracked_value_clock(t, SUSPENSE_PENDING);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Temporarily allow mutations so set() doesn't throw inside the pre-effect
|
|
241
|
+
var previous_is_mutating_allowed = is_mutating_allowed;
|
|
242
|
+
set_mutating_allowed(true);
|
|
243
|
+
|
|
244
|
+
var result;
|
|
245
|
+
try {
|
|
246
|
+
result = fn();
|
|
247
|
+
} catch (e) {
|
|
248
|
+
set_mutating_allowed(previous_is_mutating_allowed);
|
|
249
|
+
if (e === ASYNC_DERIVED_READ_THROWN) {
|
|
250
|
+
// A dependency is still pending or rejected (e.g. chained trackAsync).
|
|
251
|
+
// Check if any dependency is rejected — if so, propagate rejection.
|
|
252
|
+
var dep = active_dependency;
|
|
253
|
+
while (dep !== null) {
|
|
254
|
+
if (dep.t.__v === SUSPENSE_REJECTED) {
|
|
255
|
+
update_tracked_value_clock(t, SUSPENSE_REJECTED);
|
|
256
|
+
complete_deferred_boundaries(t, false);
|
|
257
|
+
if (request_id > 0 && boundary !== null) {
|
|
258
|
+
complete_boundary_request(boundary, request_id, false);
|
|
259
|
+
request_id = 0;
|
|
260
|
+
}
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
dep = dep.n;
|
|
264
|
+
}
|
|
265
|
+
// Dependencies are pending, not rejected — register deferred
|
|
266
|
+
// rejection so that if the boundary goes to catch mode, this
|
|
267
|
+
// tracked value is also set to REJECTED.
|
|
268
|
+
if (request_id > 0 && boundary !== null) {
|
|
269
|
+
register_boundary_deferred(boundary, request_id, () => {
|
|
270
|
+
update_tracked_value_clock(t, SUSPENSE_REJECTED);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
throw e;
|
|
276
|
+
}
|
|
277
|
+
set_mutating_allowed(previous_is_mutating_allowed);
|
|
278
|
+
|
|
279
|
+
// Check if the result is async
|
|
280
|
+
var previous_tracking = tracking;
|
|
281
|
+
set_tracking(false);
|
|
282
|
+
var async_result = get_async_track_result(result);
|
|
283
|
+
set_tracking(previous_tracking);
|
|
284
|
+
|
|
285
|
+
if (async_result === null) {
|
|
286
|
+
// Sync result
|
|
287
|
+
update_tracked_value_clock(t, result);
|
|
288
|
+
if (request_id > 0 && boundary !== null) {
|
|
289
|
+
complete_boundary_request(boundary, request_id);
|
|
290
|
+
request_id = 0;
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Capture per-invocation so async closures (rejection handler, teardown)
|
|
296
|
+
// have a stable reference. The shared abort_controller is only read
|
|
297
|
+
// synchronously at the top of the pre_effect to abort the previous request.
|
|
298
|
+
var current_abort_controller = async_result.abort_controller;
|
|
299
|
+
abort_controller = current_abort_controller;
|
|
300
|
+
|
|
301
|
+
async_result.promise.then(
|
|
302
|
+
(resolved) => {
|
|
303
|
+
if (current_version !== version) {
|
|
304
|
+
// stale
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
update_tracked_value_clock(t, resolved);
|
|
308
|
+
complete_deferred_boundaries(t);
|
|
309
|
+
if (request_id > 0 && boundary !== null) {
|
|
310
|
+
complete_boundary_request(boundary, request_id);
|
|
311
|
+
request_id = 0;
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
(error) => {
|
|
315
|
+
if (current_version !== version) return; // stale
|
|
316
|
+
|
|
317
|
+
var is_internal_abort =
|
|
318
|
+
error === TRACKED_UPDATED || current_abort_controller?.signal?.reason === TRACKED_UPDATED;
|
|
319
|
+
if (is_internal_abort) {
|
|
320
|
+
// Internal abort (superseded by a new request) — don't set rejected
|
|
321
|
+
if (request_id > 0 && boundary !== null) {
|
|
322
|
+
complete_boundary_request(boundary, request_id, false);
|
|
323
|
+
request_id = 0;
|
|
324
|
+
}
|
|
325
|
+
complete_deferred_boundaries(t, false);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
update_tracked_value_clock(t, SUSPENSE_REJECTED);
|
|
330
|
+
complete_deferred_boundaries(t, false);
|
|
331
|
+
|
|
332
|
+
// Route error to catch boundary
|
|
333
|
+
var boundary_with_catch = get_boundary_with_catch(call_site_block);
|
|
334
|
+
if (boundary_with_catch !== null) {
|
|
335
|
+
handle_boundary_error(boundary_with_catch, error);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (request_id > 0 && boundary !== null) {
|
|
339
|
+
var should_show_resolved =
|
|
340
|
+
boundary_with_catch === boundary || boundary === null ? false : true;
|
|
341
|
+
complete_boundary_request(boundary, request_id, should_show_resolved);
|
|
342
|
+
request_id = 0;
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
return () => {
|
|
348
|
+
// Teardown: abort in-flight request when block is destroyed
|
|
349
|
+
if (current_abort_controller !== null && current_abort_controller.signal.aborted === false) {
|
|
350
|
+
current_abort_controller.abort(TRACKED_UPDATED);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
return t;
|
|
356
|
+
}
|