ripple 0.3.65 → 0.3.66

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 (60) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/package.json +3 -3
  3. package/src/jsx-runtime.d.ts +17 -3
  4. package/src/runtime/index-client.js +47 -8
  5. package/src/runtime/index-server.js +0 -2
  6. package/src/runtime/internal/client/index.js +5 -0
  7. package/src/runtime/internal/client/runtime.js +111 -11
  8. package/src/runtime/internal/client/types.d.ts +5 -0
  9. package/src/runtime/internal/server/blocks.js +1 -0
  10. package/src/runtime/internal/server/index.js +175 -20
  11. package/src/utils/errors.js +13 -0
  12. package/tests/client/async-suspend.test.tsrx +2 -2
  13. package/tests/client/basic/basic.get-set.test.tsrx +26 -26
  14. package/tests/client/compiler/__snapshots__/compiler.assignments.test.rsrx.snap +1 -1
  15. package/tests/client/compiler/__snapshots__/compiler.assignments.test.tsrx.snap +1 -1
  16. package/tests/client/compiler/compiler.assignments.test.tsrx +80 -0
  17. package/tests/client/compiler/compiler.tracked-access.test.tsrx +52 -8
  18. package/tests/client/compiler/compiler.typescript.test.tsrx +23 -0
  19. package/tests/client/lazy-array.test.tsrx +34 -0
  20. package/tests/client/lazy-destructuring.test.tsrx +79 -8
  21. package/tests/client/tracked-index-access.test.tsrx +113 -0
  22. package/tests/hydration/compiled/client/basic.js +2 -2
  23. package/tests/hydration/compiled/client/events.js +9 -9
  24. package/tests/hydration/compiled/client/for.js +50 -54
  25. package/tests/hydration/compiled/client/head.js +9 -9
  26. package/tests/hydration/compiled/client/hmr.js +1 -1
  27. package/tests/hydration/compiled/client/html.js +2 -2
  28. package/tests/hydration/compiled/client/if-children.js +14 -14
  29. package/tests/hydration/compiled/client/if.js +10 -10
  30. package/tests/hydration/compiled/client/mixed-control-flow.js +7 -7
  31. package/tests/hydration/compiled/client/portal.js +2 -2
  32. package/tests/hydration/compiled/client/reactivity.js +7 -7
  33. package/tests/hydration/compiled/client/return.js +37 -37
  34. package/tests/hydration/compiled/client/switch.js +8 -8
  35. package/tests/hydration/compiled/client/track-async-serialization.js +12 -12
  36. package/tests/hydration/compiled/client/try.js +116 -33
  37. package/tests/hydration/compiled/server/basic.js +2 -2
  38. package/tests/hydration/compiled/server/events.js +8 -8
  39. package/tests/hydration/compiled/server/for.js +21 -21
  40. package/tests/hydration/compiled/server/head.js +10 -10
  41. package/tests/hydration/compiled/server/hmr.js +1 -1
  42. package/tests/hydration/compiled/server/html.js +1 -1
  43. package/tests/hydration/compiled/server/if-children.js +9 -9
  44. package/tests/hydration/compiled/server/if.js +6 -6
  45. package/tests/hydration/compiled/server/mixed-control-flow.js +4 -4
  46. package/tests/hydration/compiled/server/portal.js +1 -1
  47. package/tests/hydration/compiled/server/reactivity.js +7 -7
  48. package/tests/hydration/compiled/server/return.js +14 -14
  49. package/tests/hydration/compiled/server/switch.js +4 -4
  50. package/tests/hydration/compiled/server/track-async-serialization.js +12 -12
  51. package/tests/hydration/compiled/server/try.js +116 -4
  52. package/tests/hydration/components/try.tsrx +26 -0
  53. package/tests/hydration/try.test.js +100 -1
  54. package/tests/server/await.test.tsrx +1 -1
  55. package/tests/server/compiler.test.tsrx +109 -0
  56. package/tests/server/lazy-destructuring.test.tsrx +62 -0
  57. package/tests/server/tracked-index-access.test.tsrx +76 -0
  58. package/tests/setup-hydration.js +31 -0
  59. package/types/index.d.ts +11 -9
  60. package/types/server.d.ts +2 -1
@@ -26,6 +26,29 @@ function getString(e: string = 'test') {
26
26
  expect(result.code).toMatchSnapshot();
27
27
  });
