@tsrx/react 0.0.5 → 0.0.6

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/transform.js +110 -2
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "React compiler built on @tsrx/core",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.0.5",
6
+ "version": "0.0.6",
7
7
  "type": "module",
8
8
  "publishConfig": {
9
9
  "access": "public"
package/src/transform.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  renderStylesheets,
9
9
  setLocation,
10
10
  applyLazyTransforms as apply_lazy_transforms,
11
+ findFirstTopLevelAwaitInComponentBody as find_first_top_level_await_in_component_body,
11
12
  collectLazyBindingsFromComponent as collect_lazy_bindings_from_component,
12
13
  preallocateLazyIds as preallocate_lazy_ids,
13
14
  replaceLazyParams as replace_lazy_params,
@@ -50,6 +51,7 @@ import {
50
51
  export function transform(ast, source, filename) {
51
52
  /** @type {any[]} */
52
53
  const stylesheets = [];
54
+ const module_uses_server_directive = has_use_server_directive(ast);
53
55
 
54
56
  /** @type {TransformContext} */
55
57
  const transform_context = {
@@ -67,6 +69,22 @@ export function transform(ast, source, filename) {
67
69
  walk(/** @type {any} */ (ast), transform_context, {
68
70
  Component(node, { next, state }) {
69
71
  const as_any = /** @type {any} */ (node);
72
+ const await_expression = find_first_top_level_await_in_component_body(as_any.body || []);
73
+
74
+ if (await_expression && !module_uses_server_directive) {
75
+ throw create_compile_error(
76
+ await_expression,
77
+ 'React components can only use `await` when the module has a top-level "use server" directive.',
78
+ );
79
+ }
80
+
81
+ if (await_expression) {
82
+ as_any.metadata = /** @type {any} */ ({
83
+ ...(as_any.metadata || {}),
84
+ contains_top_level_await: true,
85
+ });
86
+ }
87
+
70
88
  const css = as_any.css;
71
89
  if (css) {
72
90
  stylesheets.push(css);
@@ -194,6 +212,9 @@ function component_to_function_declaration(component, transform_context, walk_he
194
212
  const helper_state = walk_helper_state || create_helper_state(component.id?.name || 'Component');
195
213
  const params = component.params || [];
196
214
  const body = /** @type {any[]} */ (component.body || []);
215
+ const is_async_component =
216
+ !!component?.metadata?.contains_top_level_await ||
217
+ find_first_top_level_await_in_component_body(body) !== null;
197
218
 
198
219
  // Collect param bindings from original patterns (lazy patterns still intact).
199
220
  const param_bindings = collect_param_bindings(params);
@@ -235,7 +256,7 @@ function component_to_function_declaration(component, transform_context, walk_he
235
256
  id: component.id,
236
257
  params: final_params,
237
258
  body: final_body,
238
- async: false,
259
+ async: is_async_component,
239
260
  generator: false,
240
261
  metadata: {
241
262
  path: [],
@@ -514,6 +535,34 @@ function is_hook_callee(callee) {
514
535
  return false;
515
536
  }
516
537
 
538
+ /**
539
+ * @param {AST.Program} program
540
+ * @returns {boolean}
541
+ */
542
+ function has_use_server_directive(program) {
543
+ for (const statement of program.body || []) {
544
+ const directive = /** @type {any} */ (statement).directive;
545
+
546
+ if (directive === 'use server') {
547
+ return true;
548
+ }
549
+
550
+ if (
551
+ statement.type === 'ExpressionStatement' &&
552
+ statement.expression?.type === 'Literal' &&
553
+ statement.expression.value === 'use server'
554
+ ) {
555
+ return true;
556
+ }
557
+
558
+ if (directive == null) {
559
+ break;
560
+ }
561
+ }
562
+
563
+ return false;
564
+ }
565
+
517
566
  /**
518
567
  * @param {any[]} body_nodes
519
568
  * @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} helper_state
@@ -1519,6 +1568,13 @@ function find_key_expression_in_body(body_nodes) {
1519
1568
  * @returns {ESTreeJSX.JSXExpressionContainer}
1520
1569
  */
1521
1570
  function for_of_statement_to_jsx_child(node, transform_context) {
1571
+ if (node.await) {
1572
+ throw create_compile_error(
1573
+ node,
1574
+ 'React TSRX does not support `for await...of` in component templates.',
1575
+ );
1576
+ }
1577
+
1522
1578
  if (node.key) {
1523
1579
  throw create_compile_error(
1524
1580
  node.key,
@@ -1529,7 +1585,15 @@ function for_of_statement_to_jsx_child(node, transform_context) {
1529
1585
  const loop_params = get_for_of_iteration_params(node.left, node.index);
1530
1586
  const loop_body = node.body.type === 'BlockStatement' ? node.body.body : [node.body];
1531
1587
  const has_hooks = body_contains_top_level_hook_call(loop_body);
1532
- const key_expression = has_hooks ? find_key_expression_in_body(loop_body) : undefined;
1588
+ const explicit_key_expression = has_hooks ? find_key_expression_in_body(loop_body) : undefined;
1589
+ const key_expression =
1590
+ has_hooks && explicit_key_expression == null && node.index
1591
+ ? clone_expression_node(node.index)
1592
+ : explicit_key_expression;
1593
+ const implicit_non_hook_key_expression =
1594
+ !has_hooks && node.index && find_key_expression_in_body(loop_body) == null
1595
+ ? clone_expression_node(node.index)
1596
+ : undefined;
1533
1597
 
1534
1598
  // Add loop params to available bindings so hoisted helpers receive them as props
1535
1599
  const saved_bindings = transform_context.available_bindings;
@@ -1542,6 +1606,10 @@ function for_of_statement_to_jsx_child(node, transform_context) {
1542
1606
  ? hook_safe_render_statements(loop_body, key_expression, transform_context)
1543
1607
  : build_render_statements(loop_body, true, transform_context);
1544
1608
 
1609
+ if (implicit_non_hook_key_expression) {
1610
+ apply_key_to_render_statements(body_statements, implicit_non_hook_key_expression);
1611
+ }
1612
+
1545
1613
  // Restore bindings
1546
1614
  transform_context.available_bindings = saved_bindings;
1547
1615
 
@@ -1578,6 +1646,46 @@ function for_of_statement_to_jsx_child(node, transform_context) {
1578
1646
  );
1579
1647
  }
1580
1648
 
1649
+ /**
1650
+ * @param {any[]} statements
1651
+ * @param {any} key_expression
1652
+ * @returns {void}
1653
+ */
1654
+ function apply_key_to_render_statements(statements, key_expression) {
1655
+ for (let i = statements.length - 1; i >= 0; i -= 1) {
1656
+ const statement = statements[i];
1657
+ if (statement?.type !== 'ReturnStatement' || !statement.argument) {
1658
+ continue;
1659
+ }
1660
+
1661
+ if (statement.argument.type === 'JSXElement') {
1662
+ const attributes = statement.argument.openingElement?.attributes || [];
1663
+ const has_key = attributes.some(
1664
+ (/** @type {any} */ attr) =>
1665
+ attr.type === 'JSXAttribute' &&
1666
+ attr.name?.type === 'JSXIdentifier' &&
1667
+ attr.name.name === 'key',
1668
+ );
1669
+
1670
+ if (!has_key) {
1671
+ attributes.push(
1672
+ /** @type {any} */ ({
1673
+ type: 'JSXAttribute',
1674
+ name: { type: 'JSXIdentifier', name: 'key', metadata: { path: [] } },
1675
+ value: to_jsx_expression_container(
1676
+ clone_expression_node(key_expression),
1677
+ key_expression,
1678
+ ),
1679
+ metadata: { path: [] },
1680
+ }),
1681
+ );
1682
+ }
1683
+ }
1684
+
1685
+ return;
1686
+ }
1687
+ }
1688
+
1581
1689
  /**
1582
1690
  * @param {any} node
1583
1691
  * @param {TransformContext} transform_context