@tsrx/react 0.0.7 → 0.1.0
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 +66 -40
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.1.0",
|
|
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.6"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"react": ">=18"
|
package/src/transform.js
CHANGED
|
@@ -14,6 +14,9 @@ import {
|
|
|
14
14
|
replaceLazyParams as replace_lazy_params,
|
|
15
15
|
prepareStylesheetForRender as prepare_stylesheet_for_render,
|
|
16
16
|
annotateComponentWithHash as annotate_component_with_hash,
|
|
17
|
+
isInterleavedBody as is_interleaved_body_core,
|
|
18
|
+
isCapturableJsxChild as is_capturable_jsx_child,
|
|
19
|
+
captureJsxChild,
|
|
17
20
|
} from '@tsrx/core';
|
|
18
21
|
|
|
19
22
|
/**
|
|
@@ -51,7 +54,6 @@ import {
|
|
|
51
54
|
export function transform(ast, source, filename) {
|
|
52
55
|
/** @type {any[]} */
|
|
53
56
|
const stylesheets = [];
|
|
54
|
-
const module_uses_server_directive = has_use_server_directive(ast);
|
|
55
57
|
|
|
56
58
|
/** @type {TransformContext} */
|
|
57
59
|
const transform_context = {
|
|
@@ -71,13 +73,6 @@ export function transform(ast, source, filename) {
|
|
|
71
73
|
const as_any = /** @type {any} */ (node);
|
|
72
74
|
const await_expression = find_first_top_level_await_in_component_body(as_any.body || []);
|
|
73
75
|
|
|
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
76
|
if (await_expression) {
|
|
82
77
|
as_any.metadata = /** @type {any} */ ({
|
|
83
78
|
...(as_any.metadata || {}),
|
|
@@ -304,6 +299,10 @@ function build_component_statements(
|
|
|
304
299
|
const render_nodes = [];
|
|
305
300
|
const bindings = new Map(available_bindings);
|
|
306
301
|
|
|
302
|
+
const pre_split_body = body_nodes.slice(0, split_index);
|
|
303
|
+
const interleaved = is_interleaved_body(pre_split_body);
|
|
304
|
+
let capture_index = 0;
|
|
305
|
+
|
|
307
306
|
for (let i = 0; i < split_index; i += 1) {
|
|
308
307
|
const child = body_nodes[i];
|
|
309
308
|
|
|
@@ -318,7 +317,14 @@ function build_component_statements(
|
|
|
318
317
|
}
|
|
319
318
|
|
|
320
319
|
if (is_jsx_child(child)) {
|
|
321
|
-
|
|
320
|
+
const jsx = to_jsx_child(child, transform_context);
|
|
321
|
+
if (interleaved && is_capturable_jsx_child(jsx)) {
|
|
322
|
+
const { declaration, reference } = captureJsxChild(jsx, capture_index++);
|
|
323
|
+
statements.push(declaration);
|
|
324
|
+
render_nodes.push(reference);
|
|
325
|
+
} else {
|
|
326
|
+
render_nodes.push(jsx);
|
|
327
|
+
}
|
|
322
328
|
} else {
|
|
323
329
|
statements.push(child);
|
|
324
330
|
collect_statement_bindings(child, bindings);
|
|
@@ -326,7 +332,9 @@ function build_component_statements(
|
|
|
326
332
|
}
|
|
327
333
|
}
|
|
328
334
|
|
|
329
|
-
|
|
335
|
+
if (!interleaved) {
|
|
336
|
+
hoist_static_render_nodes(render_nodes, transform_context);
|
|
337
|
+
}
|
|
330
338
|
|
|
331
339
|
const split_node = body_nodes[split_index];
|
|
332
340
|
const consequent_body =
|
|
@@ -389,6 +397,14 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
389
397
|
const saved_bindings = transform_context.available_bindings;
|
|
390
398
|
transform_context.available_bindings = new Map(saved_bindings);
|
|
391
399
|
|
|
400
|
+
// When non-JSX statements are interleaved with JSX children, we must
|
|
401
|
+
// preserve source order so each JSX expression sees the variable state
|
|
402
|
+
// at its textual position. Otherwise statements would all run before
|
|
403
|
+
// any JSX is constructed, and every JSX child would observe the final
|
|
404
|
+
// state of mutable variables.
|
|
405
|
+
const interleaved = is_interleaved_body(body_nodes);
|
|
406
|
+
let capture_index = 0;
|
|
407
|
+
|
|
392
408
|
for (const child of body_nodes) {
|
|
393
409
|
if (is_bare_return_statement(child)) {
|
|
394
410
|
statements.push(create_component_return_statement(render_nodes, child));
|
|
@@ -402,14 +418,23 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
402
418
|
}
|
|
403
419
|
|
|
404
420
|
if (is_jsx_child(child)) {
|
|
405
|
-
|
|
421
|
+
const jsx = to_jsx_child(child, transform_context);
|
|
422
|
+
if (interleaved && is_capturable_jsx_child(jsx)) {
|
|
423
|
+
const { declaration, reference } = captureJsxChild(jsx, capture_index++);
|
|
424
|
+
statements.push(declaration);
|
|
425
|
+
render_nodes.push(reference);
|
|
426
|
+
} else {
|
|
427
|
+
render_nodes.push(jsx);
|
|
428
|
+
}
|
|
406
429
|
} else {
|
|
407
430
|
statements.push(child);
|
|
408
431
|
collect_statement_bindings(child, transform_context.available_bindings);
|
|
409
432
|
}
|
|
410
433
|
}
|
|
411
434
|
|
|
412
|
-
|
|
435
|
+
if (!interleaved) {
|
|
436
|
+
hoist_static_render_nodes(render_nodes, transform_context);
|
|
437
|
+
}
|
|
413
438
|
|
|
414
439
|
const return_arg = build_return_expression(render_nodes);
|
|
415
440
|
if (return_arg || return_null_when_empty) {
|
|
@@ -423,6 +448,22 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
423
448
|
return statements;
|
|
424
449
|
}
|
|
425
450
|
|
|
451
|
+
/**
|
|
452
|
+
* React-specific wrapper around the core `isInterleavedBody` helper that
|
|
453
|
+
* ignores bare `return` / lone return-if statements. Those are rewriting
|
|
454
|
+
* signals rather than user-visible side effects, so JSX children around
|
|
455
|
+
* them don't need capturing.
|
|
456
|
+
*
|
|
457
|
+
* @param {any[]} body_nodes
|
|
458
|
+
* @returns {boolean}
|
|
459
|
+
*/
|
|
460
|
+
function is_interleaved_body(body_nodes) {
|
|
461
|
+
const filtered = body_nodes.filter(
|
|
462
|
+
(child) => !is_bare_return_statement(child) && !is_lone_return_if_statement(child),
|
|
463
|
+
);
|
|
464
|
+
return is_interleaved_body_core(filtered, is_jsx_child);
|
|
465
|
+
}
|
|
466
|
+
|
|
426
467
|
/**
|
|
427
468
|
* @param {any[]} body_nodes
|
|
428
469
|
* @returns {number}
|
|
@@ -535,34 +576,6 @@ function is_hook_callee(callee) {
|
|
|
535
576
|
return false;
|
|
536
577
|
}
|
|
537
578
|
|
|
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
|
-
|
|
566
579
|
/**
|
|
567
580
|
* @param {any[]} body_nodes
|
|
568
581
|
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} helper_state
|
|
@@ -1203,6 +1216,19 @@ function to_jsx_element(node, transform_context) {
|
|
|
1203
1216
|
return dynamic_element_to_jsx_child(node, transform_context);
|
|
1204
1217
|
}
|
|
1205
1218
|
|
|
1219
|
+
if (!node.id) {
|
|
1220
|
+
const children = create_element_children(node.children || [], transform_context);
|
|
1221
|
+
return set_loc(
|
|
1222
|
+
/** @type {any} */ ({
|
|
1223
|
+
type: 'JSXFragment',
|
|
1224
|
+
openingFragment: { type: 'JSXOpeningFragment' },
|
|
1225
|
+
closingFragment: { type: 'JSXClosingFragment' },
|
|
1226
|
+
children,
|
|
1227
|
+
}),
|
|
1228
|
+
node,
|
|
1229
|
+
);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1206
1232
|
const name = identifier_to_jsx_name(node.id);
|
|
1207
1233
|
const attributes = (node.attributes || []).map(to_jsx_attribute);
|
|
1208
1234
|
const selfClosing = !!node.selfClosing;
|