ripple 0.3.6 → 0.3.8

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 (110) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/package.json +2 -2
  3. package/src/compiler/phases/1-parse/index.js +37 -194
  4. package/src/compiler/phases/2-analyze/index.js +290 -26
  5. package/src/compiler/phases/3-transform/client/index.js +54 -14
  6. package/src/compiler/phases/3-transform/server/index.js +19 -35
  7. package/src/compiler/types/index.d.ts +3 -1
  8. package/src/compiler/types/parse.d.ts +0 -8
  9. package/src/compiler/utils.js +10 -6
  10. package/src/runtime/internal/client/composite.js +2 -2
  11. package/src/runtime/internal/client/index.js +2 -0
  12. package/src/runtime/internal/client/runtime.js +95 -45
  13. package/src/runtime/internal/client/types.d.ts +10 -0
  14. package/src/runtime/internal/client/utils.js +12 -0
  15. package/src/runtime/internal/server/index.js +89 -17
  16. package/src/runtime/internal/server/types.d.ts +10 -0
  17. package/src/utils/ast.js +1 -1
  18. package/tests/client/array/array.copy-within.test.ripple +12 -12
  19. package/tests/client/array/array.derived.test.ripple +46 -46
  20. package/tests/client/array/array.iteration.test.ripple +10 -10
  21. package/tests/client/array/array.mutations.test.ripple +20 -20
  22. package/tests/client/array/array.to-methods.test.ripple +6 -6
  23. package/tests/client/async-suspend.test.ripple +5 -5
  24. package/tests/client/basic/basic.attributes.test.ripple +81 -81
  25. package/tests/client/basic/basic.collections.test.ripple +9 -9
  26. package/tests/client/basic/basic.components.test.ripple +28 -28
  27. package/tests/client/basic/basic.errors.test.ripple +18 -18
  28. package/tests/client/basic/basic.events.test.ripple +37 -37
  29. package/tests/client/basic/basic.get-set.test.ripple +6 -6
  30. package/tests/client/basic/basic.reactivity.test.ripple +68 -68
  31. package/tests/client/basic/basic.rendering.test.ripple +19 -19
  32. package/tests/client/basic/basic.utilities.test.ripple +3 -3
  33. package/tests/client/boundaries.test.ripple +12 -12
  34. package/tests/client/compiler/__snapshots__/compiler.assignments.test.ripple.snap +5 -5
  35. package/tests/client/compiler/compiler.assignments.test.ripple +19 -19
  36. package/tests/client/compiler/compiler.basic.test.ripple +44 -15
  37. package/tests/client/compiler/compiler.tracked-access.test.ripple +68 -2
  38. package/tests/client/composite/composite.dynamic-components.test.ripple +9 -9
  39. package/tests/client/composite/composite.props.test.ripple +11 -11
  40. package/tests/client/composite/composite.reactivity.test.ripple +43 -43
  41. package/tests/client/composite/composite.render.test.ripple +3 -3
  42. package/tests/client/computed-properties.test.ripple +4 -4
  43. package/tests/client/date.test.ripple +42 -42
  44. package/tests/client/dynamic-elements.test.ripple +42 -42
  45. package/tests/client/events.test.ripple +70 -70
  46. package/tests/client/for.test.ripple +25 -25
  47. package/tests/client/head.test.ripple +19 -19
  48. package/tests/client/html.test.ripple +3 -3
  49. package/tests/client/input-value.test.ripple +84 -84
  50. package/tests/client/lazy-destructuring.test.ripple +123 -14
  51. package/tests/client/map.test.ripple +16 -16
  52. package/tests/client/media-query.test.ripple +7 -7
  53. package/tests/client/portal.test.ripple +11 -11
  54. package/tests/client/ref.test.ripple +4 -4
  55. package/tests/client/return.test.ripple +52 -52
  56. package/tests/client/set.test.ripple +6 -6
  57. package/tests/client/svg.test.ripple +5 -5
  58. package/tests/client/switch.test.ripple +44 -44
  59. package/tests/client/try.test.ripple +5 -5
  60. package/tests/client/url/url.derived.test.ripple +6 -6
  61. package/tests/client/url-search-params/url-search-params.derived.test.ripple +8 -8
  62. package/tests/client/url-search-params/url-search-params.iteration.test.ripple +10 -10
  63. package/tests/client/url-search-params/url-search-params.mutation.test.ripple +10 -10
  64. package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +18 -18
  65. package/tests/client/url-search-params/url-search-params.serialization.test.ripple +2 -2
  66. package/tests/hydration/compiled/client/events.js +25 -25
  67. package/tests/hydration/compiled/client/for.js +70 -66
  68. package/tests/hydration/compiled/client/head.js +25 -25
  69. package/tests/hydration/compiled/client/hmr.js +2 -2
  70. package/tests/hydration/compiled/client/html.js +3 -3
  71. package/tests/hydration/compiled/client/if-children.js +24 -24
  72. package/tests/hydration/compiled/client/if.js +18 -18
  73. package/tests/hydration/compiled/client/mixed-control-flow.js +9 -9
  74. package/tests/hydration/compiled/client/portal.js +3 -3
  75. package/tests/hydration/compiled/client/reactivity.js +16 -16
  76. package/tests/hydration/compiled/client/return.js +40 -40
  77. package/tests/hydration/compiled/client/switch.js +12 -12
  78. package/tests/hydration/compiled/server/events.js +19 -19
  79. package/tests/hydration/compiled/server/for.js +41 -41
  80. package/tests/hydration/compiled/server/head.js +26 -26
  81. package/tests/hydration/compiled/server/hmr.js +2 -2
  82. package/tests/hydration/compiled/server/html.js +2 -2
  83. package/tests/hydration/compiled/server/if-children.js +16 -16
  84. package/tests/hydration/compiled/server/if.js +11 -11
  85. package/tests/hydration/compiled/server/mixed-control-flow.js +6 -6
  86. package/tests/hydration/compiled/server/portal.js +2 -2
  87. package/tests/hydration/compiled/server/reactivity.js +16 -16
  88. package/tests/hydration/compiled/server/return.js +25 -25
  89. package/tests/hydration/compiled/server/switch.js +8 -8
  90. package/tests/hydration/components/events.ripple +25 -25
  91. package/tests/hydration/components/for.ripple +66 -66
  92. package/tests/hydration/components/head.ripple +16 -16
  93. package/tests/hydration/components/hmr.ripple +2 -2
  94. package/tests/hydration/components/html.ripple +3 -3
  95. package/tests/hydration/components/if-children.ripple +24 -24
  96. package/tests/hydration/components/if.ripple +18 -18
  97. package/tests/hydration/components/mixed-control-flow.ripple +9 -9
  98. package/tests/hydration/components/portal.ripple +3 -3
  99. package/tests/hydration/components/reactivity.ripple +16 -16
  100. package/tests/hydration/components/return.ripple +40 -40
  101. package/tests/hydration/components/switch.ripple +20 -20
  102. package/tests/server/await.test.ripple +3 -3
  103. package/tests/server/basic.attributes.test.ripple +34 -34
  104. package/tests/server/basic.components.test.ripple +10 -10
  105. package/tests/server/basic.test.ripple +38 -40
  106. package/tests/server/composite.props.test.ripple +9 -9
  107. package/tests/server/dynamic-elements.test.ripple +13 -12
  108. package/tests/server/head.test.ripple +11 -11
  109. package/tests/server/lazy-destructuring.test.ripple +72 -0
  110. package/types/index.d.ts +7 -2
