ripple 0.2.0 → 0.2.2

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.0",
6
+ "version": "0.2.2",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index.js",
9
9
  "main": "src/runtime/index.js",
@@ -48,7 +48,6 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@ai-sdk/anthropic": "^2.0.5",
52
51
  "@jridgewell/sourcemap-codec": "^1.5.5",
53
52
  "@types/estree": "^1.0.8",
54
53
  "acorn": "^8.15.0",
@@ -4,6 +4,17 @@ import { parse_style } from './style.js';
4
4
 
5
5
  const parser = acorn.Parser.extend(tsPlugin({ allowSatisfies: true }), RipplePlugin());
6
6
 
7
+ function convert_from_jsx(node) {
8
+ if (node.type === 'JSXIdentifier') {
9
+ node.type = 'Identifier';
10
+ } else if (node.type === 'JSXMemberExpression') {
11
+ node.type = 'MemberExpression';
12
+ node.object = convert_from_jsx(node.object)
13
+ node.property = convert_from_jsx(node.property)
14
+ }
15
+ return node;
16
+ }
17
+
7
18
  function RipplePlugin(config) {
8
19
  return (Parser) => {
9
20
  const original = acorn.Parser.prototype;
@@ -17,7 +28,7 @@ function RipplePlugin(config) {
17
28
  if (super.shouldParseExportStatement()) {
18
29
  return true;
19
30
  }
20
- if (this.value === 'component' || this.value === 'fragment') {
31
+ if (this.value === 'component') {
21
32
  return true;
22
33
  }
23
34
  return this.type.keyword === 'var';
@@ -28,17 +39,6 @@ function RipplePlugin(config) {
28
39
  let node = this.startNode();
29
40
  this.next();
30
41
 
31
- if (this.type === tok.at) {
32
- this.next();
33
-
34
- if (this.value === 'fragment') {
35
- node.decorator = 'fragment';
36
- this.next();
37
- } else {
38
- throw new Error(`Invalid syntax @` + this.value);
39
- }
40
- }
41
-
42
42
  node.expression =
43
43
  this.type === tt.braceR ? this.jsx_parseEmptyExpression() : this.parseExpression();
44
44
  this.expect(tt.braceR);
@@ -59,7 +59,12 @@ function RipplePlugin(config) {
59
59
  jsx_parseAttribute() {
60
60
  let node = this.startNode();
61
61
  if (this.eat(tt.braceL)) {
62
- if (this.lookahead().type === tt.ellipsis) {
62
+ if (this.type === tt.ellipsis) {
63
+ this.expect(tt.ellipsis);
64
+ node.argument = this.parseMaybeAssign();
65
+ this.expect(tt.braceR);
66
+ return this.finishNode(node, 'SpreadAttribute');
67
+ } else if (this.lookahead().type === tt.ellipsis) {
63
68
  this.expect(tt.ellipsis);
64
69
  node.argument = this.parseMaybeAssign();
65
70
  this.expect(tt.braceR);
@@ -250,7 +255,7 @@ function RipplePlugin(config) {
250
255
  // '}'
251
256
  if (
252
257
  ch === 125 &&
253
- (this.#path.at(-1).type === 'Component' || this.#path.at(-1).type === 'Fragment')
258
+ (this.#path.at(-1).type === 'Component')
254
259
  ) {
255
260
  return original.readToken.call(this, ch);
256
261
  }
@@ -313,8 +318,8 @@ function RipplePlugin(config) {
313
318
  if (open.name.type === 'JSXIdentifier') {
314
319
  open.name.type = 'Identifier';
315
320
  }
316
- element.id = open.name;
317
- element.id.type = 'Identifier';
321
+
322
+ element.id = convert_from_jsx(open.name);
318
323
  element.attributes = open.attributes;
319
324
  element.selfClosing = open.selfClosing;
320
325
  element.metadata = {};
@@ -377,10 +382,7 @@ function RipplePlugin(config) {
377
382
 
378
383
  if (this.type.label === '{') {
379
384
  const node = this.jsx_parseExpressionContainer();
380
- node.type = node.decorator === 'fragment' ? 'RenderFragment' : 'Text';
381
- if (node.decorator === 'fragment' && node.expression.type !== 'CallExpression') {
382
- throw new Error('{@fragment} must be a function call');
383
- }
385
+ node.type = 'Text';
384
386
  body.push(node);
385
387
  } else if (this.type.label === '}') {
386
388
  return;
@@ -448,29 +450,6 @@ function RipplePlugin(config) {
448
450
  return node;
449
451
  }
450
452
 
451
- if (this.value === 'fragment') {
452
- const node = this.startNode();
453
- node.type = 'Fragment';
454
- this.next();
455
- this.enterScope(0);
456
- node.id = this.parseIdent();
457
- this.parseFunctionParams(node);
458
- this.eat(tt.braceL);
459
- node.body = [];
460
- this.#path.push(node);
461
-
462
- this.parseTemplateBody(node.body);
463
-
464
- this.#path.pop();
465
- this.exitScope();
466
-
467
- this.finishNode(node, 'Fragment');
468
- this.next();
469
- this.awaitPos = 0;
470
-
471
- return node;
472
- }
473
-
474
453
  return super.parseStatement(context, topLevel, exports);
475
454
  }
476
455
 
@@ -479,7 +458,6 @@ function RipplePlugin(config) {
479
458
 
480
459
  if (
481
460
  parent?.type === 'Component' ||
482
- parent?.type === 'Fragment' ||
483
461
  parent?.type === 'Element'
484
462
  ) {
485
463
  if (createNewLexicalScope === void 0) createNewLexicalScope = true;
@@ -348,8 +348,10 @@ const visitors = {
348
348
  },
349
349
 
350
350
  Element(node, { state, visit }) {
351
- const type = node.id.name;
352
- const is_dom_element = type[0].toLowerCase() === type[0];
351
+ const is_dom_element =
352
+ node.id.type === 'Identifier' &&
353
+ node.id.name[0].toLowerCase() === node.id.name[0] &&
354
+ node.id.name[0] !== '$';
353
355
  const attribute_names = new Set();
354
356
 
355
357
  if (is_dom_element) {
@@ -396,7 +398,7 @@ const visitors = {
396
398
  let explicit_children = false;
397
399
 
398
400
  for (const child of node.children) {
399
- if (child.type === 'Fragment') {
401
+ if (child.type === 'Component') {
400
402
  if (child.id.name === '$children') {
401
403
  explicit_children = true;
402
404
  if (implicit_children) {
@@ -328,25 +328,35 @@ const visitors = {
328
328
  Element(node, context) {
329
329
  const { state, visit } = context;
330
330
 
331
- const type = node.id.name;
332
- const is_dom_element = type[0].toLowerCase() === type[0];
331
+ const is_dom_element =
332
+ node.id.type === 'Identifier' &&
333
+ node.id.name[0].toLowerCase() === node.id.name[0] &&
334
+ node.id.name[0] !== '$';
335
+ const is_spreading = node.attributes.some((attr) => attr.type === 'SpreadAttribute');
336
+ const spread_attributes = is_spreading ? [] : null;
333
337
 
334
338
  const handle_static_attr = (name, value) => {
335
- state.template.push(
336
- b.literal(
337
- ` ${name}${
338
- is_boolean_attribute(name) && value === true
339
- ? ''
340
- : `="${value === true ? '' : escape_html(value, true)}"`
341
- }`
342
- )
339
+ const attr_value = b.literal(
340
+ ` ${name}${
341
+ is_boolean_attribute(name) && value === true
342
+ ? ''
343
+ : `="${value === true ? '' : escape_html(value, true)}"`
344
+ }`
343
345
  );
346
+
347
+ if (is_spreading) {
348
+ if (spread_attributes.length === 0) {
349
+ state.template.push(attr_value);
350
+ } else {
351
+ spread_attributes.push(b.prop('init', b.literal(name), attr_value));
352
+ }
353
+ }
344
354
  };
345
355
 
346
356
  if (is_dom_element) {
347
357
  let class_attribute = null;
348
358
 
349
- state.template.push(`<${type}`);
359
+ state.template.push(`<${node.id.name}`);
350
360
 
351
361
  for (const attr of node.attributes) {
352
362
  if (attr.type === 'Attribute') {
@@ -493,6 +503,8 @@ const visitors = {
493
503
  }
494
504
  }
495
505
  }
506
+ } else if (attr.type === 'SpreadAttribute') {
507
+ spread_attributes.push(b.spread(b.call('$.spread_object', visit(attr.argument, state))));
496
508
  }
497
509
  }
498
510
 
@@ -527,13 +539,17 @@ const visitors = {
527
539
 
528
540
  state.template.push('>');
529
541
 
542
+ if (spread_attributes !== null && spread_attributes.length > 0) {
543
+ const id = state.flush_node();
544
+ state.init.push(
545
+ b.stmt(b.call('$.render_spread', id, b.thunk(b.object(spread_attributes))))
546
+ );
547
+ }
548
+
530
549
  transform_children(node.children, { visit, state, root: false });
531
550
 
532
- state.template.push(`</${type}>`);
551
+ state.template.push(`</${node.id.name}>`);
533
552
  } else {
534
- if (node.id.type !== 'Identifier') {
535
- throw new Error('TODO');
536
- }
537
553
  const id = state.flush_node();
538
554
 
539
555
  state.template.push('<!>');
@@ -571,7 +587,7 @@ const visitors = {
571
587
  if (node.children.length > 0) {
572
588
  const component_scope = context.state.scopes.get(node);
573
589
  const children = b.arrow(
574
- [b.id('__anchor')],
590
+ [b.id('__anchor'), b.id('__props'), b.id('__block')],
575
591
  b.block(
576
592
  transform_body(node.children, {
577
593
  ...context,
@@ -598,7 +614,9 @@ const visitors = {
598
614
  )
599
615
  );
600
616
  } else {
601
- state.init.push(b.stmt(b.call(node.id, id, b.object(props), b.id('$.active_block'))));
617
+ state.init.push(
618
+ b.stmt(b.call(visit(node.id, state), id, b.object(props), b.id('$.active_block')))
619
+ );
602
620
  }
603
621
  }
604
622
  },
@@ -1007,69 +1025,10 @@ const visitors = {
1007
1025
  return b.call(b.await(b.call('$.resume_context', context.visit(node.argument))));
1008
1026
  },
1009
1027
 
1010
- JSXText(node) {
1011
- const text = node.value;
1012
- if (text.trim() === '') {
1013
- return b.empty;
1014
- }
1015
- return b.literal(text);
1016
- },
1017
-
1018
- JSXExpressionContainer(node, context) {
1019
- const expression = context.visit(node.expression);
1020
- if (expression.type === b.empty) {
1021
- return b.empty;
1022
- }
1023
- return expression;
1024
- },
1025
-
1026
- JSXIdentifier(node, context) {
1027
- return context.visit(b.id(node.name));
1028
- },
1029
-
1030
- JSXMemberExpression(node, context) {
1031
- return b.member(context.visit(node.object), context.visit(node.property));
1032
- },
1033
-
1034
1028
  BinaryExpression(node, context) {
1035
1029
  return b.binary(node.operator, context.visit(node.left), context.visit(node.right));
1036
1030
  },
1037
1031
 
1038
- JSXElement(node, context) {
1039
- if (
1040
- !context.state.imports.has(`import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'`)
1041
- ) {
1042
- context.state.imports.add(`import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'`);
1043
- }
1044
-
1045
- const openingElement = node.openingElement;
1046
- const name = openingElement.name;
1047
- const props = b.object([]);
1048
- const children = node.children
1049
- .map((child) => context.visit(child, context.state))
1050
- .filter((child) => child !== b.empty);
1051
-
1052
- if (children.length > 0) {
1053
- props.properties.push(b.prop('init', b.id('children'), b.array(children)));
1054
- }
1055
-
1056
- for (const attr of openingElement.attributes) {
1057
- if (attr.type === 'JSXAttribute') {
1058
- props.properties.push(
1059
- b.prop('init', b.id(attr.name.name), context.visit(attr.value, context.state))
1060
- );
1061
- }
1062
- }
1063
-
1064
- return b.call(
1065
- children.length > 0 ? '_jsxs' : '_jsx',
1066
- name.type === 'JSXIdentifier' && name.name[0] === name.name[0].toLowerCase()
1067
- ? b.literal(name.name)
1068
- : context.visit(name),
1069
- props
1070
- );
1071
- },
1072
-
1073
1032
  RenderFragment(node, context) {
1074
1033
  const identifer = node.expression.callee;
1075
1034
 
@@ -1231,7 +1190,7 @@ function transform_ts_child(node, context) {
1231
1190
  });
1232
1191
 
1233
1192
  if (!node.selfClosing && !has_children_props && node.children.length > 0) {
1234
- const is_dom_element = type[0].toLowerCase() === type[0];
1193
+ const is_dom_element = type[0].toLowerCase() === type[0] && type[0] !== '$';
1235
1194
 
1236
1195
  const component_scope = context.state.scopes.get(node);
1237
1196
  const thunk = b.thunk(
@@ -1246,7 +1205,7 @@ function transform_ts_child(node, context) {
1246
1205
  if (is_dom_element) {
1247
1206
  children.push(b.jsx_expression_container(b.call(thunk)));
1248
1207
  } else {
1249
- const children_name = context.state.scope.generate('fragment');
1208
+ const children_name = context.state.scope.generate('component');
1250
1209
  const children_id = b.id(children_name);
1251
1210
  const jsx_id = b.jsx_id('$children');
1252
1211
  jsx_id.loc = node.id.loc;
@@ -1343,7 +1302,10 @@ function transform_children(children, { visit, state, root }) {
1343
1302
  node.type === 'TryStatement' ||
1344
1303
  node.type === 'ForOfStatement' ||
1345
1304
  node.type === 'RenderFragment' ||
1346
- (node.type === 'Element' && node.id.name[0].toLowerCase() !== node.id.name[0])
1305
+ (node.type === 'Element' &&
1306
+ (node.id.type !== 'Identifier' ||
1307
+ node.id.name[0].toLowerCase() !== node.id.name[0] ||
1308
+ node.id.name[0] === '$'))
1347
1309
  ) ||
1348
1310
  normalized.filter(
1349
1311
  (node) => node.type !== 'VariableDeclaration' && node.type !== 'EmptyStatement'
@@ -1354,7 +1316,10 @@ function transform_children(children, { visit, state, root }) {
1354
1316
 
1355
1317
  const get_id = (node) => {
1356
1318
  return b.id(
1357
- node.type == 'Element' && node.id.name[0].toLowerCase() === node.id.name[0]
1319
+ node.type == 'Element' &&
1320
+ node.id.type === 'Identifier' &&
1321
+ node.id.name[0].toLowerCase() === node.id.name[0] &&
1322
+ node.id.name[0] !== '$'
1358
1323
  ? state.scope.generate(node.id.name)
1359
1324
  : node.type == 'Text'
1360
1325
  ? state.scope.generate('text')
@@ -70,22 +70,10 @@ export function create_scopes(ast, root, parent) {
70
70
  next({ scope });
71
71
  },
72
72
 
73
- Fragment(node, { state, next }) {
74
- const scope = state.scope.child();
75
- scopes.set(node, scope);
76
-
77
- if (node.id) scope.declare(node.id, 'normal', 'fragment');
78
-
79
- add_params(scope, node.params);
80
- next({ scope });
81
- },
82
-
83
73
  Element(node, { state, next }) {
84
74
  const scope = state.scope.child();
85
75
  scopes.set(node, scope);
86
76
 
87
- scope.declare(node, 'normal', 'element');
88
-
89
77
  next({ scope });
90
78
  },
91
79
 
@@ -305,7 +293,6 @@ export class Scope {
305
293
  kind,
306
294
  declaration_kind,
307
295
  is_called: false,
308
- prop_alias: null,
309
296
  metadata: null
310
297
  };
311
298
 
@@ -11,6 +11,7 @@ import {
11
11
  TRY_BLOCK
12
12
  } from './constants';
13
13
  import { next_sibling } from './operations';
14
+ import { apply_element_spread } from './render';
14
15
  import {
15
16
  active_block,
16
17
  active_component,
@@ -49,6 +50,10 @@ export function render(fn, flags = 0) {
49
50
  return block(RENDER_BLOCK | flags, fn);
50
51
  }
51
52
 
53
+ export function render_spread(element, fn, flags = 0) {
54
+ return block(RENDER_BLOCK | flags, apply_element_spread(element, fn));
55
+ }
56
+
52
57
  export function branch(fn, flags = 0) {
53
58
  return block(BRANCH_BLOCK | flags, fn);
54
59
  }
@@ -10,7 +10,7 @@ export {
10
10
  set_ref
11
11
  } from './render.js';
12
12
 
13
- export { render, async } from './blocks.js';
13
+ export { render, render_spread, async } from './blocks.js';
14
14
 
15
15
  export { event, delegate } from './events.js';
16
16
 
@@ -60,6 +60,18 @@ export function set_attribute(element, attribute, value) {
60
60
  }
61
61
  }
62
62
 
63
+ export function set_attributes(element, attributes) {
64
+ for (const key in attributes) {
65
+ let value = attributes[key];
66
+
67
+ if (key === 'class') {
68
+ set_class(element, value);
69
+ } else {
70
+ set_attribute(element, key, value);
71
+ }
72
+ }
73
+ }
74
+
63
75
  /**
64
76
  * @template V
65
77
  * @param {V} value
@@ -154,3 +166,9 @@ export function set_ref(dom, fn) {
154
166
  };
155
167
  });
156
168
  }
169
+
170
+ export function apply_element_spread(element, fn) {
171
+ return () => {
172
+ set_attributes(element, fn());
173
+ };
174
+ }
package/types/index.d.ts CHANGED
@@ -1,2 +1,12 @@
1
+ export type Component<T> = (props: T) => void;
1
2
 
2
- export type Fragment<T extends any[] = []> = (...args: T) => void;
3
+ export declare function mount(
4
+ component: () => void,
5
+ options: { target: HTMLElement; props?: Record<string, any> }
6
+ ): () => void;
7
+
8
+ export declare function untrack<T>(fn: () => T): T;
9
+
10
+ export declare function flushSync<T>(fn: () => T): T;
11
+
12
+ export declare function effect(fn: (() => void) | (() => () => void)): void;
package/src/ai.js DELETED
@@ -1,292 +0,0 @@
1
- // import { anthropic } from '@ai-sdk/anthropic';
2
- // import { generateText } from 'ai';
3
-
4
- // const default_prompt = `
5
- // Ripple is a web-based JavaScript framework for building user interfaces. It's syntax and design is inspired by React and Svelte 5.
6
- // It uses JSX for templating inside '.ripple' modules. These modules allow for custom syntax that is not JavaScript compliant.
7
-
8
- // One of the core differences is that it allows for a new type of JavaScript declaration which is a 'component', which is like a 'function' but is only allowed in '.ripple' modules:
9
-
10
- // \`\`\`js
11
- // component HelloComponent(props) {
12
- // const title = 'Hello ';
13
-
14
- // <div>{title + props.name}</div>;
15
- // }
16
- // \`\`\`
17
-
18
- // Another difference is that 'component' declaration bodies allow for JSX templating. Except this JSX templating isn't "expression" based, but rather "statement" based. That
19
- // means that 'return' is not valid syntax in a component declaration body. Nor is creating a variable that references JSX. Instead, JSX is directly written in the body of the component declaration.
20
- // This means that the ordering is important, as JSX that is written first will be rendered first. This is different from React.
21
-
22
- // Another difference from JSX in other frameworks is that JSXText is not allowed here. That's because JSX is now statement based, and not expression based. This means that all JSX must be wrapped in a JSXExpressionContainer.
23
-
24
- // For example, this is invalid Ripple code:
25
-
26
- // \`\`\`js
27
- // <button>=</button>
28
- // \`\`\`
29
-
30
- // The correct version is:
31
-
32
- // \`\`\`js
33
- // <button>{"="}</button>
34
- // \`\`\`
35
-
36
- // Another core difference is that Ripple defines reactive variables by their usage of a "$" prefix. If the variable declaration does not have a dollar prefix, it is not reactive.
37
-
38
- // \`\`\`js
39
- // component HelloComponent(props) {
40
- // let $count = 0;
41
-
42
- // <div>{$count}</div>;
43
- // <button onClick={() => $count++}>{"Increment"}</button>;
44
- // }
45
- // \`\`\`
46
-
47
- // Object properties can also be reactive if the property name starts with a "$" prefix.
48
-
49
- // \`\`\`js
50
- // component HelloComponent(props) {
51
- // let state = { $count: 0 };
52
-
53
- // <div>{state.$count}</div>;
54
- // <button onClick={() => state.$count++}>{"Increment"}</button>;
55
- // }
56
- // \`\`\`
57
-
58
- // Ripple doesn't allow for inline expressions with JSX for conditionals or for collections such as arrays or objects.
59
- // Instead, prefer using normal JavaScript logic where you have a "if" or "for" statement that wraps the JSX.
60
-
61
- // Here is valid Ripple code:
62
-
63
- // \`\`\`js
64
- // export component Counter() {
65
- // let $count = 0;
66
-
67
- // if ($count > 5) {
68
- // <div>{$count}</div>;
69
- // }
70
-
71
- // <div>
72
- // if ($count > 5) {
73
- // <div>{$count}</div>;
74
- // }
75
- // </div>;
76
-
77
- // for (const item of items) {
78
- // <div>{item}</div>;
79
- // }
80
-
81
- // <ul>
82
- // for (const item of items) {
83
- // <li>{item}</li>;
84
- // }
85
- // </ul>;
86
- // }
87
- // \`\`\`
88
-
89
- // Ripple allows for shorthand props on components, so '<Child state={state} />' can be written as '<Child {state} />'.
90
-
91
- // Ripple also allows for a singular "<style>" JSX element at the top level of the component declaration body. This is used for styling any JSX elements within the component.
92
- // The style element can contain any valid CSS, and can also contain CSS variables. CSS variables are defined with a "--" prefix. This is the preferred way of doing styling over inline styles.
93
-
94
- // If inline styles are to be used, then they should be done using the HTML style attribute approach rather than the JSX style attribute property approach.
95
-
96
- // In Ripple variables that are created with an identifier that starts with a "$" prefix are considered reactive. If declaration init expression also references reactive variables, or function expressions, then
97
- // this type of variable is considered "computed". Computed reactive declarations will re-run when any of the reactive variables they reference change. If this is not desired then the "untrack" function call should
98
- // be used to prevent reactivity.
99
-
100
- // \`\`\`js
101
- // import { untrack } from 'ripple';
102
-
103
- // component Counter({ $initial }) {
104
- // let $count = untrack(() => $initial);
105
- // }
106
- // \`\`\`
107
-
108
- // An important part of Ripple's reactivity model is that passing reactivity between boundaries can only happen via two ways:
109
- // - the usage of closures, where a value is referenced in a function or property getter
110
- // - the usage of objects and/or arrays, where the object or array is passed as a property with a "$" prefix so its reactivity is kept
111
-
112
- // For example if you were to create a typical Ripple hook function, then you should pass any reactive values through using objects. Otherwise, the
113
- // hook will act as a computed function and re-run every time the reactive value changes – which is likely not the desired behaviour of a "hook" function.
114
-
115
- // \`\`\`js
116
- // function useCounter(initial) {
117
- // let $count = initial;
118
- // const $double = $count * 2;
119
-
120
- // const increment = () => $count++;
121
-
122
- // return { $double, increment };
123
- // }
124
-
125
- // component Counter({ $count }) {
126
- // const { $double, increment } = useCounter($count);
127
-
128
- // <button onClick={increment}>{"Increment"}</button>;
129
- // <div>{$double}</div>;
130
- // }
131
- // \`\`\`
132
-
133
- // If a value needs to be mutated from within a hook, then it should be referenced by the hook in its object form instead:
134
-
135
- // \`\`\`js
136
- // function useCounter(state) {
137
- // const $double = state.$count * 2;
138
-
139
- // const increment = () => state.$count++;
140
-
141
- // return { $double, increment };
142
- // }
143
-
144
- // component Counter({ $count }) {
145
- // let $count = 0;
146
-
147
- // const { $double, increment } = useCounter({ $count });
148
-
149
- // <button onClick={increment}>{"Increment"}</button>;
150
- // <div>{$double}</div>;
151
- // }
152
- // \`\`\`
153
-
154
- // It should be noted that in this example, the "$count" inside the "Counter" component will not be mutated by the "increment" function.
155
-
156
- // If this is desired, then the call to "useCounter" needs to provide a getter and setter for the "$count" value:
157
-
158
- // \`\`\`js
159
- // function useCounter(state) {
160
- // const $double = state.$count * 2;
161
-
162
- // const increment = () => state.$count++;
163
-
164
- // return { $double, increment };
165
- // }
166
-
167
- // component Counter({ $count }) {
168
- // let $count = 0;
169
-
170
- // const { $double, increment } = useCounter({ get $count() { return $count }, set $count(value) { $count = value } });
171
-
172
- // <button onClick={increment}>{"Increment"}</button>;
173
- // <div>{$double}</div>;
174
- // }
175
- // \`\`\`
176
-
177
- // Normally, you shouldn't provide getters/setters in the object returned from a hook, especially if the usage site intends to destruct the object.
178
-
179
- // Ripple also provides a way of handling Suspense and asynchronous data fetching. This requires two parts:
180
- // - a "try" block, that has an "async" block that shows the fallback pending UI. These blocks can only be used inside Ripple components
181
- // - an "await" that must happen at the top-level of the component body
182
-
183
- // Here is an example:
184
-
185
- // \`\`\`js
186
- // export component App() {
187
- // try {
188
- // <Child />;
189
- // } async {
190
- // <div>{"Loading..."}</div>;
191
- // }
192
- // }
193
-
194
- // component Child() {
195
- // const $pokemons = await fetch('https://pokeapi.co/api/v2/pokemon/').then((res) => res.json());
196
-
197
- // for (const pokemon of $pokemons.results) {
198
- // <div>{pokemon.name}</div>;
199
- // }
200
- // }
201
- // \`\`\`
202
-
203
- // It's important that the transformed code never uses an async fetch() call inside an effect function. This is an anti-pattern, instead the "await" expression should be used
204
- // directly inside the fragment or component body. Also when using "await" then loading states should be handled using the "try" and "async" blocks, so this isn't required in the
205
- // output code.
206
-
207
- // Ripple also supports "fragment" syntax, which is similar to the "component" syntax but allows for multiple arguments:
208
-
209
- // \`\`\`js
210
- // fragment foo() {
211
- // <div>{"Hello World"}</div>;
212
- // }
213
-
214
- // component App() {
215
- // {fragment foo()};
216
- // }
217
- // \`\`\`
218
-
219
- // Fragments can be seen as reactive functions that can take arguments and using the "{@fragment fragment(...args)}" syntax, they can be rendered as if they were JSX elements.
220
-
221
- // Ripple denotes attributes and properties on JSX elements as being reactive when they also have a "$" prefix. This means that if a property is reactive, then the element will re-render when the property changes.
222
-
223
- // Ripple does not support both a non-reactive and reactive version of a prop – so having "$ref" and "ref" is not allowed. If a prop could be possibly reactive, then it should always have a "$" prefix to ensure maximum compatibility.
224
-
225
- // There are also some special attributes that such as "$ref" and "$children" that always start with a "$" prefix.
226
-
227
- // When creating an implicit children fragment from a JSX component, such as:
228
-
229
- // \`\`\`js
230
- // <ChildComponent>
231
- // {"Hello World"}
232
- // </ChildComponent>
233
- // \`\`\`
234
-
235
- // This can also be written as:
236
-
237
- // \`\`\`js
238
- // fragment $children() {
239
- // {"Hello World"};
240
- // }
241
-
242
- // <ChildComponent {$children} />;
243
- // \`\`\`
244
-
245
- // Which is the same as the previous example.
246
-
247
- // The "Hello world" will be passed as a "$children" prop to the "ChildComponent" and it will be of the type of "Fragment". Which means that it's not a string, or JSX element, but rather a special kind of thing.
248
-
249
- // To render a type of "Fragment" the {@fragment thing()} syntax should be used. This will render the "thing" as if it was a JSX element. Here's an example:
250
-
251
- // \`\`\`js
252
- // component Child({ $children }) {
253
- // <div>
254
- // {@fragment $children()};
255
- // </div>;
256
- // }
257
- // \`\`\`
258
-
259
- // Ripple uses for...of blocks for templating over collections or lists. While loops, standard for loops and while loops are not permitted in Ripple components or fragments.
260
-
261
- // For example, to render a list of items:
262
-
263
- // \`\`\`js
264
- // <ul>
265
- // for (const num of [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) {
266
- // <li>{num}</li>;
267
- // }
268
- // </ul>;
269
- // \`\`\`
270
-
271
-
272
- // `;
273
-
274
- // export async function validate_with_ai(source) {
275
- // const { text } = await generateText({
276
- // model: anthropic('claude-3-7-sonnet-20250219'),
277
- // messages: [
278
- // {
279
- // role: 'user',
280
- // content: default_prompt,
281
- // providerOptions: {
282
- // anthropic: { cacheControl: { type: 'ephemeral' } }
283
- // }
284
- // },
285
- // {
286
- // role: 'user',
287
- // content: `Please validate the following Ripple code and provide feedback on any issues:\n\n${source}`
288
- // }
289
- // ]
290
- // });
291
- // return text;
292
- // }
package/test-mappings.js DELETED
File without changes