ripple 0.4.2 → 0.4.4

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.
Files changed (98) hide show
  1. package/CHANGELOG.md +164 -0
  2. package/package.json +10 -4
  3. package/src/constants.js +6 -0
  4. package/src/jsx-runtime.d.ts +2 -1
  5. package/src/runtime/array.js +15 -9
  6. package/src/runtime/index-client.js +25 -7
  7. package/src/runtime/index-server.js +1 -0
  8. package/src/runtime/internal/client/attributes.js +439 -0
  9. package/src/runtime/internal/client/blocks.js +20 -20
  10. package/src/runtime/internal/client/component.js +2 -5
  11. package/src/runtime/internal/client/composite.js +295 -95
  12. package/src/runtime/internal/client/constants.js +14 -6
  13. package/src/runtime/internal/client/context.js +3 -2
  14. package/src/runtime/internal/client/css.js +11 -26
  15. package/src/runtime/internal/client/errors.js +188 -0
  16. package/src/runtime/internal/client/events.js +79 -34
  17. package/src/runtime/internal/client/expression.js +25 -120
  18. package/src/runtime/internal/client/for.js +113 -262
  19. package/src/runtime/internal/client/head.js +4 -3
  20. package/src/runtime/internal/client/html.js +5 -3
  21. package/src/runtime/internal/client/hydrate.js +484 -0
  22. package/src/runtime/internal/client/hydration-enabled.js +7 -0
  23. package/src/runtime/internal/client/hydration.js +16 -81
  24. package/src/runtime/internal/client/if.js +69 -10
  25. package/src/runtime/internal/client/index.js +18 -10
  26. package/src/runtime/internal/client/operations.js +11 -7
  27. package/src/runtime/internal/client/portal.js +3 -2
  28. package/src/runtime/internal/client/render.js +42 -325
  29. package/src/runtime/internal/client/root-boundary-enabled.js +7 -0
  30. package/src/runtime/internal/client/runtime.js +157 -371
  31. package/src/runtime/internal/client/template-ns.js +88 -0
  32. package/src/runtime/internal/client/template.js +118 -139
  33. package/src/runtime/internal/client/track-async.js +356 -0
  34. package/src/runtime/internal/client/try.js +133 -319
  35. package/src/runtime/internal/client/types.d.ts +48 -24
  36. package/src/runtime/internal/server/index.js +31 -29
  37. package/src/runtime/internal/server/prerender.js +27 -0
  38. package/src/server/index.js +1 -0
  39. package/src/utils/class-name.js +28 -0
  40. package/tests/client/basic/basic.errors.test.tsrx +3 -3
  41. package/tests/client/compiler/__snapshots__/compiler.typescript.test.tsrx.snap +0 -2
  42. package/tests/client/compiler/compiler.basic.test.tsrx +2 -2
  43. package/tests/client/compiler/compiler.tracked-access.test.tsrx +3 -3
  44. package/tests/client/composite/composite.dynamic-components.test.tsrx +40 -0
  45. package/tests/client/composite/composite.props.test.tsrx +10 -10
  46. package/tests/client/css/scoped-styles.test.tsrx +3 -1
  47. package/tests/client/derived-release.test.tsrx +3 -3
  48. package/tests/client/for-item.test.tsrx +39 -0
  49. package/tests/client/for-selector.test.tsrx +95 -0
  50. package/tests/client/svg.test.tsrx +273 -1
  51. package/tests/hydration/compiled/client/basic.js +88 -87
  52. package/tests/hydration/compiled/client/composite.js +15 -6
  53. package/tests/hydration/compiled/client/events.js +40 -48
  54. package/tests/hydration/compiled/client/for.js +191 -199
  55. package/tests/hydration/compiled/client/fragment-cursor.js +633 -334
  56. package/tests/hydration/compiled/client/head.js +22 -22
  57. package/tests/hydration/compiled/client/hmr.js +14 -5
  58. package/tests/hydration/compiled/client/html-in-template.js +5 -5
  59. package/tests/hydration/compiled/client/html.js +195 -177
  60. package/tests/hydration/compiled/client/if-children.js +136 -63
  61. package/tests/hydration/compiled/client/if-fragment-controlflow.js +63 -43
  62. package/tests/hydration/compiled/client/if.js +78 -30
  63. package/tests/hydration/compiled/client/mixed-control-flow.js +90 -72
  64. package/tests/hydration/compiled/client/nested-control-flow.js +266 -307
  65. package/tests/hydration/compiled/client/portal.js +20 -11
  66. package/tests/hydration/compiled/client/reactivity.js +29 -29
  67. package/tests/hydration/compiled/client/return.js +2 -2
  68. package/tests/hydration/compiled/client/streaming.js +33 -35
  69. package/tests/hydration/compiled/client/switch.js +25 -25
  70. package/tests/hydration/compiled/client/track-async-serialization.js +41 -41
  71. package/tests/hydration/compiled/client/try.js +15 -15
  72. package/tests/hydration/compiled/client/typed-text.js +8 -7
  73. package/tests/hydration/compiled/server/basic.js +6 -6
  74. package/tests/hydration/compiled/server/events.js +8 -8
  75. package/tests/hydration/compiled/server/for.js +22 -22
  76. package/tests/hydration/compiled/server/fragment-cursor.js +188 -52
  77. package/tests/hydration/compiled/server/head.js +6 -6
  78. package/tests/hydration/compiled/server/hmr.js +1 -1
  79. package/tests/hydration/compiled/server/html.js +1 -1
  80. package/tests/hydration/compiled/server/if-children.js +9 -9
  81. package/tests/hydration/compiled/server/if.js +5 -5
  82. package/tests/hydration/compiled/server/mixed-control-flow.js +4 -4
  83. package/tests/hydration/compiled/server/portal.js +1 -1
  84. package/tests/hydration/compiled/server/reactivity.js +9 -9
  85. package/tests/hydration/compiled/server/streaming.js +9 -9
  86. package/tests/hydration/compiled/server/switch.js +4 -4
  87. package/tests/hydration/compiled/server/track-async-serialization.js +16 -16
  88. package/tests/hydration/compiled/server/try.js +4 -4
  89. package/tests/hydration/compiled/server/typed-text.js +1 -1
  90. package/tests/hydration/components/fragment-cursor.tsrx +60 -0
  91. package/tests/hydration/components/track-async-serialization.tsrx +2 -2
  92. package/tests/hydration/fragment-cursor.test.js +34 -1
  93. package/tests/hydration/track-async-serialization.test.js +1 -1
  94. package/tests/server/basic.components.test.tsrx +3 -3
  95. package/tests/server/track-async-serialization.test.tsrx +6 -6
  96. package/tests/utils/tracked-types.test.js +3 -3
  97. package/types/index.d.ts +8 -8
  98. package/types/server.d.ts +17 -0
@@ -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
+ }