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.
- package/CHANGELOG.md +43 -0
- package/package.json +2 -2
- package/src/compiler/phases/1-parse/index.js +37 -194
- package/src/compiler/phases/2-analyze/index.js +290 -26
- package/src/compiler/phases/3-transform/client/index.js +54 -14
- package/src/compiler/phases/3-transform/server/index.js +19 -35
- package/src/compiler/types/index.d.ts +3 -1
- package/src/compiler/types/parse.d.ts +0 -8
- package/src/compiler/utils.js +10 -6
- package/src/runtime/internal/client/composite.js +2 -2
- package/src/runtime/internal/client/index.js +2 -0
- package/src/runtime/internal/client/runtime.js +95 -45
- package/src/runtime/internal/client/types.d.ts +10 -0
- package/src/runtime/internal/client/utils.js +12 -0
- package/src/runtime/internal/server/index.js +89 -17
- package/src/runtime/internal/server/types.d.ts +10 -0
- package/src/utils/ast.js +1 -1
- package/tests/client/array/array.copy-within.test.ripple +12 -12
- package/tests/client/array/array.derived.test.ripple +46 -46
- package/tests/client/array/array.iteration.test.ripple +10 -10
- package/tests/client/array/array.mutations.test.ripple +20 -20
- package/tests/client/array/array.to-methods.test.ripple +6 -6
- package/tests/client/async-suspend.test.ripple +5 -5
- package/tests/client/basic/basic.attributes.test.ripple +81 -81
- package/tests/client/basic/basic.collections.test.ripple +9 -9
- package/tests/client/basic/basic.components.test.ripple +28 -28
- package/tests/client/basic/basic.errors.test.ripple +18 -18
- package/tests/client/basic/basic.events.test.ripple +37 -37
- package/tests/client/basic/basic.get-set.test.ripple +6 -6
- package/tests/client/basic/basic.reactivity.test.ripple +68 -68
- package/tests/client/basic/basic.rendering.test.ripple +19 -19
- package/tests/client/basic/basic.utilities.test.ripple +3 -3
- package/tests/client/boundaries.test.ripple +12 -12
- package/tests/client/compiler/__snapshots__/compiler.assignments.test.ripple.snap +5 -5
- package/tests/client/compiler/compiler.assignments.test.ripple +19 -19
- package/tests/client/compiler/compiler.basic.test.ripple +44 -15
- package/tests/client/compiler/compiler.tracked-access.test.ripple +68 -2
- package/tests/client/composite/composite.dynamic-components.test.ripple +9 -9
- package/tests/client/composite/composite.props.test.ripple +11 -11
- package/tests/client/composite/composite.reactivity.test.ripple +43 -43
- package/tests/client/composite/composite.render.test.ripple +3 -3
- package/tests/client/computed-properties.test.ripple +4 -4
- package/tests/client/date.test.ripple +42 -42
- package/tests/client/dynamic-elements.test.ripple +42 -42
- package/tests/client/events.test.ripple +70 -70
- package/tests/client/for.test.ripple +25 -25
- package/tests/client/head.test.ripple +19 -19
- package/tests/client/html.test.ripple +3 -3
- package/tests/client/input-value.test.ripple +84 -84
- package/tests/client/lazy-destructuring.test.ripple +123 -14
- package/tests/client/map.test.ripple +16 -16
- package/tests/client/media-query.test.ripple +7 -7
- package/tests/client/portal.test.ripple +11 -11
- package/tests/client/ref.test.ripple +4 -4
- package/tests/client/return.test.ripple +52 -52
- package/tests/client/set.test.ripple +6 -6
- package/tests/client/svg.test.ripple +5 -5
- package/tests/client/switch.test.ripple +44 -44
- package/tests/client/try.test.ripple +5 -5
- package/tests/client/url/url.derived.test.ripple +6 -6
- package/tests/client/url-search-params/url-search-params.derived.test.ripple +8 -8
- package/tests/client/url-search-params/url-search-params.iteration.test.ripple +10 -10
- package/tests/client/url-search-params/url-search-params.mutation.test.ripple +10 -10
- package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +18 -18
- package/tests/client/url-search-params/url-search-params.serialization.test.ripple +2 -2
- package/tests/hydration/compiled/client/events.js +25 -25
- package/tests/hydration/compiled/client/for.js +70 -66
- package/tests/hydration/compiled/client/head.js +25 -25
- package/tests/hydration/compiled/client/hmr.js +2 -2
- package/tests/hydration/compiled/client/html.js +3 -3
- package/tests/hydration/compiled/client/if-children.js +24 -24
- package/tests/hydration/compiled/client/if.js +18 -18
- package/tests/hydration/compiled/client/mixed-control-flow.js +9 -9
- package/tests/hydration/compiled/client/portal.js +3 -3
- package/tests/hydration/compiled/client/reactivity.js +16 -16
- package/tests/hydration/compiled/client/return.js +40 -40
- package/tests/hydration/compiled/client/switch.js +12 -12
- package/tests/hydration/compiled/server/events.js +19 -19
- package/tests/hydration/compiled/server/for.js +41 -41
- package/tests/hydration/compiled/server/head.js +26 -26
- package/tests/hydration/compiled/server/hmr.js +2 -2
- package/tests/hydration/compiled/server/html.js +2 -2
- package/tests/hydration/compiled/server/if-children.js +16 -16
- package/tests/hydration/compiled/server/if.js +11 -11
- package/tests/hydration/compiled/server/mixed-control-flow.js +6 -6
- package/tests/hydration/compiled/server/portal.js +2 -2
- package/tests/hydration/compiled/server/reactivity.js +16 -16
- package/tests/hydration/compiled/server/return.js +25 -25
- package/tests/hydration/compiled/server/switch.js +8 -8
- package/tests/hydration/components/events.ripple +25 -25
- package/tests/hydration/components/for.ripple +66 -66
- package/tests/hydration/components/head.ripple +16 -16
- package/tests/hydration/components/hmr.ripple +2 -2
- package/tests/hydration/components/html.ripple +3 -3
- package/tests/hydration/components/if-children.ripple +24 -24
- package/tests/hydration/components/if.ripple +18 -18
- package/tests/hydration/components/mixed-control-flow.ripple +9 -9
- package/tests/hydration/components/portal.ripple +3 -3
- package/tests/hydration/components/reactivity.ripple +16 -16
- package/tests/hydration/components/return.ripple +40 -40
- package/tests/hydration/components/switch.ripple +20 -20
- package/tests/server/await.test.ripple +3 -3
- package/tests/server/basic.attributes.test.ripple +34 -34
- package/tests/server/basic.components.test.ripple +10 -10
- package/tests/server/basic.test.ripple +38 -40
- package/tests/server/composite.props.test.ripple +9 -9
- package/tests/server/dynamic-elements.test.ripple +13 -12
- package/tests/server/head.test.ripple +11 -11
- package/tests/server/lazy-destructuring.test.ripple +72 -0
- package/types/index.d.ts +7 -2
|
@@ -76,8 +76,16 @@ function mark_control_flow_has_template(path) {
|
|
|
76
76
|
* @param {AST.Identifier} source_id - The identifier to access properties on
|
|
77
77
|
* @param {AnalysisState} state - The analysis state
|
|
78
78
|
* @param {boolean} writable - Whether assignments/updates should be supported (let vs const)
|
|
79
|
+
* @param {boolean} is_track_call - Whether the RHS is a Ripple track() call
|
|
79
80
|
*/
|
|
80
|
-
function setup_lazy_transforms(pattern, source_id, state, writable) {
|
|
81
|
+
function setup_lazy_transforms(pattern, source_id, state, writable, is_track_call) {
|
|
82
|
+
// For ArrayPattern from track() calls, use direct get/set calls as a fast path
|
|
83
|
+
// instead of going through prototype getters source[0]/source[1]
|
|
84
|
+
if (pattern.type === 'ArrayPattern' && is_track_call) {
|
|
85
|
+
setup_lazy_array_transforms(pattern, source_id, state, writable);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
81
89
|
const paths = extract_paths(pattern);
|
|
82
90
|
|
|
83
91
|
for (const path of paths) {
|
|
@@ -141,6 +149,214 @@ function setup_lazy_transforms(pattern, source_id, state, writable) {
|
|
|
141
149
|
}
|
|
142
150
|
}
|
|
143
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Set up fast-path transforms for lazy array destructuring of tracked values.
|
|
154
|
+
* For index 0 (the value): uses _$_.get/set/update directly instead of source[0] getters.
|
|
155
|
+
* For index 1 (the tracked ref): returns source directly instead of source[1].
|
|
156
|
+
* @param {AST.ArrayPattern} pattern - The array destructuring pattern
|
|
157
|
+
* @param {AST.Identifier} source_id - The identifier for the tracked value
|
|
158
|
+
* @param {AnalysisState} state - The analysis state
|
|
159
|
+
* @param {boolean} writable - Whether assignments/updates should be supported
|
|
160
|
+
*/
|
|
161
|
+
function setup_lazy_array_transforms(pattern, source_id, state, writable) {
|
|
162
|
+
for (let i = 0; i < pattern.elements.length; i++) {
|
|
163
|
+
const element = pattern.elements[i];
|
|
164
|
+
if (!element) continue;
|
|
165
|
+
|
|
166
|
+
// Rest elements — fall back to generic source.slice(i)
|
|
167
|
+
if (element.type === 'RestElement') {
|
|
168
|
+
const rest_paths = extract_paths(pattern);
|
|
169
|
+
for (const path of rest_paths) {
|
|
170
|
+
if (!path.is_rest) continue;
|
|
171
|
+
const name = /** @type {AST.Identifier} */ (path.node).name;
|
|
172
|
+
const binding = state.scope.get(name);
|
|
173
|
+
if (binding !== null) {
|
|
174
|
+
binding.kind = path.has_default_value ? 'lazy_fallback' : 'lazy';
|
|
175
|
+
binding.transform = {
|
|
176
|
+
read: (_) => path.expression(source_id),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const actual = element.type === 'AssignmentPattern' ? element.left : element;
|
|
184
|
+
const has_fallback = element.type === 'AssignmentPattern';
|
|
185
|
+
const fallback_value = has_fallback
|
|
186
|
+
? /** @type {AST.AssignmentPattern} */ (element).right
|
|
187
|
+
: null;
|
|
188
|
+
|
|
189
|
+
if (actual.type === 'Identifier' && i <= 1) {
|
|
190
|
+
const name = actual.name;
|
|
191
|
+
const binding = state.scope.get(name);
|
|
192
|
+
if (binding === null) continue;
|
|
193
|
+
|
|
194
|
+
binding.kind = has_fallback ? 'lazy_fallback' : 'lazy';
|
|
195
|
+
|
|
196
|
+
if (i === 0) {
|
|
197
|
+
// Fast path for index 0: use _$_.get(source) instead of source[0]
|
|
198
|
+
const read_expr = has_fallback
|
|
199
|
+
? () => b.call('_$_.fallback', b.call('_$_.get', source_id), fallback_value)
|
|
200
|
+
: () => b.call('_$_.get', source_id);
|
|
201
|
+
|
|
202
|
+
// Signal that read already produces an unwrapped value (calls _$_.get internally)
|
|
203
|
+
binding.read_unwraps = true;
|
|
204
|
+
|
|
205
|
+
binding.transform = {
|
|
206
|
+
read: (_) => read_expr(),
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
if (writable) {
|
|
210
|
+
binding.transform.assign = (_, value) => {
|
|
211
|
+
return b.call('_$_.set', source_id, value);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
if (has_fallback) {
|
|
215
|
+
binding.transform.update = (node) => {
|
|
216
|
+
const delta = node.operator === '++' ? b.literal(1) : b.literal(-1);
|
|
217
|
+
const temp = b.id('_v');
|
|
218
|
+
|
|
219
|
+
if (node.prefix) {
|
|
220
|
+
// ++count: compute new value and set it, return new value
|
|
221
|
+
return b.call(
|
|
222
|
+
b.arrow(
|
|
223
|
+
[],
|
|
224
|
+
b.block([
|
|
225
|
+
b.var(temp, b.binary('+', read_expr(), delta)),
|
|
226
|
+
b.stmt(b.call('_$_.set', source_id, temp)),
|
|
227
|
+
b.return(temp),
|
|
228
|
+
]),
|
|
229
|
+
),
|
|
230
|
+
);
|
|
231
|
+
} else {
|
|
232
|
+
// count++: read old value, set new value, return old value
|
|
233
|
+
return b.call(
|
|
234
|
+
b.arrow(
|
|
235
|
+
[],
|
|
236
|
+
b.block([
|
|
237
|
+
b.var(temp, read_expr()),
|
|
238
|
+
b.stmt(b.call('_$_.set', source_id, b.binary('+', temp, delta))),
|
|
239
|
+
b.return(temp),
|
|
240
|
+
]),
|
|
241
|
+
),
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
} else {
|
|
246
|
+
binding.transform.update = (node) => {
|
|
247
|
+
const fn_name = node.prefix ? '_$_.update_pre' : '_$_.update';
|
|
248
|
+
const args = [source_id];
|
|
249
|
+
if (node.operator === '--') {
|
|
250
|
+
args.push(b.literal(-1));
|
|
251
|
+
}
|
|
252
|
+
return b.call(fn_name, ...args);
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
// Fast path for index 1: source itself is the tracked ref
|
|
258
|
+
binding.transform = {
|
|
259
|
+
read: (_) => source_id,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
// Nested patterns or indices > 1: fall back to generic source[i] access via extract_paths
|
|
264
|
+
/** @type {(object: AST.Expression) => AST.Expression} */
|
|
265
|
+
const base_expression =
|
|
266
|
+
i === 0
|
|
267
|
+
? (object) => b.call('_$_.get', object)
|
|
268
|
+
: i === 1
|
|
269
|
+
? (object) => object
|
|
270
|
+
: (object) => b.member(object, b.literal(i), true);
|
|
271
|
+
|
|
272
|
+
const inner_paths = extract_paths(element);
|
|
273
|
+
for (const path of inner_paths) {
|
|
274
|
+
const name = /** @type {AST.Identifier} */ (path.node).name;
|
|
275
|
+
const binding = state.scope.get(name);
|
|
276
|
+
if (binding === null) continue;
|
|
277
|
+
|
|
278
|
+
binding.kind = path.has_default_value ? 'lazy_fallback' : 'lazy';
|
|
279
|
+
|
|
280
|
+
binding.transform = {
|
|
281
|
+
read: (_) => path.expression(base_expression(source_id)),
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
if (writable) {
|
|
285
|
+
binding.transform.assign = (node, value) => {
|
|
286
|
+
return b.assignment(
|
|
287
|
+
'=',
|
|
288
|
+
/** @type {AST.MemberExpression} */ (
|
|
289
|
+
path.update_expression(base_expression(source_id))
|
|
290
|
+
),
|
|
291
|
+
value,
|
|
292
|
+
);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
if (path.has_default_value) {
|
|
296
|
+
binding.transform.update = (node) => {
|
|
297
|
+
const member = path.update_expression(base_expression(source_id));
|
|
298
|
+
const fallback_read = path.expression(base_expression(source_id));
|
|
299
|
+
const delta = node.operator === '++' ? b.literal(1) : b.literal(-1);
|
|
300
|
+
|
|
301
|
+
if (node.prefix) {
|
|
302
|
+
return b.assignment('=', member, b.binary('+', fallback_read, delta));
|
|
303
|
+
} else {
|
|
304
|
+
const temp = b.id('_v');
|
|
305
|
+
return b.call(
|
|
306
|
+
b.arrow(
|
|
307
|
+
[],
|
|
308
|
+
b.block([
|
|
309
|
+
b.var(temp, fallback_read),
|
|
310
|
+
b.stmt(b.assignment('=', member, b.binary('+', temp, delta))),
|
|
311
|
+
b.return(temp),
|
|
312
|
+
]),
|
|
313
|
+
),
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
} else {
|
|
318
|
+
binding.transform.update = (node) =>
|
|
319
|
+
b.update(
|
|
320
|
+
node.operator,
|
|
321
|
+
path.update_expression(base_expression(source_id)),
|
|
322
|
+
node.prefix,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Checks if a function parameter has a Tracked<T> type annotation imported from ripple.
|
|
333
|
+
* This is used to determine if lazy array destructuring should use the track tuple fast path.
|
|
334
|
+
* @param {AST.ArrayPattern} param - The parameter pattern node
|
|
335
|
+
* @param {AnalysisContext} context - The analysis context
|
|
336
|
+
* @returns {boolean}
|
|
337
|
+
*/
|
|
338
|
+
function is_param_tracked_type(param, context) {
|
|
339
|
+
const annotation = param.typeAnnotation?.typeAnnotation;
|
|
340
|
+
|
|
341
|
+
if (
|
|
342
|
+
annotation?.type === 'TSTypeReference' &&
|
|
343
|
+
annotation.typeName?.type === 'Identifier' &&
|
|
344
|
+
annotation.typeName.name === 'Tracked'
|
|
345
|
+
) {
|
|
346
|
+
const binding = context.state.scope.get('Tracked');
|
|
347
|
+
|
|
348
|
+
return (
|
|
349
|
+
binding?.declaration_kind === 'import' &&
|
|
350
|
+
binding.initial !== null &&
|
|
351
|
+
binding.initial.type === 'ImportDeclaration' &&
|
|
352
|
+
binding.initial.source.type === 'Literal' &&
|
|
353
|
+
binding.initial.source.value === 'ripple'
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
|
|
144
360
|
/**
|
|
145
361
|
* @param {AST.Function} node
|
|
146
362
|
* @param {AnalysisContext} context
|
|
@@ -158,7 +374,11 @@ function visit_function(node, context) {
|
|
|
158
374
|
|
|
159
375
|
if ((param.type === 'ObjectPattern' || param.type === 'ArrayPattern') && param.lazy) {
|
|
160
376
|
const param_id = b.id(context.state.scope.generate('param'));
|
|
161
|
-
|
|
377
|
+
// For ArrayPattern params with a Tracked<T> type annotation from ripple,
|
|
378
|
+
// use the track tuple fast path (get/set instead of source[0]/source[1])
|
|
379
|
+
const is_tracked_type =
|
|
380
|
+
param.type === 'ArrayPattern' && is_param_tracked_type(param, context);
|
|
381
|
+
setup_lazy_transforms(param, param_id, context.state, true, is_tracked_type);
|
|
162
382
|
// Store the generated identifier name on the pattern for the transform phase
|
|
163
383
|
param.metadata = { ...param.metadata, lazy_id: param_id.name };
|
|
164
384
|
}
|
|
@@ -391,6 +611,21 @@ const visitors = {
|
|
|
391
611
|
}
|
|
392
612
|
}
|
|
393
613
|
|
|
614
|
+
// Lazy bindings from track() calls (read_unwraps) are inherently reactive —
|
|
615
|
+
// propagate tracking so that control flow (if/for/switch)
|
|
616
|
+
// and early returns create reactive blocks
|
|
617
|
+
if (
|
|
618
|
+
!node.tracked &&
|
|
619
|
+
binding?.read_unwraps &&
|
|
620
|
+
is_reference(node, /** @type {AST.Node} */ (parent)) &&
|
|
621
|
+
binding.node !== node
|
|
622
|
+
) {
|
|
623
|
+
mark_as_tracked(context.path);
|
|
624
|
+
if (context.state.metadata?.tracking === false) {
|
|
625
|
+
context.state.metadata.tracking = true;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
394
629
|
context.next();
|
|
395
630
|
},
|
|
396
631
|
|
|
@@ -471,7 +706,7 @@ const visitors = {
|
|
|
471
706
|
|
|
472
707
|
if (propertyName && internalProperties.has(propertyName)) {
|
|
473
708
|
error(
|
|
474
|
-
`Directly accessing internal property "${propertyName}" of a tracked object is not allowed. Use
|
|
709
|
+
`Directly accessing internal property "${propertyName}" of a tracked object is not allowed. Use \`${node.object.name}.value\` or \`&[]\` lazy destructuring instead.`,
|
|
475
710
|
context.state.analysis.module.filename,
|
|
476
711
|
node.property,
|
|
477
712
|
context.state.loose ? context.state.analysis.errors : undefined,
|
|
@@ -482,16 +717,32 @@ const visitors = {
|
|
|
482
717
|
|
|
483
718
|
if (
|
|
484
719
|
binding !== null &&
|
|
720
|
+
binding.kind !== 'lazy' &&
|
|
721
|
+
binding.kind !== 'lazy_fallback' &&
|
|
485
722
|
binding.initial?.type === 'CallExpression' &&
|
|
486
723
|
is_ripple_track_call(binding.initial.callee, context)
|
|
487
724
|
) {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
725
|
+
const is_allowed_tracked_access =
|
|
726
|
+
// Allow [0] and [1] indexed access on tracked objects.
|
|
727
|
+
(node.computed &&
|
|
728
|
+
node.property.type === 'Literal' &&
|
|
729
|
+
(node.property.value === 0 || node.property.value === 1)) ||
|
|
730
|
+
// Allow .value and .length property access on tracked objects.
|
|
731
|
+
(!node.computed &&
|
|
732
|
+
node.property.type === 'Identifier' &&
|
|
733
|
+
(node.property.name === 'value' || node.property.name === 'length'));
|
|
734
|
+
|
|
735
|
+
if (is_allowed_tracked_access) {
|
|
736
|
+
// pass through
|
|
737
|
+
} else {
|
|
738
|
+
error(
|
|
739
|
+
`Accessing a tracked object directly is not allowed, use \`.value\` or \`&[]\` lazy destructuring to read the value inside a tracked object - for example \`${node.object.name}.value\``,
|
|
740
|
+
context.state.analysis.module.filename,
|
|
741
|
+
node.object,
|
|
742
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
743
|
+
context.state.analysis.comments,
|
|
744
|
+
);
|
|
745
|
+
}
|
|
495
746
|
}
|
|
496
747
|
}
|
|
497
748
|
|
|
@@ -570,25 +821,14 @@ const visitors = {
|
|
|
570
821
|
) {
|
|
571
822
|
const lazy_id = b.id(state.scope.generate('lazy'));
|
|
572
823
|
const writable = node.kind !== 'const';
|
|
573
|
-
|
|
824
|
+
const init_is_track =
|
|
825
|
+
declarator.init?.type === 'CallExpression' &&
|
|
826
|
+
is_ripple_track_call(declarator.init.callee, context) === 'track';
|
|
827
|
+
setup_lazy_transforms(declarator.id, lazy_id, state, writable, !!init_is_track);
|
|
574
828
|
// Store the generated identifier name on the pattern for the transform phase
|
|
575
829
|
declarator.id.metadata = { ...declarator.id.metadata, lazy_id: lazy_id.name };
|
|
576
830
|
}
|
|
577
831
|
|
|
578
|
-
const paths = extract_paths(declarator.id);
|
|
579
|
-
|
|
580
|
-
for (const path of paths) {
|
|
581
|
-
if (path.node.tracked) {
|
|
582
|
-
error(
|
|
583
|
-
'Variables cannot be reactively referenced using @',
|
|
584
|
-
state.analysis.module.filename,
|
|
585
|
-
path.node,
|
|
586
|
-
context.state.loose ? context.state.analysis.errors : undefined,
|
|
587
|
-
context.state.analysis.comments,
|
|
588
|
-
);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
832
|
visit(declarator, state);
|
|
593
833
|
}
|
|
594
834
|
|
|
@@ -596,6 +836,30 @@ const visitors = {
|
|
|
596
836
|
}
|
|
597
837
|
},
|
|
598
838
|
|
|
839
|
+
ExpressionStatement(node, context) {
|
|
840
|
+
const { state, visit } = context;
|
|
841
|
+
|
|
842
|
+
// Handle standalone lazy destructuring assignment: &[data] = track(0);
|
|
843
|
+
if (
|
|
844
|
+
node.expression.type === 'AssignmentExpression' &&
|
|
845
|
+
node.expression.operator === '=' &&
|
|
846
|
+
(node.expression.left.type === 'ObjectPattern' ||
|
|
847
|
+
node.expression.left.type === 'ArrayPattern') &&
|
|
848
|
+
node.expression.left.lazy
|
|
849
|
+
) {
|
|
850
|
+
const pattern = /** @type {AST.ObjectPattern | AST.ArrayPattern} */ (node.expression.left);
|
|
851
|
+
const lazy_id = b.id(state.scope.generate('lazy'));
|
|
852
|
+
const init = /** @type {AST.Expression} */ (node.expression.right);
|
|
853
|
+
const init_is_track =
|
|
854
|
+
init?.type === 'CallExpression' && is_ripple_track_call(init.callee, context) === 'track';
|
|
855
|
+
setup_lazy_transforms(pattern, lazy_id, state, true, !!init_is_track);
|
|
856
|
+
// Store the generated identifier name on the pattern for the transform phase
|
|
857
|
+
pattern.metadata = { ...pattern.metadata, lazy_id: lazy_id.name };
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
context.next();
|
|
861
|
+
},
|
|
862
|
+
|
|
599
863
|
StyleIdentifier(node, context) {
|
|
600
864
|
const component = is_inside_component(context, true);
|
|
601
865
|
const parent = context.path.at(-1);
|
|
@@ -651,7 +915,7 @@ const visitors = {
|
|
|
651
915
|
|
|
652
916
|
if ((props.type === 'ObjectPattern' || props.type === 'ArrayPattern') && props.lazy) {
|
|
653
917
|
// Lazy destructuring: &{...} or &[...] — set up lazy transforms
|
|
654
|
-
setup_lazy_transforms(props, b.id('__props'), context.state, true);
|
|
918
|
+
setup_lazy_transforms(props, b.id('__props'), context.state, true, false);
|
|
655
919
|
} else if (props.type === 'AssignmentPattern') {
|
|
656
920
|
error(
|
|
657
921
|
'Props are always an object, use destructured props with default values instead',
|
|
@@ -533,9 +533,6 @@ const visitors = {
|
|
|
533
533
|
if (context.state.metadata?.tracking === false) {
|
|
534
534
|
context.state.metadata.tracking = true;
|
|
535
535
|
}
|
|
536
|
-
if (node.tracked) {
|
|
537
|
-
return b.call('_$_.get', build_getter(node, context));
|
|
538
|
-
}
|
|
539
536
|
}
|
|
540
537
|
return build_getter(node, context);
|
|
541
538
|
}
|
|
@@ -634,17 +631,9 @@ const visitors = {
|
|
|
634
631
|
}
|
|
635
632
|
}
|
|
636
633
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
? callee.name === 'trackSplit'
|
|
641
|
-
? 'track_split'
|
|
642
|
-
: 'track'
|
|
643
|
-
: callee.type === 'MemberExpression' && callee.property.type === 'Identifier'
|
|
644
|
-
? callee.property.name === 'trackSplit'
|
|
645
|
-
? 'track_split'
|
|
646
|
-
: 'track'
|
|
647
|
-
: 'track';
|
|
634
|
+
const matched_track_call = !context.state.to_ts ? is_ripple_track_call(callee, context) : null;
|
|
635
|
+
if (matched_track_call) {
|
|
636
|
+
const track_method_name = matched_track_call === 'trackSplit' ? 'track_split' : 'track';
|
|
648
637
|
|
|
649
638
|
if (callee.type === 'Identifier' && callee.name === 'track') {
|
|
650
639
|
if (node.arguments.length === 0) {
|
|
@@ -927,6 +916,25 @@ const visitors = {
|
|
|
927
916
|
return context.next();
|
|
928
917
|
},
|
|
929
918
|
|
|
919
|
+
ExpressionStatement(node, context) {
|
|
920
|
+
// Handle standalone lazy destructuring: &[data] = track(0); → const lazy0 = track(0);
|
|
921
|
+
if (
|
|
922
|
+
node.expression.type === 'AssignmentExpression' &&
|
|
923
|
+
node.expression.left.lazy &&
|
|
924
|
+
node.expression.left.metadata?.lazy_id
|
|
925
|
+
) {
|
|
926
|
+
if (context.state.to_ts) {
|
|
927
|
+
// In TypeScript mode, convert to a regular assignment (drop the pattern)
|
|
928
|
+
node.expression.left.lazy = false;
|
|
929
|
+
delete node.expression.left.metadata.lazy_id;
|
|
930
|
+
return context.next();
|
|
931
|
+
}
|
|
932
|
+
const right = /** @type {AST.Expression} */ (context.visit(node.expression.right));
|
|
933
|
+
return b.const(b.id(node.expression.left.metadata.lazy_id), right);
|
|
934
|
+
}
|
|
935
|
+
return context.next();
|
|
936
|
+
},
|
|
937
|
+
|
|
930
938
|
VariableDeclaration(node, context) {
|
|
931
939
|
for (const declarator of node.declarations) {
|
|
932
940
|
if (!context.state.to_ts) {
|
|
@@ -943,6 +951,20 @@ const visitors = {
|
|
|
943
951
|
}
|
|
944
952
|
}
|
|
945
953
|
|
|
954
|
+
if (context.state.to_ts) {
|
|
955
|
+
for (const declarator of node.declarations) {
|
|
956
|
+
if (
|
|
957
|
+
(declarator.id.type === 'ObjectPattern' || declarator.id.type === 'ArrayPattern') &&
|
|
958
|
+
declarator.id.lazy
|
|
959
|
+
) {
|
|
960
|
+
declarator.id.lazy = false;
|
|
961
|
+
if (declarator.id.metadata?.lazy_id) {
|
|
962
|
+
delete declarator.id.metadata.lazy_id;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
946
968
|
return context.next();
|
|
947
969
|
},
|
|
948
970
|
|
|
@@ -2019,6 +2041,24 @@ const visitors = {
|
|
|
2019
2041
|
|
|
2020
2042
|
const left = node.left;
|
|
2021
2043
|
|
|
2044
|
+
// Handle lazy binding assignments (e.g., value = 5 where value is from let &[value] = track(0))
|
|
2045
|
+
// Must come before the left.tracked check to use the binding's transform
|
|
2046
|
+
if (left.type === 'Identifier') {
|
|
2047
|
+
const binding = context.state.scope?.get(left.name);
|
|
2048
|
+
if (binding?.transform?.assign && binding.node !== left) {
|
|
2049
|
+
let value = /** @type {AST.Expression} */ (context.visit(node.right));
|
|
2050
|
+
|
|
2051
|
+
// For compound operators (+=, -=, *=, /=), expand to read + operation
|
|
2052
|
+
if (node.operator !== '=') {
|
|
2053
|
+
const operator = node.operator.slice(0, -1); // '+=' -> '+'
|
|
2054
|
+
const current = binding.transform.read(left);
|
|
2055
|
+
value = b.binary(/** @type {AST.BinaryOperator} */ (operator), current, value);
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
return binding.transform.assign(left, value);
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2022
2062
|
if (
|
|
2023
2063
|
left.type === 'MemberExpression' &&
|
|
2024
2064
|
(left.tracked || (left.property.type === 'Identifier' && left.property.tracked))
|
|
@@ -333,29 +333,7 @@ const visitors = {
|
|
|
333
333
|
binding.node !== node &&
|
|
334
334
|
(binding.kind === 'lazy' || binding.kind === 'lazy_fallback')
|
|
335
335
|
) {
|
|
336
|
-
|
|
337
|
-
if (node.tracked) {
|
|
338
|
-
const is_right_side_of_assignment =
|
|
339
|
-
parent.type === 'AssignmentExpression' && parent.right === node;
|
|
340
|
-
if (
|
|
341
|
-
(parent.type !== 'AssignmentExpression' && parent.type !== 'UpdateExpression') ||
|
|
342
|
-
is_right_side_of_assignment
|
|
343
|
-
) {
|
|
344
|
-
return b.call('_$_.get', transformed);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
return transformed;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (node.tracked) {
|
|
351
|
-
const is_right_side_of_assignment =
|
|
352
|
-
parent.type === 'AssignmentExpression' && parent.right === node;
|
|
353
|
-
if (
|
|
354
|
-
(parent.type !== 'AssignmentExpression' && parent.type !== 'UpdateExpression') ||
|
|
355
|
-
is_right_side_of_assignment
|
|
356
|
-
) {
|
|
357
|
-
return b.call('_$_.get', node);
|
|
358
|
-
}
|
|
336
|
+
return binding.transform.read(node);
|
|
359
337
|
}
|
|
360
338
|
|
|
361
339
|
return node;
|
|
@@ -520,17 +498,9 @@ const visitors = {
|
|
|
520
498
|
}
|
|
521
499
|
}
|
|
522
500
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
? callee.name === 'trackSplit'
|
|
527
|
-
? 'track_split'
|
|
528
|
-
: 'track'
|
|
529
|
-
: callee.type === 'MemberExpression' && callee.property.type === 'Identifier'
|
|
530
|
-
? callee.property.name === 'trackSplit'
|
|
531
|
-
? 'track_split'
|
|
532
|
-
: 'track'
|
|
533
|
-
: 'track';
|
|
501
|
+
const track_call_name = is_ripple_track_call(callee, context);
|
|
502
|
+
if (track_call_name) {
|
|
503
|
+
const track_method_name = track_call_name === 'trackSplit' ? 'track_split' : 'track';
|
|
534
504
|
|
|
535
505
|
return {
|
|
536
506
|
...node,
|
|
@@ -843,6 +813,19 @@ const visitors = {
|
|
|
843
813
|
return statements.length ? b.block(statements) : b.empty;
|
|
844
814
|
},
|
|
845
815
|
|
|
816
|
+
ExpressionStatement(node, context) {
|
|
817
|
+
// Handle standalone lazy destructuring: &[data] = track(0); → const lazy0 = track(0);
|
|
818
|
+
if (
|
|
819
|
+
node.expression.type === 'AssignmentExpression' &&
|
|
820
|
+
node.expression.left.lazy &&
|
|
821
|
+
node.expression.left.metadata?.lazy_id
|
|
822
|
+
) {
|
|
823
|
+
const right = /** @type {AST.Expression} */ (context.visit(node.expression.right));
|
|
824
|
+
return b.const(b.id(node.expression.left.metadata.lazy_id), right);
|
|
825
|
+
}
|
|
826
|
+
return context.next();
|
|
827
|
+
},
|
|
828
|
+
|
|
846
829
|
VariableDeclaration(node, context) {
|
|
847
830
|
for (const declarator of node.declarations) {
|
|
848
831
|
if (!context.state.to_ts) {
|
|
@@ -1210,9 +1193,10 @@ const visitors = {
|
|
|
1210
1193
|
|
|
1211
1194
|
/** @type {AST.Statement[]} */
|
|
1212
1195
|
const init = [];
|
|
1196
|
+
const visited_id = /** @type {AST.Expression} */ (visit(node.id, state));
|
|
1213
1197
|
/** @type {AST.Statement[]} */
|
|
1214
1198
|
const statements = [
|
|
1215
|
-
b.const(comp_id,
|
|
1199
|
+
b.const(comp_id, is_element_dynamic(node) ? b.call('_$_.get', visited_id) : visited_id),
|
|
1216
1200
|
b.const(args_id, b.array(args)),
|
|
1217
1201
|
];
|
|
1218
1202
|
|
|
@@ -1157,9 +1157,11 @@ export interface Binding {
|
|
|
1157
1157
|
/** Transform functions for reading, assigning, and updating this binding */
|
|
1158
1158
|
transform?: {
|
|
1159
1159
|
read: (node?: AST.Identifier) => AST.Expression;
|
|
1160
|
-
assign?: (node: AST.
|
|
1160
|
+
assign?: (node: AST.Identifier, value: AST.Expression) => AST.Expression;
|
|
1161
1161
|
update?: (node: AST.UpdateExpression) => AST.Expression;
|
|
1162
1162
|
};
|
|
1163
|
+
/** Whether the read transform already produces an unwrapped value (calls get() internally) */
|
|
1164
|
+
read_unwraps?: boolean;
|
|
1163
1165
|
}
|
|
1164
1166
|
|
|
1165
1167
|
/**
|
|
@@ -543,8 +543,6 @@ export namespace Parse {
|
|
|
543
543
|
*/
|
|
544
544
|
finishToken(type: TokenType, val?: string | number | RegExp | bigint): void;
|
|
545
545
|
|
|
546
|
-
readAtIdentifier(): void;
|
|
547
|
-
|
|
548
546
|
/**
|
|
549
547
|
* Read a token based on character code
|
|
550
548
|
* Called by nextToken() for each character
|
|
@@ -930,8 +928,6 @@ export namespace Parse {
|
|
|
930
928
|
| AST.ServerIdentifier
|
|
931
929
|
| AST.StyleIdentifier
|
|
932
930
|
| AST.TrackedExpression
|
|
933
|
-
| AST.RippleArrayExpression
|
|
934
|
-
| AST.RippleObjectExpression
|
|
935
931
|
| AST.Component
|
|
936
932
|
| AST.Identifier
|
|
937
933
|
| AST.Literal;
|
|
@@ -954,12 +950,8 @@ export namespace Parse {
|
|
|
954
950
|
/** Parse parenthesized expression (just the expression) */
|
|
955
951
|
parseParenExpression(): AST.Expression;
|
|
956
952
|
|
|
957
|
-
parseRippleArrayExpression(): AST.RippleArrayExpression;
|
|
958
|
-
|
|
959
953
|
parseTrackedExpression(): AST.TrackedExpression;
|
|
960
954
|
|
|
961
|
-
parseRippleObjectExpression(): AST.RippleObjectExpression;
|
|
962
|
-
|
|
963
955
|
/**
|
|
964
956
|
* Parse item in parentheses (can be overridden for flow/ts)
|
|
965
957
|
*/
|
package/src/compiler/utils.js
CHANGED
|
@@ -289,27 +289,31 @@ export function is_component_level_function(context) {
|
|
|
289
289
|
}
|
|
290
290
|
|
|
291
291
|
/**
|
|
292
|
-
* Returns
|
|
292
|
+
* Returns the matched Ripple tracking call name
|
|
293
293
|
* @param {AST.Expression | AST.Super} callee
|
|
294
294
|
* @param {CommonContext} context
|
|
295
|
-
* @returns {
|
|
295
|
+
* @returns {'track' | 'trackSplit' | null}
|
|
296
296
|
*/
|
|
297
297
|
export function is_ripple_track_call(callee, context) {
|
|
298
298
|
// Super expressions cannot be Ripple track calls
|
|
299
|
-
if (callee.type === 'Super') return
|
|
299
|
+
if (callee.type === 'Super') return null;
|
|
300
300
|
|
|
301
301
|
if (callee.type === 'Identifier' && (callee.name === 'track' || callee.name === 'trackSplit')) {
|
|
302
|
-
return is_ripple_import(callee, context);
|
|
302
|
+
return is_ripple_import(callee, context) ? callee.name : null;
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
|
|
305
|
+
if (
|
|
306
306
|
callee.type === 'MemberExpression' &&
|
|
307
307
|
callee.object.type === 'Identifier' &&
|
|
308
308
|
callee.property.type === 'Identifier' &&
|
|
309
309
|
(callee.property.name === 'track' || callee.property.name === 'trackSplit') &&
|
|
310
310
|
!callee.computed &&
|
|
311
311
|
is_ripple_import(callee, context)
|
|
312
|
-
)
|
|
312
|
+
) {
|
|
313
|
+
return callee.property.name;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return null;
|
|
313
317
|
}
|
|
314
318
|
|
|
315
319
|
/**
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { branch, destroy_block, render, render_spread } from './blocks.js';
|
|
4
4
|
import { COMPOSITE_BLOCK, DEFAULT_NAMESPACE, NAMESPACE_URI } from './constants.js';
|
|
5
5
|
import { hydrate_next, hydrating } from './hydration.js';
|
|
6
|
-
import { active_block, active_namespace, with_ns } from './runtime.js';
|
|
6
|
+
import { active_block, active_namespace, get, with_ns } from './runtime.js';
|
|
7
7
|
import { top_element_to_ns } from './utils.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -29,7 +29,7 @@ export function composite(get_component, node, props) {
|
|
|
29
29
|
|
|
30
30
|
render(
|
|
31
31
|
() => {
|
|
32
|
-
var component = get_component();
|
|
32
|
+
var component = get(get_component());
|
|
33
33
|
|
|
34
34
|
if (b !== null) {
|
|
35
35
|
destroy_block(b);
|
|
@@ -80,6 +80,8 @@ export { switch_block as switch } from './switch.js';
|
|
|
80
80
|
|
|
81
81
|
export { template, append, text } from './template.js';
|
|
82
82
|
|
|
83
|
+
export { array_slice } from './utils.js';
|
|
84
|
+
|
|
83
85
|
export { ripple_array } from '../../array.js';
|
|
84
86
|
|
|
85
87
|
export { ripple_object } from '../../object.js';
|