28
28
 
29
+ it('removes TypeScript-only expression wrappers from assignment and update targets', () => {
30
+ const source = `
31
+ beforeAll(() => {
32
+ (global as any).ResizeObserver = createMockResizeObserver;
33
+ (global as any).count++;
34
+ });
35
+
36
+ component Test() {
37
+ let toggle: { value: boolean } | undefined;
38
+ toggle!.value = false;
39
+ toggle!.value++;
40
+ }`;
41
+
42
+ const { code } = compile(source, 'test.tsrx', { mode: 'client' });
43
+
44
+ expect(code).toContain('global.ResizeObserver = createMockResizeObserver');
45
+ expect(code).toContain('global.count++');
46
+ expect(code).toContain('toggle.value = false');
47
+ expect(code).toContain('toggle.value++');
48
+ expect(code).not.toContain('as any');
49
+ expect(code).not.toContain('!');
50
+ });
51
+
29
52
  it('removes class TypeScript syntax from JS output', () => {
30
53
  const source = `interface BaseEvent {}
31
54
 
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { flushSync, track } from 'ripple';
3
+
4
+ describe('lazy array destructuring', () => {
5
+ it('updates unknown lazy array bindings through tracked values', () => {
6
+ component Child({ tr: &[count, tr] }) {
7
+ <button
8
+ onClick={() => {
9
+ count++;
10
+ tr[0]++;
11
+ }}
12
+ >
13
+ {count}
14
+ </button>
15
+ }
16
+
17
+ component App() {
18
+ let tracked = track(0);
19
+
20
+ <Child tr={tracked} />
21
+ }
22
+
23
+ render(App);
24
+
25
+ const button = container.querySelector('button');
26
+ expect(button?.textContent).toBe('0');
27
+
28
+ flushSync(() => {
29
+ button?.click();
30
+ });
31
+
32
+ expect(button?.textContent).toBe('2');
33
+ });
34
+ });
@@ -1,3 +1,4 @@
1
+ import type { Tracked } from 'ripple';
1
2
  import { flushSync, track } from 'ripple';
2
3
 
3
4
  describe('lazy destructuring', () => {
@@ -77,9 +78,22 @@ describe('lazy destructuring', () => {
77
78
  expect(container.querySelector('pre')!.textContent).toBe('30-40');
78
79
  });
79
80
 
81
+ it('preserves numeric member access on lazy array value bindings', () => {
82
+ component Child({ pair: &[first] }: { pair: [{ 0: string }] }) {
83
+ <pre>{first[0]}</pre>
84
+ }
85
+
86
+ component Test() {
87
+ <Child pair={[{ 0: 'x' }]} />
88
+ }
89
+
90
+ render(Test);
91
+ expect(container.querySelector('pre')!.textContent).toBe('x');
92
+ });
93
+
80
94
  it('supports default values in lazy object destructuring', () => {
81
95
  component Test() {
82
- const obj = { a: 5 };
96
+ const obj: { a: number; b?: number } = { a: 5 };
83
97
  const &{ a, b = 99 } = obj;
84
98
  <pre>{`${a}-${b}`}</pre>
85
99
  }
@@ -127,7 +141,7 @@ describe('lazy destructuring', () => {
127
141
  });
128
142
 
129
143
  it('supports nested lazy destructuring in non-lazy component params', () => {
130
- component Inner({ something: &[first, second] }) {
144
+ component Inner({ something: &[first, second] }: { something: Tracked<number> }) {
131
145
  first = second.value + 1;
132
146
  <pre>{`${first}-${second.value}`}</pre>
133
147
  }
@@ -143,7 +157,11 @@ describe('lazy destructuring', () => {
143
157
  it(
144
158
  'preserves lazy getter/setter behavior for nested rest destructuring in non-lazy component params',
145
159
  () => {
146
- component Inner({ values: [head, ...&{ length: rest_length, 0: first_rest }] }) {
160
+ component Inner({
161
+ values: [head, ...&{ length: rest_length, 0: first_rest }],
162
+ }: {
163
+ values: [number, Tracked<number>, Tracked<number>];
164
+ }) {
147
165
  const before = `${first_rest?.value ?? 'nil'}-${rest_length}`;
148
166
  rest_length = 0;
149
167
  <pre>{`${head}-${before}-${first_rest?.value ?? 'nil'}-${rest_length}`}</pre>
@@ -175,7 +193,7 @@ describe('lazy destructuring', () => {
175
193
  component Test() {
176
194
  const something = track(1);
177
195
 
178
- function getInfo({ something: &[first, second] }) {
196
+ function getInfo({ something: &[first, second] }: { something: Tracked<number> }) {
179
197
  first = second.value + 1;
180
198
  return `${first}-${second.value}`;
181
199
  }
@@ -192,7 +210,11 @@ describe('lazy destructuring', () => {
192
210
  'preserves lazy getter/setter behavior for nested rest destructuring in non-lazy function params',
193
211
  () => {
194
212
  component Test() {
195
- function summarize({ values: [head, ...&{ length: rest_length, 0: first_rest }] }) {
213
+ function summarize({
214
+ values: [head, ...&{ length: rest_length, 0: first_rest }],
215
+ }: {
216
+ values: [number, Tracked<number>, Tracked<number>];
217
+ }) {
196
218
  const before = `${first_rest?.value ?? 'nil'}-${rest_length}`;
197
219
  rest_length = 0;
198
220
  return `${head}-${before}-${first_rest?.value ?? 'nil'}-${rest_length}`;
@@ -276,11 +298,11 @@ describe('lazy destructuring', () => {
276
298
  it('supports rest destructuring from iterable array-like tracked values', () => {
277
299
  component Test() {
278
300
  let &[value, ...rest] = track(0);
279
- <pre>{`${value}-${rest.length}-${rest[0] === value}`}</pre>
301
+ <pre>{`${value}-${rest.length}-${(rest[0] as Tracked<number>).value === value}`}</pre>
280
302
  }
281
303
 
282
304
  render(Test);
283
- expect(container.querySelector('pre')!.textContent).toBe('0-1-false');
305
+ expect(container.querySelector('pre')!.textContent).toBe('0-1-true');
284
306
  });
285
307
 
286
308
  it('supports rest destructuring from length-only array-like sources', () => {
@@ -294,6 +316,23 @@ describe('lazy destructuring', () => {
294
316
  expect(container.querySelector('pre')!.textContent).toBe('x-y,z');
295
317
  });
296
318
 
319
+ it('supports rest destructuring from iterable sources', () => {
320
+ component Test() {
321
+ const source = {
322
+ *[Symbol.iterator]() {
323
+ yield 'x';
324
+ yield 'y';
325
+ yield 'z';
326
+ },
327
+ };
328
+ const &[first, ...rest] = source;
329
+ <pre>{`${first}-${rest.join(',')}`}</pre>
330
+ }
331
+
332
+ render(Test);
333
+ expect(container.querySelector('pre')!.textContent).toBe('x-y,z');
334
+ });
335
+
297
336
  it('supports update expressions on lazy bindings with default values', () => {
298
337
  component Test() {
299
338
  const obj: { count?: number } = {};
@@ -320,7 +359,7 @@ describe('lazy destructuring', () => {
320
359
 
321
360
  it('supports standalone lazy array destructuring with track()', () => {
322
361
  component Test() {
323
- let count;
362
+ let count: number;
324
363
  &[count] = track(0);
325
364
  <div>{count}</div>
326
365
  <button
@@ -361,6 +400,38 @@ describe('lazy destructuring', () => {
361
400
  expect(container.querySelector('div')!.textContent).toBe('100');
362
401
  });
363
402
 
403
+ it('supports direct value access on tracked values', () => {
404
+ component Test() {
405
+ let tracked = track(0);
406
+ ++tracked.value;
407
+ tracked.value++;
408
+ tracked.value = tracked.value + 1;
409
+ let value = tracked.value;
410
+ let ref = tracked;
411
+
412
+ <pre>{`${value}-${ref === tracked}`}</pre>
413
+ }
414
+
415
+ render(Test);
416
+ expect(container.querySelector('pre')!.textContent).toBe('3-true');
417
+ });
418
+
419
+ it('supports lazy destructured tracked ref value access', () => {
420
+ component Child({ pair: &[value, tracked_ref] }: { pair: Tracked<number> }) {
421
+ ++tracked_ref.value;
422
+ tracked_ref.value++;
423
+ <pre>{`${value}-${tracked_ref.value}`}</pre>
424
+ }
425
+
426
+ component Test() {
427
+ let tracked = track(0);
428
+ <Child pair={tracked} />
429
+ }
430
+
431
+ render(Test);
432
+ expect(container.querySelector('pre')!.textContent).toBe('2-2');
433
+ });
434
+
364
435
  it('supports standalone lazy object destructuring', () => {
365
436
  component Test() {
366
437
  let a;
@@ -0,0 +1,113 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { track } from 'ripple';
3
+ import {
4
+ lazy_array_get,
5
+ lazy_array_set,
6
+ lazy_array_update,
7
+ } from '../../src/runtime/internal/client/runtime.js';
8
+
9
+ const value_message = /Use \.value or &\[\] lazy destructuring/;
10
+ const reference_message = /Use the tracked value directly instead/;
11
+ const value_index = Number(0);
12
+ const reference_index = Number(1);
13
+
14
+ describe('client tracked numeric access', () => {
15
+ it('throws when tracked values are accessed through numeric properties', () => {
16
+ let value: any;
17
+
18
+ component Test() {
19
+ value = track(0);
20
+ <div />
21
+ }
22
+
23
+ render(Test);
24
+
25
+ expect(() => value[value_index]).toThrow(value_message);
26
+ expect(() => {
27
+ value[value_index] = 1;
28
+ }).toThrow(value_message);
29
+ expect(() => value[reference_index]).toThrow(reference_message);
30
+ });
31
+
32
+ it('throws when derived values are accessed through numeric properties', () => {
33
+ let value: any;
34
+
35
+ component Test() {
36
+ value = track(() => 1);
37
+ <div />
38
+ }
39
+
40
+ render(Test);
41
+
42
+ expect(() => value[value_index]).toThrow(value_message);
43
+ expect(() => {
44
+ value[value_index] = 2;
45
+ }).toThrow(value_message);
46
+ expect(() => value[reference_index]).toThrow(reference_message);
47
+ });
48
+
49
+ it('sets tracked lazy index 0 and rejects tracked lazy index 1', () => {
50
+ let value: any;
51
+ let next: any;
52
+
53
+ component Test() {
54
+ value = track(0);
55
+ next = track(1);
56
+ <div />
57
+ }
58
+
59
+ render(Test);
60
+
61
+ lazy_array_set(value, 2, 0);
62
+ expect(value.value).toBe(2);
63
+ expect(() => {
64
+ lazy_array_set(value, next, 1);
65
+ }).toThrow(reference_message);
66
+ expect(() => {
67
+ lazy_array_update(value, 1);
68
+ }).toThrow(reference_message);
69
+ });
70
+
71
+ it('returns undefined for tracked lazy indexes past the tuple length', () => {
72
+ let value: any;
73
+ let derived: any;
74
+
75
+ component Test() {
76
+ value = track(0);
77
+ derived = track(() => value.value + 1);
78
+ <div />
79
+ }
80
+
81
+ render(Test);
82
+
83
+ expect(lazy_array_get(value, 0)).toBe(0);
84
+ expect(lazy_array_get(value, 1)).toBe(value);
85
+ expect(lazy_array_get(value, 2)).toBeUndefined();
86
+ expect(lazy_array_get(derived, 0)).toBe(1);
87
+ expect(lazy_array_get(derived, 1)).toBe(derived);
88
+ expect(lazy_array_get(derived, 2)).toBeUndefined();
89
+ });
90
+
91
+ it('ignores tracked lazy writes past the tuple length', () => {
92
+ let value: any;
93
+ let derived: any;
94
+
95
+ component Test() {
96
+ value = track(0);
97
+ derived = track(() => value.value + 1);
98
+ <div />
99
+ }
100
+
101
+ render(Test);
102
+
103
+ lazy_array_set(value, 10, 2);
104
+ lazy_array_update(value, 2);
105
+ lazy_array_set(derived, 20, 2);
106
+ lazy_array_update(derived, 2);
107
+
108
+ expect(lazy_array_get(value, 2)).toBeUndefined();
109
+ expect(lazy_array_get(derived, 2)).toBeUndefined();
110
+ expect(Object.hasOwn(value, 2)).toBe(false);
111
+ expect(Object.hasOwn(derived, 2)).toBe(false);
112
+ });
113
+ });
@@ -537,7 +537,7 @@ export function DynamicArrayFromTrack(__anchor, _, __block) {
537
537
  {
538
538
  var expression_23 = _$_.child(div_16);
539
539
 
540
- _$_.expression(expression_23, () => _$_.get(lazy));
540
+ _$_.expression(expression_23, () => lazy.value);
541
541
  _$_.pop(div_16);
542
542
  }
543
543
 
@@ -707,7 +707,7 @@ export function TextPropWithToggle(__anchor, _, __block) {
707
707
  node_5,
708
708
  {
709
709
  get children() {
710
- return _$_.normalize_children(_$_.get(lazy_1) ? 'hello' : '');
710
+ return _$_.normalize_children(lazy_1.value ? 'hello' : '');
711
711
  }
712
712
  },
713
713
  _$_.active_block
@@ -29,7 +29,7 @@ export function ClickCounter(__anchor, _, __block) {
29
29
  {
30
30
  var expression = _$_.child(span_1);
31
31
 
32
- _$_.expression(expression, () => _$_.get(lazy));
32
+ _$_.expression(expression, () => lazy.value);
33
33
  _$_.pop(span_1);
34
34
  }
35
35
  }
@@ -56,7 +56,7 @@ export function IncrementDecrement(__anchor, _, __block) {
56
56
  {
57
57
  var expression_1 = _$_.child(span_2);
58
58
 
59
- _$_.expression(expression_1, () => _$_.get(lazy_1));
59
+ _$_.expression(expression_1, () => lazy_1.value);
60
60
  _$_.pop(span_2);
61
61
  }
62
62
 
@@ -94,7 +94,7 @@ export function MultipleEvents(__anchor, _, __block) {
94
94
  {
95
95
  var expression_2 = _$_.child(span_3);
96
96
 
97
- _$_.expression(expression_2, () => _$_.get(lazy_2));
97
+ _$_.expression(expression_2, () => lazy_2.value);
98
98
  _$_.pop(span_3);
99
99
  }
100
100
 
@@ -103,7 +103,7 @@ export function MultipleEvents(__anchor, _, __block) {
103
103
  {
104
104
  var expression_3 = _$_.child(span_4);
105
105
 
106
- _$_.expression(expression_3, () => _$_.get(lazy_3));
106
+ _$_.expression(expression_3, () => lazy_3.value);
107
107
  _$_.pop(span_4);
108
108
  }
109
109
  }
@@ -135,7 +135,7 @@ export function MultiStateUpdate(__anchor, _, __block) {
135
135
  {
136
136
  var expression_4 = _$_.child(span_5);
137
137
 
138
- _$_.expression(expression_4, () => _$_.get(lazy_4));
138
+ _$_.expression(expression_4, () => lazy_4.value);
139
139
  _$_.pop(span_5);
140
140
  }
141
141
 
@@ -144,7 +144,7 @@ export function MultiStateUpdate(__anchor, _, __block) {
144
144
  {
145
145
  var expression_5 = _$_.child(span_6);
146
146
 
147
- _$_.expression(expression_5, () => _$_.get(lazy_5));
147
+ _$_.expression(expression_5, () => lazy_5.value);
148
148
  _$_.pop(span_6);
149
149
  }
150
150
  }
@@ -163,13 +163,13 @@ export function ToggleButton(__anchor, _, __block) {
163
163
  var button_6 = _$_.child(div_5);
164
164
 
165
165
  button_6.__click = () => {
166
- _$_.set(lazy_6, !_$_.get(lazy_6));
166
+ _$_.set(lazy_6, !lazy_6.value);
167
167
  };
168
168
 
169
169
  {
170
170
  var expression_6 = _$_.child(button_6);
171
171
 
172
- _$_.expression(expression_6, () => _$_.get(lazy_6) ? 'ON' : 'OFF');
172
+ _$_.expression(expression_6, () => lazy_6.value ? 'ON' : 'OFF');
173
173
  _$_.pop(button_6);
174
174
  }
175
175
  }
@@ -221,7 +221,7 @@ export function ParentWithChildButton(__anchor, _, __block) {
221
221
  {
222
222
  var expression_8 = _$_.child(span_7);
223
223
 
224
- _$_.expression(expression_8, () => _$_.get(lazy_7));
224
+ _$_.expression(expression_8, () => lazy_7.value);
225
225
  _$_.pop(span_7);
226
226
  }
227
227