@tsrx/core 0.0.24 → 0.0.26
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/index.js +4 -1
- package/src/plugin.js +36 -2
- package/src/transform/jsx/index.js +171 -38
- package/src/utils/builders.js +7 -4
- package/src/utils/escaping.js +11 -0
- package/src/utils/events.js +1 -1
- package/types/index.d.ts +12 -6
- package/types/jsx-platform.d.ts +1 -1
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -133,8 +133,11 @@ export {
|
|
|
133
133
|
// Sanitize
|
|
134
134
|
export { sanitize_template_string as sanitizeTemplateString } from './utils/sanitize_template_string.js';
|
|
135
135
|
|
|
136
|
+
// CSS Property Name
|
|
137
|
+
export { normalize_css_property_name as normalizeCssPropertyName } from './utils/normalize_css_property_name.js';
|
|
138
|
+
|
|
136
139
|
// Escaping
|
|
137
|
-
export { escape } from './utils/escaping.js';
|
|
140
|
+
export { escape, escape_script as escapeScript } from './utils/escaping.js';
|
|
138
141
|
|
|
139
142
|
// Transform
|
|
140
143
|
export {
|
package/src/plugin.js
CHANGED
|
@@ -313,6 +313,12 @@ export function TSRXPlugin(config) {
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
#popTemplateLiteralTokenContext() {
|
|
317
|
+
while (this.curContext()?.token === '`') {
|
|
318
|
+
this.context.pop();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
316
322
|
#isDoubleQuotedTextChildStart() {
|
|
317
323
|
if (this.#path.findLast((n) => n.type === 'TsxCompat' || n.type === 'Tsx')) {
|
|
318
324
|
return false;
|
|
@@ -1097,6 +1103,19 @@ export function TSRXPlugin(config) {
|
|
|
1097
1103
|
this.parseFunctionParams(node);
|
|
1098
1104
|
this.checkComponentParams(node.params);
|
|
1099
1105
|
|
|
1106
|
+
const is_arrow_component = this.type === tt.arrow;
|
|
1107
|
+
if (is_arrow_component) {
|
|
1108
|
+
if (node.id || requireName || skipName) {
|
|
1109
|
+
this.raise(
|
|
1110
|
+
this.start,
|
|
1111
|
+
'Arrow component syntax is only supported for anonymous component expressions.',
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
node.metadata ??= { path: [] };
|
|
1115
|
+
node.metadata.arrow = true;
|
|
1116
|
+
this.next();
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1100
1119
|
// Reset before `eat(braceL)` so the lookahead `next()` it triggers reads
|
|
1101
1120
|
// the component body's first token as if we'd entered fresh — no
|
|
1102
1121
|
// surrounding function body should affect our parseStatement/parseBlock
|
|
@@ -2370,7 +2389,7 @@ export function TSRXPlugin(config) {
|
|
|
2370
2389
|
body.push(node);
|
|
2371
2390
|
} else if (this.type === tstt.jsxTagStart) {
|
|
2372
2391
|
// Parse JSX element
|
|
2373
|
-
const node = super.
|
|
2392
|
+
const node = super.jsx_parseElement();
|
|
2374
2393
|
body.push(node);
|
|
2375
2394
|
} else {
|
|
2376
2395
|
const start = this.start;
|
|
@@ -2401,6 +2420,7 @@ export function TSRXPlugin(config) {
|
|
|
2401
2420
|
body.push(node);
|
|
2402
2421
|
}
|
|
2403
2422
|
|
|
2423
|
+
this.#popTemplateLiteralTokenContext();
|
|
2404
2424
|
// Always call next() to ensure parser makes progress
|
|
2405
2425
|
this.next();
|
|
2406
2426
|
}
|
|
@@ -2433,7 +2453,7 @@ export function TSRXPlugin(config) {
|
|
|
2433
2453
|
body.push(node);
|
|
2434
2454
|
} else if (this.type === tstt.jsxTagStart) {
|
|
2435
2455
|
// Parse JSX element
|
|
2436
|
-
const node = super.
|
|
2456
|
+
const node = super.jsx_parseElement();
|
|
2437
2457
|
body.push(node);
|
|
2438
2458
|
} else {
|
|
2439
2459
|
const start = this.start;
|
|
@@ -2464,6 +2484,7 @@ export function TSRXPlugin(config) {
|
|
|
2464
2484
|
body.push(node);
|
|
2465
2485
|
}
|
|
2466
2486
|
|
|
2487
|
+
this.#popTemplateLiteralTokenContext();
|
|
2467
2488
|
this.next();
|
|
2468
2489
|
}
|
|
2469
2490
|
}
|
|
@@ -2748,6 +2769,19 @@ export function TSRXPlugin(config) {
|
|
|
2748
2769
|
return node;
|
|
2749
2770
|
}
|
|
2750
2771
|
|
|
2772
|
+
if (
|
|
2773
|
+
this.#functionBodyDepth === 0 &&
|
|
2774
|
+
this.type === tt.string &&
|
|
2775
|
+
this.input.charCodeAt(this.start) === 34 &&
|
|
2776
|
+
(this.#path.at(-1)?.type === 'Component' || this.#path.at(-1)?.type === 'Element')
|
|
2777
|
+
) {
|
|
2778
|
+
this.pos = this.start;
|
|
2779
|
+
this.#readDoubleQuotedTextChildToken();
|
|
2780
|
+
const node = this.parseDoubleQuotedTextChild();
|
|
2781
|
+
this.semicolon();
|
|
2782
|
+
return node;
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2751
2785
|
// &[ or &{ at statement level — lazy destructuring assignment
|
|
2752
2786
|
// e.g., &[data] = track(0); or &{x, y} = obj;
|
|
2753
2787
|
if (this.type === tt.bitwiseAND) {
|
|
@@ -487,7 +487,7 @@ function has_use_server_directive(program) {
|
|
|
487
487
|
* @param {any} component
|
|
488
488
|
* @param {TransformContext} transform_context
|
|
489
489
|
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} [walk_helper_state]
|
|
490
|
-
* @returns {AST.FunctionDeclaration}
|
|
490
|
+
* @returns {AST.FunctionDeclaration | AST.FunctionExpression | AST.ArrowFunctionExpression}
|
|
491
491
|
*/
|
|
492
492
|
export function component_to_function_declaration(component, transform_context, walk_helper_state) {
|
|
493
493
|
const helper_state = walk_helper_state || create_helper_state(component.id?.name || 'Component');
|
|
@@ -527,28 +527,62 @@ export function component_to_function_declaration(component, transform_context,
|
|
|
527
527
|
const final_body =
|
|
528
528
|
lazy_bindings.size > 0 ? apply_lazy_transforms(body_block, lazy_bindings) : body_block;
|
|
529
529
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
530
|
+
/** @type {AST.FunctionDeclaration | AST.FunctionExpression | AST.ArrowFunctionExpression} */
|
|
531
|
+
let fn;
|
|
532
|
+
|
|
533
|
+
if (component.id) {
|
|
534
|
+
fn = /** @type {any} */ ({
|
|
535
|
+
type: 'FunctionDeclaration',
|
|
536
|
+
id: component.id,
|
|
537
|
+
typeParameters: component.typeParameters,
|
|
538
|
+
params: final_params,
|
|
539
|
+
body: final_body,
|
|
540
|
+
async: is_async_component,
|
|
541
|
+
generator: false,
|
|
542
|
+
metadata: {
|
|
543
|
+
path: [],
|
|
544
|
+
is_component: true,
|
|
545
|
+
},
|
|
546
|
+
});
|
|
547
|
+
} else if (component.metadata?.arrow) {
|
|
548
|
+
fn = /** @type {any} */ ({
|
|
549
|
+
type: 'ArrowFunctionExpression',
|
|
550
|
+
typeParameters: component.typeParameters,
|
|
551
|
+
params: final_params,
|
|
552
|
+
body: final_body,
|
|
553
|
+
async: is_async_component,
|
|
554
|
+
generator: false,
|
|
555
|
+
expression: false,
|
|
556
|
+
metadata: {
|
|
557
|
+
path: [],
|
|
558
|
+
is_component: true,
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
} else {
|
|
562
|
+
fn = /** @type {any} */ ({
|
|
563
|
+
type: 'FunctionExpression',
|
|
564
|
+
id: null,
|
|
565
|
+
typeParameters: component.typeParameters,
|
|
566
|
+
params: final_params,
|
|
567
|
+
body: final_body,
|
|
568
|
+
async: is_async_component,
|
|
569
|
+
generator: false,
|
|
570
|
+
metadata: {
|
|
571
|
+
path: [],
|
|
572
|
+
is_component: true,
|
|
573
|
+
},
|
|
574
|
+
});
|
|
575
|
+
}
|
|
543
576
|
|
|
544
577
|
// Restore context
|
|
545
578
|
transform_context.helper_state = saved_helper_state;
|
|
546
579
|
transform_context.available_bindings = saved_bindings;
|
|
547
580
|
|
|
548
|
-
|
|
549
|
-
|
|
581
|
+
const fn_metadata = /** @type {any} */ (fn.metadata);
|
|
582
|
+
fn_metadata.generated_helpers = helper_state.helpers;
|
|
583
|
+
fn_metadata.generated_statics = helper_state.statics;
|
|
550
584
|
|
|
551
|
-
if (fn.id) {
|
|
585
|
+
if (fn.type === 'FunctionDeclaration' && fn.id) {
|
|
552
586
|
fn.id.metadata = /** @type {AST.Identifier['metadata']} */ ({
|
|
553
587
|
...fn.id.metadata,
|
|
554
588
|
is_component: true,
|
|
@@ -590,6 +624,10 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
590
624
|
// any JSX is constructed, and every JSX child would observe the final
|
|
591
625
|
// state of mutable variables.
|
|
592
626
|
const interleaved = is_interleaved_body(body_nodes);
|
|
627
|
+
const capture_static_early_return_nodes =
|
|
628
|
+
!interleaved &&
|
|
629
|
+
!transform_context.platform.hooks?.isTopLevelSetupCall &&
|
|
630
|
+
body_nodes.filter(is_returning_if_statement).length > 1;
|
|
593
631
|
let capture_index = 0;
|
|
594
632
|
|
|
595
633
|
for (let i = 0; i < body_nodes.length; i += 1) {
|
|
@@ -614,6 +652,15 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
614
652
|
true,
|
|
615
653
|
);
|
|
616
654
|
|
|
655
|
+
if (capture_static_early_return_nodes) {
|
|
656
|
+
capture_index = capture_static_early_return_render_nodes(
|
|
657
|
+
render_nodes,
|
|
658
|
+
statements,
|
|
659
|
+
capture_index,
|
|
660
|
+
transform_context,
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
|
|
617
664
|
if (branch_has_hooks || continuation_has_hooks) {
|
|
618
665
|
if (transform_context.platform.hooks?.isTopLevelSetupCall) {
|
|
619
666
|
statements.push(
|
|
@@ -1291,15 +1338,68 @@ function hoist_static_render_nodes(render_nodes, transform_context) {
|
|
|
1291
1338
|
}
|
|
1292
1339
|
}
|
|
1293
1340
|
|
|
1341
|
+
/**
|
|
1342
|
+
* Static JSX that appears before multiple early-return guards is otherwise
|
|
1343
|
+
* cloned into every generated return. Capture it once at its source position
|
|
1344
|
+
* and reuse the reference, matching the interleaved-statement capture path
|
|
1345
|
+
* without moving dynamic render-time expressions across guards.
|
|
1346
|
+
*
|
|
1347
|
+
* @param {any[]} render_nodes
|
|
1348
|
+
* @param {any[]} statements
|
|
1349
|
+
* @param {number} capture_index
|
|
1350
|
+
* @param {TransformContext} transform_context
|
|
1351
|
+
* @returns {number}
|
|
1352
|
+
*/
|
|
1353
|
+
function capture_static_early_return_render_nodes(
|
|
1354
|
+
render_nodes,
|
|
1355
|
+
statements,
|
|
1356
|
+
capture_index,
|
|
1357
|
+
transform_context,
|
|
1358
|
+
) {
|
|
1359
|
+
for (let i = 0; i < render_nodes.length; i += 1) {
|
|
1360
|
+
const node = render_nodes[i];
|
|
1361
|
+
if (!is_static_early_return_capture_node(node, transform_context)) {
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
const { declaration, reference } = captureJsxChild(node, capture_index++);
|
|
1366
|
+
statements.push(declaration);
|
|
1367
|
+
render_nodes[i] = reference;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
return capture_index;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
/**
|
|
1374
|
+
* @param {any} node
|
|
1375
|
+
* @param {TransformContext} transform_context
|
|
1376
|
+
* @returns {boolean}
|
|
1377
|
+
*/
|
|
1378
|
+
function is_static_early_return_capture_node(node, transform_context) {
|
|
1379
|
+
if (node?.type !== 'JSXElement' && node?.type !== 'JSXFragment') {
|
|
1380
|
+
return false;
|
|
1381
|
+
}
|
|
1382
|
+
if (!is_hoist_safe_jsx_node(node)) {
|
|
1383
|
+
return false;
|
|
1384
|
+
}
|
|
1385
|
+
if (
|
|
1386
|
+
transform_context.platform.hooks?.canHoistStaticNode &&
|
|
1387
|
+
!transform_context.platform.hooks.canHoistStaticNode(node, transform_context)
|
|
1388
|
+
) {
|
|
1389
|
+
return false;
|
|
1390
|
+
}
|
|
1391
|
+
return !references_scope_bindings(node, transform_context.available_bindings);
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1294
1394
|
/**
|
|
1295
1395
|
* @param {AST.Program} program
|
|
1296
1396
|
* @returns {AST.Program}
|
|
1297
1397
|
*/
|
|
1298
1398
|
function expand_component_helpers(program) {
|
|
1299
1399
|
program.body = program.body.flatMap((statement) => {
|
|
1300
|
-
const
|
|
1301
|
-
const statics = meta
|
|
1302
|
-
const helpers = meta
|
|
1400
|
+
const metas = get_generated_component_metadata_list(statement);
|
|
1401
|
+
const statics = metas.flatMap((meta) => meta.generated_statics || []);
|
|
1402
|
+
const helpers = metas.flatMap((meta) => meta.generated_helpers || []);
|
|
1303
1403
|
if (statics.length || helpers.length) {
|
|
1304
1404
|
return [...statics, ...helpers, statement];
|
|
1305
1405
|
}
|
|
@@ -1312,30 +1412,63 @@ function expand_component_helpers(program) {
|
|
|
1312
1412
|
|
|
1313
1413
|
/**
|
|
1314
1414
|
* Component hooks may replace a `Component` node with a function declaration,
|
|
1315
|
-
* variable declaration, or export-safe expression.
|
|
1316
|
-
* metadata is carried on whichever replacement node
|
|
1317
|
-
* helper expansion must read metadata from that broader
|
|
1415
|
+
* variable declaration, object literal member, or export-safe expression.
|
|
1416
|
+
* Generated helper/statics metadata is carried on whichever replacement node
|
|
1417
|
+
* the hook returns, so helper expansion must read metadata from that broader
|
|
1418
|
+
* set.
|
|
1318
1419
|
*
|
|
1319
1420
|
* @param {any} node
|
|
1320
|
-
* @returns {{ generated_helpers?: any[], generated_statics?: any[] }
|
|
1421
|
+
* @returns {{ generated_helpers?: any[], generated_statics?: any[] }[]}
|
|
1321
1422
|
*/
|
|
1322
|
-
function
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1423
|
+
function get_generated_component_metadata_list(node) {
|
|
1424
|
+
/** @type {{ generated_helpers?: any[], generated_statics?: any[] }[]} */
|
|
1425
|
+
const metas = [];
|
|
1426
|
+
const seen_nodes = new Set();
|
|
1427
|
+
const seen_metas = new Set();
|
|
1428
|
+
|
|
1429
|
+
/** @param {any} current */
|
|
1430
|
+
const visit = (current) => {
|
|
1431
|
+
if (!current || typeof current !== 'object' || seen_nodes.has(current)) {
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1326
1434
|
|
|
1327
|
-
|
|
1328
|
-
return node.metadata;
|
|
1329
|
-
}
|
|
1435
|
+
seen_nodes.add(current);
|
|
1330
1436
|
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1437
|
+
if (current.metadata?.generated_helpers || current.metadata?.generated_statics) {
|
|
1438
|
+
if (!seen_metas.has(current.metadata)) {
|
|
1439
|
+
seen_metas.add(current.metadata);
|
|
1440
|
+
metas.push(current.metadata);
|
|
1441
|
+
}
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
if (
|
|
1446
|
+
current.type === 'FunctionDeclaration' ||
|
|
1447
|
+
current.type === 'FunctionExpression' ||
|
|
1448
|
+
current.type === 'ArrowFunctionExpression'
|
|
1449
|
+
) {
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
for (const key of Object.keys(current)) {
|
|
1454
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
1455
|
+
continue;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
const value = current[key];
|
|
1459
|
+
if (Array.isArray(value)) {
|
|
1460
|
+
for (const child of value) {
|
|
1461
|
+
visit(child);
|
|
1462
|
+
}
|
|
1463
|
+
} else {
|
|
1464
|
+
visit(value);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
};
|
|
1468
|
+
|
|
1469
|
+
visit(node);
|
|
1337
1470
|
|
|
1338
|
-
return
|
|
1471
|
+
return metas;
|
|
1339
1472
|
}
|
|
1340
1473
|
|
|
1341
1474
|
/**
|
package/src/utils/builders.js
CHANGED
|
@@ -57,18 +57,21 @@ export function assignment_pattern(left, right) {
|
|
|
57
57
|
/**
|
|
58
58
|
* @param {Array<AST.Pattern>} params
|
|
59
59
|
* @param {AST.BlockStatement | AST.Expression} body
|
|
60
|
+
* @param {AST.NodeWithLocation} [loc_info]
|
|
60
61
|
* @returns {AST.ArrowFunctionExpression}
|
|
61
62
|
*/
|
|
62
|
-
export function arrow(params, body, async = false) {
|
|
63
|
-
|
|
63
|
+
export function arrow(params, body, async = false, loc_info) {
|
|
64
|
+
const node = /** @type {AST.ArrowFunctionExpression} */ ({
|
|
64
65
|
type: 'ArrowFunctionExpression',
|
|
65
66
|
params,
|
|
66
67
|
body,
|
|
67
68
|
expression: body.type !== 'BlockStatement',
|
|
68
69
|
generator: false,
|
|
69
70
|
async,
|
|
70
|
-
metadata:
|
|
71
|
-
};
|
|
71
|
+
metadata: { path: [] },
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return set_location(node, loc_info);
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
/**
|
package/src/utils/escaping.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const ATTR_REGEX = /[&"<]/g;
|
|
2
2
|
const CONTENT_REGEX = /[&<]/g;
|
|
3
|
+
const OPEN_TAG_REGEX = /</g;
|
|
4
|
+
const CLOSE_TAG_REGEX = />/g;
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* @template V
|
|
@@ -24,3 +26,12 @@ export function escape(value, is_attr) {
|
|
|
24
26
|
|
|
25
27
|
return escaped + str.substring(last);
|
|
26
28
|
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Escapes characters that can prematurely terminate inline script tags.
|
|
32
|
+
* @param {string} str
|
|
33
|
+
* @returns {string}
|
|
34
|
+
*/
|
|
35
|
+
export function escape_script(str) {
|
|
36
|
+
return str.replace(OPEN_TAG_REGEX, '\\u003c').replace(CLOSE_TAG_REGEX, '\\u003e');
|
|
37
|
+
}
|
package/src/utils/events.js
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -320,12 +320,13 @@ declare module 'estree' {
|
|
|
320
320
|
*/
|
|
321
321
|
interface Component extends AST.BaseNode {
|
|
322
322
|
type: 'Component';
|
|
323
|
-
// null is for anonymous components
|
|
323
|
+
// null is for anonymous components, e.g. `component(props) => {}`
|
|
324
324
|
id: AST.Identifier | null;
|
|
325
325
|
params: AST.Pattern[];
|
|
326
326
|
body: AST.Node[];
|
|
327
327
|
css: CSS.StyleSheet | null;
|
|
328
328
|
metadata: BaseNodeMetaData & {
|
|
329
|
+
arrow?: boolean;
|
|
329
330
|
topScopedClasses?: TopScopedClasses;
|
|
330
331
|
styleClasses?: StyleClasses;
|
|
331
332
|
};
|
|
@@ -1499,15 +1500,20 @@ export type StyleClasses = Map<string, AST.MemberExpression['property']>;
|
|
|
1499
1500
|
/**
|
|
1500
1501
|
* Event handling types
|
|
1501
1502
|
*/
|
|
1502
|
-
export interface
|
|
1503
|
+
export interface AddEventOptions extends ExtendedEventOptions {
|
|
1503
1504
|
customName?: string;
|
|
1504
|
-
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
export interface AddEventObject extends AddEventOptions {
|
|
1508
|
+
handleEvent(object: Event): void;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
export interface ExtendedEventOptions {
|
|
1512
|
+
capture?: boolean;
|
|
1505
1513
|
once?: boolean;
|
|
1506
1514
|
passive?: boolean;
|
|
1507
1515
|
signal?: AbortSignal;
|
|
1508
|
-
|
|
1509
|
-
// from EventListenerObject
|
|
1510
|
-
handleEvent?(object: Event): void;
|
|
1516
|
+
delegated?: boolean;
|
|
1511
1517
|
}
|
|
1512
1518
|
|
|
1513
1519
|
/**
|
package/types/jsx-platform.d.ts
CHANGED