@tsrx/react 0.0.5 → 0.0.7
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 +1 -1
- package/src/transform.js +221 -2
package/package.json
CHANGED
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
|
|
@@ -832,6 +881,116 @@ function references_scope_bindings(node, scope_bindings) {
|
|
|
832
881
|
return false;
|
|
833
882
|
}
|
|
834
883
|
|
|
884
|
+
/**
|
|
885
|
+
* @param {AST.Literal} node
|
|
886
|
+
* @returns {boolean}
|
|
887
|
+
*/
|
|
888
|
+
function is_static_literal(node) {
|
|
889
|
+
return (
|
|
890
|
+
node.value === null ||
|
|
891
|
+
typeof node.value === 'string' ||
|
|
892
|
+
typeof node.value === 'number' ||
|
|
893
|
+
typeof node.value === 'boolean' ||
|
|
894
|
+
typeof node.value === 'bigint'
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* @param {any} node
|
|
900
|
+
* @returns {boolean}
|
|
901
|
+
*/
|
|
902
|
+
function is_hoist_safe_expression(node) {
|
|
903
|
+
if (!node || typeof node !== 'object') return false;
|
|
904
|
+
|
|
905
|
+
switch (node.type) {
|
|
906
|
+
case 'Literal':
|
|
907
|
+
return is_static_literal(node);
|
|
908
|
+
case 'TemplateLiteral':
|
|
909
|
+
return node.expressions.length === 0;
|
|
910
|
+
case 'UnaryExpression':
|
|
911
|
+
return node.operator !== 'delete' && is_hoist_safe_expression(node.argument);
|
|
912
|
+
case 'BinaryExpression':
|
|
913
|
+
case 'LogicalExpression':
|
|
914
|
+
return is_hoist_safe_expression(node.left) && is_hoist_safe_expression(node.right);
|
|
915
|
+
case 'ConditionalExpression':
|
|
916
|
+
return (
|
|
917
|
+
is_hoist_safe_expression(node.test) &&
|
|
918
|
+
is_hoist_safe_expression(node.consequent) &&
|
|
919
|
+
is_hoist_safe_expression(node.alternate)
|
|
920
|
+
);
|
|
921
|
+
case 'SequenceExpression':
|
|
922
|
+
return node.expressions.every(is_hoist_safe_expression);
|
|
923
|
+
case 'ParenthesizedExpression':
|
|
924
|
+
return is_hoist_safe_expression(node.expression);
|
|
925
|
+
case 'JSXElement':
|
|
926
|
+
return is_hoist_safe_jsx_node(node);
|
|
927
|
+
case 'JSXFragment':
|
|
928
|
+
return node.children.every(is_hoist_safe_jsx_child);
|
|
929
|
+
default:
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* @param {any} node
|
|
936
|
+
* @returns {boolean}
|
|
937
|
+
*/
|
|
938
|
+
function is_hoist_safe_jsx_child(node) {
|
|
939
|
+
if (!node || typeof node !== 'object') return false;
|
|
940
|
+
|
|
941
|
+
switch (node.type) {
|
|
942
|
+
case 'JSXText':
|
|
943
|
+
return true;
|
|
944
|
+
case 'JSXElement':
|
|
945
|
+
return is_hoist_safe_jsx_node(node);
|
|
946
|
+
case 'JSXFragment':
|
|
947
|
+
return node.children.every(is_hoist_safe_jsx_child);
|
|
948
|
+
case 'JSXExpressionContainer':
|
|
949
|
+
return (
|
|
950
|
+
node.expression.type !== 'JSXEmptyExpression' && is_hoist_safe_expression(node.expression)
|
|
951
|
+
);
|
|
952
|
+
default:
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* @param {ESTreeJSX.JSXAttribute | ESTreeJSX.JSXSpreadAttribute} attribute
|
|
959
|
+
* @returns {boolean}
|
|
960
|
+
*/
|
|
961
|
+
function is_hoist_safe_jsx_attribute(attribute) {
|
|
962
|
+
if (attribute.type === 'JSXSpreadAttribute') return false;
|
|
963
|
+
if (attribute.value == null) return true;
|
|
964
|
+
|
|
965
|
+
if (attribute.value.type === 'Literal') {
|
|
966
|
+
return is_static_literal(attribute.value);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
if (attribute.value.type === 'JSXExpressionContainer') {
|
|
970
|
+
return (
|
|
971
|
+
attribute.value.expression.type !== 'JSXEmptyExpression' &&
|
|
972
|
+
is_hoist_safe_expression(attribute.value.expression)
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* @param {ESTreeJSX.JSXElement | ESTreeJSX.JSXFragment} node
|
|
981
|
+
* @returns {boolean}
|
|
982
|
+
*/
|
|
983
|
+
function is_hoist_safe_jsx_node(node) {
|
|
984
|
+
if (node.type === 'JSXFragment') {
|
|
985
|
+
return node.children.every(is_hoist_safe_jsx_child);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
return (
|
|
989
|
+
node.openingElement.attributes.every(is_hoist_safe_jsx_attribute) &&
|
|
990
|
+
node.children.every(is_hoist_safe_jsx_child)
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
|
|
835
994
|
/**
|
|
836
995
|
* Hoist static JSX elements from render_nodes to module level.
|
|
837
996
|
* A JSX element is static if it doesn't reference any component-scope bindings.
|
|
@@ -847,6 +1006,7 @@ function hoist_static_render_nodes(render_nodes, transform_context) {
|
|
|
847
1006
|
for (let i = 0; i < render_nodes.length; i++) {
|
|
848
1007
|
const node = render_nodes[i];
|
|
849
1008
|
if (node.type !== 'JSXElement') continue;
|
|
1009
|
+
if (!is_hoist_safe_jsx_node(node)) continue;
|
|
850
1010
|
if (references_scope_bindings(node, transform_context.available_bindings)) continue;
|
|
851
1011
|
|
|
852
1012
|
const name = create_helper_name(transform_context.helper_state, 'static');
|
|
@@ -1519,6 +1679,13 @@ function find_key_expression_in_body(body_nodes) {
|
|
|
1519
1679
|
* @returns {ESTreeJSX.JSXExpressionContainer}
|
|
1520
1680
|
*/
|
|
1521
1681
|
function for_of_statement_to_jsx_child(node, transform_context) {
|
|
1682
|
+
if (node.await) {
|
|
1683
|
+
throw create_compile_error(
|
|
1684
|
+
node,
|
|
1685
|
+
'React TSRX does not support `for await...of` in component templates.',
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1522
1689
|
if (node.key) {
|
|
1523
1690
|
throw create_compile_error(
|
|
1524
1691
|
node.key,
|
|
@@ -1529,7 +1696,15 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
1529
1696
|
const loop_params = get_for_of_iteration_params(node.left, node.index);
|
|
1530
1697
|
const loop_body = node.body.type === 'BlockStatement' ? node.body.body : [node.body];
|
|
1531
1698
|
const has_hooks = body_contains_top_level_hook_call(loop_body);
|
|
1532
|
-
const
|
|
1699
|
+
const explicit_key_expression = has_hooks ? find_key_expression_in_body(loop_body) : undefined;
|
|
1700
|
+
const key_expression =
|
|
1701
|
+
has_hooks && explicit_key_expression == null && node.index
|
|
1702
|
+
? clone_expression_node(node.index)
|
|
1703
|
+
: explicit_key_expression;
|
|
1704
|
+
const implicit_non_hook_key_expression =
|
|
1705
|
+
!has_hooks && node.index && find_key_expression_in_body(loop_body) == null
|
|
1706
|
+
? clone_expression_node(node.index)
|
|
1707
|
+
: undefined;
|
|
1533
1708
|
|
|
1534
1709
|
// Add loop params to available bindings so hoisted helpers receive them as props
|
|
1535
1710
|
const saved_bindings = transform_context.available_bindings;
|
|
@@ -1542,6 +1717,10 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
1542
1717
|
? hook_safe_render_statements(loop_body, key_expression, transform_context)
|
|
1543
1718
|
: build_render_statements(loop_body, true, transform_context);
|
|
1544
1719
|
|
|
1720
|
+
if (implicit_non_hook_key_expression) {
|
|
1721
|
+
apply_key_to_render_statements(body_statements, implicit_non_hook_key_expression);
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1545
1724
|
// Restore bindings
|
|
1546
1725
|
transform_context.available_bindings = saved_bindings;
|
|
1547
1726
|
|
|
@@ -1578,6 +1757,46 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
1578
1757
|
);
|
|
1579
1758
|
}
|
|
1580
1759
|
|
|
1760
|
+
/**
|
|
1761
|
+
* @param {any[]} statements
|
|
1762
|
+
* @param {any} key_expression
|
|
1763
|
+
* @returns {void}
|
|
1764
|
+
*/
|
|
1765
|
+
function apply_key_to_render_statements(statements, key_expression) {
|
|
1766
|
+
for (let i = statements.length - 1; i >= 0; i -= 1) {
|
|
1767
|
+
const statement = statements[i];
|
|
1768
|
+
if (statement?.type !== 'ReturnStatement' || !statement.argument) {
|
|
1769
|
+
continue;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
if (statement.argument.type === 'JSXElement') {
|
|
1773
|
+
const attributes = statement.argument.openingElement?.attributes || [];
|
|
1774
|
+
const has_key = attributes.some(
|
|
1775
|
+
(/** @type {any} */ attr) =>
|
|
1776
|
+
attr.type === 'JSXAttribute' &&
|
|
1777
|
+
attr.name?.type === 'JSXIdentifier' &&
|
|
1778
|
+
attr.name.name === 'key',
|
|
1779
|
+
);
|
|
1780
|
+
|
|
1781
|
+
if (!has_key) {
|
|
1782
|
+
attributes.push(
|
|
1783
|
+
/** @type {any} */ ({
|
|
1784
|
+
type: 'JSXAttribute',
|
|
1785
|
+
name: { type: 'JSXIdentifier', name: 'key', metadata: { path: [] } },
|
|
1786
|
+
value: to_jsx_expression_container(
|
|
1787
|
+
clone_expression_node(key_expression),
|
|
1788
|
+
key_expression,
|
|
1789
|
+
),
|
|
1790
|
+
metadata: { path: [] },
|
|
1791
|
+
}),
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1581
1800
|
/**
|
|
1582
1801
|
* @param {any} node
|
|
1583
1802
|
* @param {TransformContext} transform_context
|