@tsrx/core 0.0.6 → 0.0.8
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 +7 -0
- package/src/plugin.js +37 -6
- package/src/transform/jsx-hoist.js +119 -0
- package/src/transform/segments.js +11 -2
- package/types/index.d.ts +11 -0
- package/types/parse.d.ts +0 -1
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -164,6 +164,13 @@ export {
|
|
|
164
164
|
is_capturable_jsx_child as isCapturableJsxChild,
|
|
165
165
|
capture_jsx_child as captureJsxChild,
|
|
166
166
|
} from './transform/jsx-interleave.js';
|
|
167
|
+
export {
|
|
168
|
+
is_static_literal as isStaticLiteral,
|
|
169
|
+
is_hoist_safe_expression as isHoistSafeExpression,
|
|
170
|
+
is_hoist_safe_jsx_child as isHoistSafeJsxChild,
|
|
171
|
+
is_hoist_safe_jsx_attribute as isHoistSafeJsxAttribute,
|
|
172
|
+
is_hoist_safe_jsx_node as isHoistSafeJsxNode,
|
|
173
|
+
} from './transform/jsx-hoist.js';
|
|
167
174
|
|
|
168
175
|
// Analyze
|
|
169
176
|
export { analyze_css as analyzeCss } from './analyze/css-analyze.js';
|
package/src/plugin.js
CHANGED
|
@@ -1431,7 +1431,8 @@ export function TSRXPlugin(config) {
|
|
|
1431
1431
|
/**
|
|
1432
1432
|
* Override jsx_parseElement to intercept expression-level JSX.
|
|
1433
1433
|
* This is called by acorn-jsx's parseExprAtom when it encounters <
|
|
1434
|
-
* in expression position.
|
|
1434
|
+
* in expression position. Bare fragments are treated as shorthand
|
|
1435
|
+
* for <tsx>...</tsx>; other tags must still use <tsx> or <tsx:*>.
|
|
1435
1436
|
* @type {Parse.Parser['jsx_parseElement']}
|
|
1436
1437
|
*/
|
|
1437
1438
|
jsx_parseElement() {
|
|
@@ -1444,6 +1445,7 @@ export function TSRXPlugin(config) {
|
|
|
1444
1445
|
// Check if the element being parsed IS a <tsx> or <tsx:*> tag
|
|
1445
1446
|
// Current token is jsxTagStart, this.end is position after '<'
|
|
1446
1447
|
const tag_name_start = this.end;
|
|
1448
|
+
const is_fragment_tag = this.input.charCodeAt(tag_name_start) === 62;
|
|
1447
1449
|
const char_after_tsx = this.input.charCodeAt(tag_name_start + 3);
|
|
1448
1450
|
const is_tsx_tag =
|
|
1449
1451
|
this.input.startsWith('tsx', tag_name_start) &&
|
|
@@ -1456,8 +1458,9 @@ export function TSRXPlugin(config) {
|
|
|
1456
1458
|
char_after_tsx === 13 || // carriage return
|
|
1457
1459
|
char_after_tsx === 58); // : (tsx:react)
|
|
1458
1460
|
|
|
1459
|
-
if (is_tsx_tag) {
|
|
1460
|
-
// Use Ripple's parseElement to create a Tsx/TsxCompat node
|
|
1461
|
+
if (is_fragment_tag || is_tsx_tag) {
|
|
1462
|
+
// Use Ripple's parseElement to create a Tsx/TsxCompat node.
|
|
1463
|
+
// Bare fragments (<></>) are shorthand for <tsx>...</tsx>.
|
|
1461
1464
|
this.next();
|
|
1462
1465
|
return /** @type {import('estree-jsx').JSXElement} */ (
|
|
1463
1466
|
/** @type {unknown} */ (this.parseElement())
|
|
@@ -1524,6 +1527,8 @@ export function TSRXPlugin(config) {
|
|
|
1524
1527
|
`TSX elements cannot be self-closing. '<tsx />' must have a closing tag '</tsx>'.`,
|
|
1525
1528
|
);
|
|
1526
1529
|
}
|
|
1530
|
+
} else if (is_fragment) {
|
|
1531
|
+
/** @type {AST.Tsx} */ (element).type = 'Tsx';
|
|
1527
1532
|
} else {
|
|
1528
1533
|
element.type = 'Element';
|
|
1529
1534
|
}
|
|
@@ -1575,6 +1580,26 @@ export function TSRXPlugin(config) {
|
|
|
1575
1580
|
this.enterScope(0);
|
|
1576
1581
|
this.parseTemplateBody(/** @type {AST.Element} */ (element).children);
|
|
1577
1582
|
this.exitScope();
|
|
1583
|
+
|
|
1584
|
+
if (element.type === 'Tsx') {
|
|
1585
|
+
this.#path.pop();
|
|
1586
|
+
|
|
1587
|
+
if (!element.unclosed) {
|
|
1588
|
+
const raise_error = () => {
|
|
1589
|
+
this.raise(this.start, `Expected closing tag '</>'`);
|
|
1590
|
+
};
|
|
1591
|
+
|
|
1592
|
+
this.next();
|
|
1593
|
+
if (this.value !== '/') {
|
|
1594
|
+
raise_error();
|
|
1595
|
+
}
|
|
1596
|
+
this.next();
|
|
1597
|
+
if (this.type !== tstt.jsxTagEnd) {
|
|
1598
|
+
raise_error();
|
|
1599
|
+
}
|
|
1600
|
+
this.next();
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1578
1603
|
} else {
|
|
1579
1604
|
if (/** @type {ESTreeJSX.JSXIdentifier} */ (open.name).name === 'script') {
|
|
1580
1605
|
let content = '';
|
|
@@ -1875,7 +1900,11 @@ export function TSRXPlugin(config) {
|
|
|
1875
1900
|
return;
|
|
1876
1901
|
}
|
|
1877
1902
|
|
|
1878
|
-
if (
|
|
1903
|
+
if (!inside_tsx.openingElement.name) {
|
|
1904
|
+
if (this.input.slice(this.pos, this.pos + 2) === '/>') {
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
} else if (this.input.slice(this.pos, this.pos + 4) === '/tsx') {
|
|
1879
1908
|
const after = this.input.charCodeAt(this.pos + 4);
|
|
1880
1909
|
// Make sure it's </tsx> and not </tsx:...>
|
|
1881
1910
|
if (after === 62 /* > */) {
|
|
@@ -2046,7 +2075,7 @@ export function TSRXPlugin(config) {
|
|
|
2046
2075
|
? closingElement.name.namespace.name + ':' + closingElement.name.name.name
|
|
2047
2076
|
: this.getElementName(closingElement.name);
|
|
2048
2077
|
} else if (currentElement.type === 'Tsx') {
|
|
2049
|
-
openingTagName = 'tsx';
|
|
2078
|
+
openingTagName = currentElement.openingElement.name ? 'tsx' : null;
|
|
2050
2079
|
closingTagName =
|
|
2051
2080
|
closingElement.name?.type === 'JSXNamespacedName'
|
|
2052
2081
|
? closingElement.name.namespace.name + ':' + closingElement.name.name.name
|
|
@@ -2081,7 +2110,9 @@ export function TSRXPlugin(config) {
|
|
|
2081
2110
|
elem.type === 'TsxCompat'
|
|
2082
2111
|
? 'tsx:' + elem.kind
|
|
2083
2112
|
: elem.type === 'Tsx'
|
|
2084
|
-
?
|
|
2113
|
+
? elem.openingElement.name
|
|
2114
|
+
? 'tsx'
|
|
2115
|
+
: null
|
|
2085
2116
|
: elem.id
|
|
2086
2117
|
? this.getElementName(elem.id)
|
|
2087
2118
|
: null;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** @import * as ESTreeJSX from 'estree-jsx' */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Predicates that decide whether a JSX subtree can be safely hoisted out of a
|
|
5
|
+
* component body into a module-level `const`. A subtree is hoist-safe only
|
|
6
|
+
* when evaluating it at module-load time produces the same value as
|
|
7
|
+
* evaluating it on every render — i.e. it contains no identifier references,
|
|
8
|
+
* no calls, no spreads, and no other render-time expressions.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {import('estree').Literal} node
|
|
13
|
+
* @returns {boolean}
|
|
14
|
+
*/
|
|
15
|
+
export function is_static_literal(node) {
|
|
16
|
+
return (
|
|
17
|
+
node.value === null ||
|
|
18
|
+
typeof node.value === 'string' ||
|
|
19
|
+
typeof node.value === 'number' ||
|
|
20
|
+
typeof node.value === 'boolean' ||
|
|
21
|
+
typeof node.value === 'bigint'
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {any} node
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
export function is_hoist_safe_expression(node) {
|
|
30
|
+
if (!node || typeof node !== 'object') return false;
|
|
31
|
+
|
|
32
|
+
switch (node.type) {
|
|
33
|
+
case 'Literal':
|
|
34
|
+
return is_static_literal(node);
|
|
35
|
+
case 'TemplateLiteral':
|
|
36
|
+
return node.expressions.length === 0;
|
|
37
|
+
case 'UnaryExpression':
|
|
38
|
+
return node.operator !== 'delete' && is_hoist_safe_expression(node.argument);
|
|
39
|
+
case 'BinaryExpression':
|
|
40
|
+
case 'LogicalExpression':
|
|
41
|
+
return is_hoist_safe_expression(node.left) && is_hoist_safe_expression(node.right);
|
|
42
|
+
case 'ConditionalExpression':
|
|
43
|
+
return (
|
|
44
|
+
is_hoist_safe_expression(node.test) &&
|
|
45
|
+
is_hoist_safe_expression(node.consequent) &&
|
|
46
|
+
is_hoist_safe_expression(node.alternate)
|
|
47
|
+
);
|
|
48
|
+
case 'SequenceExpression':
|
|
49
|
+
return node.expressions.every(is_hoist_safe_expression);
|
|
50
|
+
case 'ParenthesizedExpression':
|
|
51
|
+
return is_hoist_safe_expression(node.expression);
|
|
52
|
+
case 'JSXElement':
|
|
53
|
+
return is_hoist_safe_jsx_node(node);
|
|
54
|
+
case 'JSXFragment':
|
|
55
|
+
return node.children.every(is_hoist_safe_jsx_child);
|
|
56
|
+
default:
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {any} node
|
|
63
|
+
* @returns {boolean}
|
|
64
|
+
*/
|
|
65
|
+
export function is_hoist_safe_jsx_child(node) {
|
|
66
|
+
if (!node || typeof node !== 'object') return false;
|
|
67
|
+
|
|
68
|
+
switch (node.type) {
|
|
69
|
+
case 'JSXText':
|
|
70
|
+
return true;
|
|
71
|
+
case 'JSXElement':
|
|
72
|
+
return is_hoist_safe_jsx_node(node);
|
|
73
|
+
case 'JSXFragment':
|
|
74
|
+
return node.children.every(is_hoist_safe_jsx_child);
|
|
75
|
+
case 'JSXExpressionContainer':
|
|
76
|
+
return (
|
|
77
|
+
node.expression.type !== 'JSXEmptyExpression' && is_hoist_safe_expression(node.expression)
|
|
78
|
+
);
|
|
79
|
+
default:
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {ESTreeJSX.JSXAttribute | ESTreeJSX.JSXSpreadAttribute} attribute
|
|
86
|
+
* @returns {boolean}
|
|
87
|
+
*/
|
|
88
|
+
export function is_hoist_safe_jsx_attribute(attribute) {
|
|
89
|
+
if (attribute.type === 'JSXSpreadAttribute') return false;
|
|
90
|
+
if (attribute.value == null) return true;
|
|
91
|
+
|
|
92
|
+
if (attribute.value.type === 'Literal') {
|
|
93
|
+
return is_static_literal(attribute.value);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (attribute.value.type === 'JSXExpressionContainer') {
|
|
97
|
+
return (
|
|
98
|
+
attribute.value.expression.type !== 'JSXEmptyExpression' &&
|
|
99
|
+
is_hoist_safe_expression(attribute.value.expression)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @param {ESTreeJSX.JSXElement | ESTreeJSX.JSXFragment} node
|
|
108
|
+
* @returns {boolean}
|
|
109
|
+
*/
|
|
110
|
+
export function is_hoist_safe_jsx_node(node) {
|
|
111
|
+
if (node.type === 'JSXFragment') {
|
|
112
|
+
return node.children.every(is_hoist_safe_jsx_child);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
node.openingElement.attributes.every(is_hoist_safe_jsx_attribute) &&
|
|
117
|
+
node.children.every(is_hoist_safe_jsx_child)
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -1618,9 +1618,18 @@ export function convert_source_map_to_mappings(
|
|
|
1618
1618
|
node.type === 'TSTypeParameterDeclaration'
|
|
1619
1619
|
) {
|
|
1620
1620
|
if (node.loc) {
|
|
1621
|
-
|
|
1622
|
-
|
|
1621
|
+
// Generic spans can be emitted by downstream transforms with sparse source-map
|
|
1622
|
+
// coverage around the angle-bracket delimiters. Skip missing whole-node mappings
|
|
1623
|
+
// instead of crashing Volar, and rely on child type-node mappings instead.
|
|
1624
|
+
const mapping = maybe_get_mapping_from_node(
|
|
1625
|
+
node,
|
|
1626
|
+
src_to_gen_map,
|
|
1627
|
+
gen_line_offsets,
|
|
1628
|
+
mapping_data_verify_only,
|
|
1623
1629
|
);
|
|
1630
|
+
if (!(mapping instanceof Error)) {
|
|
1631
|
+
mappings.push(mapping);
|
|
1632
|
+
}
|
|
1624
1633
|
}
|
|
1625
1634
|
// Generic type parameters - visit to collect type variable names
|
|
1626
1635
|
if (node.params) {
|
package/types/index.d.ts
CHANGED
|
@@ -98,6 +98,12 @@ declare module 'estree' {
|
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
interface SimpleCallExpression {
|
|
102
|
+
metadata: BaseNodeMetaData & {
|
|
103
|
+
hash?: string;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
101
107
|
type Accessibility = 'public' | 'protected' | 'private'; // missing in acorn-typescript types
|
|
102
108
|
interface MethodDefinition {
|
|
103
109
|
typeParameters?: TSTypeParameterDeclaration;
|
|
@@ -1335,6 +1341,11 @@ export interface AnalysisState extends BaseState {
|
|
|
1335
1341
|
styleClasses?: StyleClasses;
|
|
1336
1342
|
};
|
|
1337
1343
|
mode: CompileOptions['mode'];
|
|
1344
|
+
// keep this as an object as we destructure
|
|
1345
|
+
module: {
|
|
1346
|
+
// Incremented counter for generating unique track/trackAsync hashes
|
|
1347
|
+
track_id: number;
|
|
1348
|
+
};
|
|
1338
1349
|
}
|
|
1339
1350
|
|
|
1340
1351
|
export interface TransformServerState extends BaseState {
|
package/types/parse.d.ts
CHANGED
|
@@ -1652,7 +1652,6 @@ export namespace Parse {
|
|
|
1652
1652
|
startLoc?: AST.Position,
|
|
1653
1653
|
): ESTreeJSX.JSXOpeningElement;
|
|
1654
1654
|
// it could also be ESTreeJSX.JSXOpeningFragment
|
|
1655
|
-
// but not in our case since we don't use fragments
|
|
1656
1655
|
|
|
1657
1656
|
/**
|
|
1658
1657
|
* Parse JSX closing element at position
|