@@ -1,20 +1,42 @@
1
- import { flushSync, track } from 'ripple';
1
+ import { flushSync, track, trackSplit } from 'ripple';
2
2
 
3
3
  describe('lazy destructuring', () => {
4
+ it('supports tracked value getter and setter', () => {
5
+ component Test() {
6
+ let count = track(1);
7
+ let doubled = track(() => count.value * 2);
8
+
9
+ <div>{`${count.value}-${doubled.value}`}</div>
10
+ <button
11
+ onClick={() => {
12
+ count.value = 5;
13
+ }}
14
+ >
15
+ {'set'}
16
+ </button>
17
+ }
18
+
19
+ render(Test);
20
+ expect(container.querySelector('div')!.textContent).toBe('1-2');
21
+ container.querySelector('button')!.click();
22
+ flushSync();
23
+ expect(container.querySelector('div')!.textContent).toBe('5-10');
24
+ });
25
+
4
26
  it('lazily accesses object properties with const', () => {
5
27
  component Inner(&{ a, b }: { a: number; b: string }) {
6
28
  <pre>{`${a}-${b}`}</pre>
7
29
  }
8
30
 
9
31
  component Test() {
10
- let a = track(1);
11
- let b = track('hello');
32
+ let &[a] = track(1);
33
+ let &[b] = track('hello');
12
34
 
13
- <Inner {@a} {@b} />
35
+ <Inner {a} {b} />
14
36
  <button
15
37
  onClick={() => {
16
- @a = 2;
17
- @b = 'world';
38
+ a = 2;
39
+ b = 'world';
18
40
  }}
19
41
  >
20
42
  {'update'}
@@ -34,14 +56,14 @@ describe('lazy destructuring', () => {
34
56
  }
35
57
 
36
58
  component Test() {
37
- let first = track(10);
38
- let second = track(20);
59
+ let &[first] = track(10);
60
+ let &[second] = track(20);
39
61
 
40
- <Inner {@first} {@second} />
62
+ <Inner {first} {second} />
41
63
  <button
42
64
  onClick={() => {
43
- @first = 30;
44
- @second = 40;
65
+ first = 30;
66
+ second = 40;
45
67
  }}
46
68
  >
47
69
  {'update'}
@@ -85,12 +107,12 @@ describe('lazy destructuring', () => {
85
107
  }
86
108
 
87
109
  component Test() {
88
- let count = track(0);
110
+ let &[count] = track(0);
89
111
 
90
- <Inner {@count} />
112
+ <Inner {count} />
91
113
  <button
92
114
  onClick={() => {
93
- @count++;
115
+ count++;
94
116
  }}
95
117
  >
96
118
  {'increment'}
@@ -183,6 +205,38 @@ describe('lazy destructuring', () => {
183
205
  expect(container.querySelector('pre')!.textContent).toBe('1-99');
184
206
  });
185
207
 
208
+ it('does not apply the track tuple fast-path to trackSplit lazy arrays', () => {
209
+ component Test() {
210
+ const source = { a: 1, b: 2, c: 3 };
211
+ let [a, b, rest] = trackSplit(source, ['a', 'b']);
212
+ <pre>{`${a.value}-${b.value}-${rest.value.c}`}</pre>
213
+ }
214
+
215
+ render(Test);
216
+ expect(container.querySelector('pre')!.textContent).toBe('1-2-3');
217
+ });
218
+
219
+ it('supports rest destructuring from iterable array-like tracked values', () => {
220
+ component Test() {
221
+ let &[value, ...rest] = track(0);
222
+ <pre>{`${value}-${rest.length}-${rest[0] === value}`}</pre>
223
+ }
224
+
225
+ render(Test);
226
+ expect(container.querySelector('pre')!.textContent).toBe('0-1-false');
227
+ });
228
+
229
+ it('supports rest destructuring from length-only array-like sources', () => {
230
+ component Test() {
231
+ const source = { 0: 'x', 1: 'y', 2: 'z', length: 3 };
232
+ const &[first, ...rest] = source;
233
+ <pre>{`${first}-${rest.join(',')}`}</pre>
234
+ }
235
+
236
+ render(Test);
237
+ expect(container.querySelector('pre')!.textContent).toBe('x-y,z');
238
+ });
239
+
186
240
  it('supports update expressions on lazy bindings with default values', () => {
187
241
  component Test() {
188
242
  const obj: { count?: number } = {};
@@ -206,4 +260,59 @@ describe('lazy destructuring', () => {
206
260
  render(Test);
207
261
  expect(container.querySelector('pre')!.textContent).toBe('Alice-30');
208
262
  });
263
+
264
+ it('supports standalone lazy array destructuring with track()', () => {
265
+ component Test() {
266
+ let count;
267
+ &[count] = track(0);
268
+ <div>{count}</div>
269
+ <button
270
+ onClick={() => {
271
+ count++;
272
+ }}
273
+ >
274
+ {'inc'}
275
+ </button>
276
+ }
277
+
278
+ render(Test);
279
+ expect(container.querySelector('div')!.textContent).toBe('0');
280
+ container.querySelector('button')!.click();
281
+ flushSync();
282
+ expect(container.querySelector('div')!.textContent).toBe('1');
283
+ });
284
+
285
+ it('supports standalone lazy array destructuring with second element', () => {
286
+ component Test() {
287
+ let value;
288
+ let tracked;
289
+ &[value, tracked] = track(42);
290
+ <div>{value}</div>
291
+ <button
292
+ onClick={() => {
293
+ value = 100;
294
+ }}
295
+ >
296
+ {'set'}
297
+ </button>
298
+ }
299
+
300
+ render(Test);
301
+ expect(container.querySelector('div')!.textContent).toBe('42');
302
+ container.querySelector('button')!.click();
303
+ flushSync();
304
+ expect(container.querySelector('div')!.textContent).toBe('100');
305
+ });
306
+
307
+ it('supports standalone lazy object destructuring', () => {
308
+ component Test() {
309
+ let a;
310
+ let b;
311
+ &{ a, b } = { a: 10, b: 20 };
312
+ <pre>{`${a}-${b}`}</pre>
313
+ }
314
+
315
+ render(Test);
316
+ expect(container.querySelector('pre')!.textContent).toBe('10-20');
317
+ });
209
318
  });
@@ -4,16 +4,16 @@ describe('RippleMap', () => {
4
4
  it('handles set with update and delete operations with a reactive size var', () => {
5
5
  component MapTest() {
6
6
  let map = RippleMap([['a', 1], ['b', 2], ['c', 3]]);
7
- let value = track(() => map.get('a'));
8
- let size = track(() => map.size);
7
+ let &[value] = track(() => map.get('a'));
8
+ let &[size] = track(() => map.size);
9
9
 
10
10
  <button onClick={() => map.set('d', 4)}>{'set'}</button>
11
11
  <button onClick={() => map.delete('b')}>{'delete'}</button>
12
12
  <button onClick={() => map.set('a', 5)}>{'update'}</button>
13
13
 
14
14
  <pre>{map.get('d')}</pre>
15
- <pre>{@size}</pre>
16
- <pre>{@value}</pre>
15
+ <pre>{size}</pre>
16
+ <pre>{value}</pre>
17
17
  }
18
18
 
19
19
  render(MapTest);
@@ -61,10 +61,10 @@ describe('RippleMap', () => {
61
61
  it('handles has operation and tracks reactive $has', () => {
62
62
  component MapTest() {
63
63
  let map = RippleMap([['a', 1], ['b', 2], ['c', 3]]);
64
- let has = track(() => map.has('b'));
64
+ let &[has] = track(() => map.has('b'));
65
65
 
66
66
  <button onClick={() => map.delete('b')}>{'delete'}</button>
67
- <pre>{@has}</pre>
67
+ <pre>{has}</pre>
68
68
  }
69
69
 
70
70
  render(MapTest);
@@ -81,15 +81,15 @@ describe('RippleMap', () => {
81
81
  it('handles reactivity of keys, values, and entries', () => {
82
82
  component MapTest() {
83
83
  let map = RippleMap([['x', 10], ['y', 20]]);
84
- let keys = track(() => Array.from(map.keys()));
85
- let values = track(() => Array.from(map.values()));
86
- let entries = track(() => Array.from(map.entries()));
84
+ let &[keys] = track(() => Array.from(map.keys()));
85
+ let &[values] = track(() => Array.from(map.values()));
86
+ let &[entries] = track(() => Array.from(map.entries()));
87
87
 
88
88
  <button onClick={() => map.delete('x')}>{'delete'}</button>
89
89
 
90
- <pre>{JSON.stringify(@keys)}</pre>
91
- <pre>{JSON.stringify(@values)}</pre>
92
- <pre>{JSON.stringify(@entries)}</pre>
90
+ <pre>{JSON.stringify(keys)}</pre>
91
+ <pre>{JSON.stringify(values)}</pre>
92
+ <pre>{JSON.stringify(entries)}</pre>
93
93
  }
94
94
 
95
95
  render(MapTest);
@@ -142,11 +142,11 @@ describe('RippleMap', () => {
142
142
  it('creates RippleMap with initial entries using RippleMap() shorthand syntax', () => {
143
143
  component MapTest() {
144
144
  let map = RippleMap([['a', 1], ['b', 2], ['c', 3]]);
145
- let value = track(() => map.get('b'));
145
+ let &[value] = track(() => map.get('b'));
146
146
 
147
147
  <button onClick={() => map.set('b', 10)}>{'update'}</button>
148
148
  <pre>{map.size}</pre>
149
- <pre>{@value}</pre>
149
+ <pre>{value}</pre>
150
150
  }
151
151
 
152
152
  render(MapTest);
@@ -164,13 +164,13 @@ describe('RippleMap', () => {
164
164
  it('handles all operations with RippleMap() shorthand syntax', () => {
165
165
  component MapTest() {
166
166
  let map = RippleMap([['x', 100], ['y', 200]]);
167
- let keys = track(() => Array.from(map.keys()));
167
+ let &[keys] = track(() => Array.from(map.keys()));
168
168
 
169
169
  <button onClick={() => map.set('z', 300)}>{'add'}</button>
170
170
  <button onClick={() => map.delete('x')}>{'delete'}</button>
171
171
  <button onClick={() => map.clear()}>{'clear'}</button>
172
172
 
173
- <pre>{JSON.stringify(@keys)}</pre>
173
+ <pre>{JSON.stringify(keys)}</pre>
174
174
  <pre>{map.size}</pre>
175
175
  }
176
176
 
@@ -51,10 +51,10 @@ describe('MediaQuery', () => {
51
51
  const media = '(min-width: 600px)';
52
52
 
53
53
  component App() {
54
- const medium = MediaQuery(media);
54
+ let &[medium] = MediaQuery(media);
55
55
 
56
56
  <div>
57
- <p>{@medium}</p>
57
+ <p>{medium}</p>
58
58
  </div>
59
59
  }
60
60
 
@@ -86,20 +86,20 @@ describe('MediaQuery', () => {
86
86
  const media = '(min-width: 600px)';
87
87
 
88
88
  component App() {
89
- let show = track(true);
89
+ let &[show] = track(true);
90
90
 
91
- if (@show) {
91
+ if (show) {
92
92
  <Child />
93
93
  }
94
94
 
95
- <button onClick={() => (@show = !@show)}>{'Toggle Child'}</button>
95
+ <button onClick={() => (show = !show)}>{'Toggle Child'}</button>
96
96
  }
97
97
 
98
98
  component Child() {
99
- const medium = MediaQuery(media);
99
+ let &[medium] = MediaQuery(media);
100
100
 
101
101
  <div>
102
- <p>{@medium}</p>
102
+ <p>{medium}</p>
103
103
  </div>
104
104
  }
105
105
 
@@ -46,15 +46,15 @@ describe('Portal', () => {
46
46
 
47
47
  it('cleans up portal content when destroyed via conditional rendering', () => {
48
48
  component TestPortal() {
49
- let open = track(true);
49
+ let &[open] = track(true);
50
50
 
51
- if (@open) {
51
+ if (open) {
52
52
  <Portal target={document.body}>
53
53
  <div class="test-portal">{'Conditional content'}</div>
54
54
  </Portal>
55
55
  }
56
56
 
57
- <button onClick={() => (@open = false)}>{'Close'}</button>
57
+ <button onClick={() => (open = false)}>{'Close'}</button>
58
58
  }
59
59
 
60
60
  render(TestPortal);
@@ -72,19 +72,19 @@ describe('Portal', () => {
72
72
 
73
73
  it('opens and closes portal via conditional rendering', () => {
74
74
  component TestPortal() {
75
- let open = track(false);
75
+ let &[open] = track(false);
76
76
 
77
- if (@open) {
77
+ if (open) {
78
78
  <Portal target={document.body}>
79
79
  <div class="test-portal">
80
80
  {'Content'}
81
- <button onClick={() => (@open = false)}>{'Close'}</button>
81
+ <button onClick={() => (open = false)}>{'Close'}</button>
82
82
  </div>
83
83
  </Portal>
84
84
  }
85
85
 
86
- if (!@open) {
87
- <button onClick={() => (@open = true)}>{'Open'}</button>
86
+ if (!open) {
87
+ <button onClick={() => (open = true)}>{'Open'}</button>
88
88
  }
89
89
  }
90
90
 
@@ -138,13 +138,13 @@ describe('Portal', () => {
138
138
 
139
139
  it('handles portal with reactive content', () => {
140
140
  component TestReactivePortal() {
141
- let count = track(0);
141
+ let &[count] = track(0);
142
142
 
143
143
  <Portal target={document.body}>
144
144
  <div class="test-portal">
145
145
  {'Count: '}
146
- {String(@count)}
147
- <button onClick={() => @count++}>{'Increment'}</button>
146
+ {String(count)}
147
+ <button onClick={() => count++}>{'Increment'}</button>
148
148
  </div>
149
149
  </Portal>
150
150
  }
@@ -50,7 +50,7 @@ describe('refs', () => {
50
50
  let logs: string[] = [];
51
51
 
52
52
  component App() {
53
- let value = track('test');
53
+ let &[value] = track('test');
54
54
 
55
55
  function inputRef(node: HTMLInputElement) {
56
56
  logs.push('ref called');
@@ -58,7 +58,7 @@ describe('refs', () => {
58
58
 
59
59
  const props = {
60
60
  id: 'example',
61
- @value,
61
+ value,
62
62
  [createRefKey()]: inputRef,
63
63
  };
64
64
 
@@ -81,7 +81,7 @@ describe('refs', () => {
81
81
  let logs: string[] = [];
82
82
 
83
83
  component App() {
84
- let value = track('test');
84
+ let &[value] = track('test');
85
85
 
86
86
  function inputRef(node: HTMLInputElement) {
87
87
  logs.push('ref called');
@@ -89,7 +89,7 @@ describe('refs', () => {
89
89
 
90
90
  const props = {
91
91
  id: 'example',
92
- @value,
92
+ value,
93
93
  };
94
94
 
95
95
  <input type="text" {ref inputRef} {...props} />