@tsrx/react 0.0.4 → 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.
- package/package.json +2 -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.
|
|
6
|
+
"version": "0.0.6",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"publishConfig": {
|
|
9
9
|
"access": "public"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"esrap": "^2.1.0",
|
|
27
27
|
"zimmerframe": "^1.1.2",
|
|
28
|
-
"@tsrx/core": "0.0.
|
|
28
|
+
"@tsrx/core": "0.0.5"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"react": ">=18"
|
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:
|
|
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
|
|
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
|