ripple 0.2.36 → 0.2.38

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Ripple is a TypeScript UI framework for the web",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.2.36",
6
+ "version": "0.2.38",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index.js",
9
9
  "main": "src/runtime/index.js",
@@ -15,6 +15,29 @@ import is_reference from 'is-reference';
15
15
  import { prune_css } from './prune.js';
16
16
  import { error } from '../../errors.js';
17
17
 
18
+ function mark_for_loop_has_template(path) {
19
+ for (let i = path.length - 1; i >= 0; i -= 1) {
20
+ const node = path[i];
21
+
22
+ if (
23
+ node.type === 'Component' ||
24
+ node.type === 'FunctionExpression' ||
25
+ node.type === 'ArrowFunctionExpression' ||
26
+ node.type === 'FunctionDeclaration'
27
+ ) {
28
+ break;
29
+ }
30
+ if (
31
+ node.type === 'ForStatement' ||
32
+ node.type === 'ForInStatement' ||
33
+ node.type === 'ForOfStatement'
34
+ ) {
35
+ node.metadata.has_template = true;
36
+ break;
37
+ }
38
+ }
39
+ }
40
+
18
41
  function visit_function(node, context) {
19
42
  node.metadata = {
20
43
  hoisted: false,
@@ -407,6 +430,20 @@ const visitors = {
407
430
  context.next();
408
431
  },
409
432
 
433
+ ForOfStatement(node, context) {
434
+ node.metadata = {
435
+ has_template: false,
436
+ };
437
+ context.next();
438
+ if (!node.metadata.has_template) {
439
+ error(
440
+ 'For...of loops must contain a template in their body. Move the for loop into an effect if it does not render anything.',
441
+ context.state.analysis.module.filename,
442
+ node,
443
+ );
444
+ }
445
+ },
446
+
410
447
  ForInStatement(node, context) {
411
448
  if (is_inside_component(context)) {
412
449
  error(
@@ -429,13 +466,15 @@ const visitors = {
429
466
  }
430
467
  },
431
468
 
432
- Element(node, { state, visit }) {
469
+ Element(node, { state, visit, path }) {
433
470
  const is_dom_element =
434
471
  node.id.type === 'Identifier' &&
435
472
  node.id.name[0].toLowerCase() === node.id.name[0] &&
436
473
  node.id.name[0] !== '$';
437
474
  const attribute_names = new Set();
438
475
 
476
+ mark_for_loop_has_template(path);
477
+
439
478
  if (is_dom_element) {
440
479
  const is_void = is_void_element(node.id.name);
441
480
 
@@ -567,6 +606,11 @@ const visitors = {
567
606
  };
568
607
  },
569
608
 
609
+ Text(node, context) {
610
+ mark_for_loop_has_template(context.path);
611
+ context.next();
612
+ },
613
+
570
614
  AwaitExpression(node, context) {
571
615
  if (is_inside_component(context)) {
572
616
  if (context.state.metadata?.await === false) {
@@ -328,34 +328,52 @@ const visitors = {
328
328
  delete declarator.id.typeAnnotation;
329
329
  }
330
330
 
331
- if (binding !== null && binding.kind === 'tracked' && !context.state.to_ts) {
331
+ if (binding !== null && binding.kind === 'tracked') {
332
332
  let expression;
333
333
 
334
- if (metadata.tracking && metadata.await) {
335
- expression = b.call(
336
- b.await(
337
- b.call(
338
- '$.resume_context',
334
+ if (context.state.to_ts) {
335
+ // TypeScript mode: lighter transformation
336
+ if (metadata.tracking && !metadata.await) {
337
+ expression = b.call(
338
+ '$.computed',
339
+ b.thunk(context.visit(declarator.init)),
340
+ b.id('__block'),
341
+ );
342
+ } else {
343
+ expression = b.call(
344
+ '$.tracked',
345
+ declarator.init === null ? undefined : context.visit(declarator.init),
346
+ b.id('__block'),
347
+ );
348
+ }
349
+ } else {
350
+ // Runtime mode: full transformation
351
+ if (metadata.tracking && metadata.await) {
352
+ expression = b.call(
353
+ b.await(
339
354
  b.call(
340
- '$.async_computed',
341
- b.thunk(context.visit(declarator.init), true),
342
- b.id('__block'),
355
+ '$.resume_context',
356
+ b.call(
357
+ '$.async_computed',
358
+ b.thunk(context.visit(declarator.init), true),
359
+ b.id('__block'),
360
+ ),
343
361
  ),
344
362
  ),
345
- ),
346
- );
347
- } else if (metadata.tracking && !metadata.await) {
348
- expression = b.call(
349
- '$.computed',
350
- b.thunk(context.visit(declarator.init)),
351
- b.id('__block'),
352
- );
353
- } else {
354
- expression = b.call(
355
- '$.tracked',
356
- declarator.init === null ? undefined : context.visit(declarator.init),
357
- b.id('__block'),
358
- );
363
+ );
364
+ } else if (metadata.tracking && !metadata.await) {
365
+ expression = b.call(
366
+ '$.computed',
367
+ b.thunk(context.visit(declarator.init)),
368
+ b.id('__block'),
369
+ );
370
+ } else {
371
+ expression = b.call(
372
+ '$.tracked',
373
+ declarator.init === null ? undefined : context.visit(declarator.init),
374
+ b.id('__block'),
375
+ );
376
+ }
359
377
  }
360
378
 
361
379
  declarations.push(b.declarator(declarator.id, expression));
@@ -372,11 +390,32 @@ const visitors = {
372
390
  delete declarator.id.typeAnnotation;
373
391
  }
374
392
 
375
- if (!has_tracked || context.state.to_ts) {
393
+ if (!has_tracked) {
376
394
  declarations.push(context.visit(declarator));
377
395
  continue;
378
396
  }
379
397
 
398
+ // For TypeScript mode, we still need to transform tracked variables
399
+ // but use a lighter approach that maintains type information
400
+ if (context.state.to_ts) {
401
+ const transformed = declarator.transformed || declarator.id;
402
+ let expression;
403
+
404
+ if (metadata.tracking && !metadata.await) {
405
+ expression = b.call(
406
+ '$.computed',
407
+ b.thunk(context.visit(declarator.init)),
408
+ b.id('__block'),
409
+ );
410
+ } else {
411
+ // Simple tracked variable - always use $.tracked for $ prefixed variables
412
+ expression = b.call('$.tracked', context.visit(declarator.init), b.id('__block'));
413
+ }
414
+
415
+ declarations.push(b.declarator(transformed, expression));
416
+ continue;
417
+ }
418
+
380
419
  const transformed = declarator.transformed;
381
420
  let expression;
382
421
 
package/types/index.d.ts CHANGED
@@ -55,3 +55,27 @@ export class RippleMap<K, V> extends Map<K, V> {
55
55
  get $size(): number;
56
56
  toJSON(): [K, V][];
57
57
  }
58
+
59
+ // Compiler-injected runtime symbols (for Ripple component development)
60
+ declare global {
61
+ /**
62
+ * Runtime block context injected by the Ripple compiler.
63
+ * This is automatically available in component scopes and passed to runtime functions.
64
+ */
65
+ var __block: any;
66
+
67
+ /**
68
+ * Ripple runtime namespace - injected by the compiler
69
+ * These functions are available in compiled Ripple components for TypeScript analysis
70
+ */
71
+ var $: {
72
+ tracked<T>(value: T, block?: any): T;
73
+ tracked_object<T extends Record<string, any>>(obj: T, props: string[], block?: any): T;
74
+ computed<T>(fn: () => T, block?: any): T;
75
+ scope(): any;
76
+ get_tracked(node: any): any;
77
+ get_computed(node: any): any;
78
+ set(node: any, value: any, block?: any): any;
79
+ // Add other runtime functions as needed for TypeScript analysis
80
+ };
81
+ }