ripple 0.3.9 → 0.3.11
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/CHANGELOG.md +43 -0
- package/package.json +2 -2
- package/src/compiler/errors.js +1 -1
- package/src/compiler/index.d.ts +3 -1
- package/src/compiler/phases/1-parse/index.js +195 -23
- package/src/compiler/phases/2-analyze/index.js +266 -108
- package/src/compiler/phases/2-analyze/prune.js +13 -5
- package/src/compiler/phases/3-transform/client/index.js +304 -80
- package/src/compiler/phases/3-transform/server/index.js +108 -43
- package/src/compiler/types/index.d.ts +28 -3
- package/src/compiler/types/parse.d.ts +3 -1
- package/src/compiler/utils.js +275 -1
- package/src/runtime/element.js +39 -0
- package/src/runtime/index-client.js +14 -4
- package/src/runtime/internal/client/composite.js +10 -6
- package/src/runtime/internal/client/expression.js +280 -0
- package/src/runtime/internal/client/index.js +4 -0
- package/src/runtime/internal/client/portal.js +12 -6
- package/src/runtime/internal/server/index.js +26 -1
- package/src/utils/builders.js +30 -0
- package/tests/client/basic/__snapshots__/basic.rendering.test.ripple.snap +1 -0
- package/tests/client/basic/basic.components.test.ripple +85 -87
- package/tests/client/basic/basic.errors.test.ripple +4 -8
- package/tests/client/basic/basic.rendering.test.ripple +27 -10
- package/tests/client/capture-error.js +12 -0
- package/tests/client/compiler/compiler.basic.test.ripple +76 -6
- package/tests/client/composite/composite.props.test.ripple +1 -3
- package/tests/client/composite/composite.render.test.ripple +91 -13
- package/tests/client/css/global-additional-cases.test.ripple +3 -3
- package/tests/client/return.test.ripple +101 -0
- package/tests/client/svg.test.ripple +4 -4
- package/tests/client/tsx.test.ripple +486 -0
- package/tests/hydration/basic.test.js +23 -0
- package/tests/hydration/compiled/client/basic.js +111 -75
- package/tests/hydration/compiled/client/composite.js +81 -46
- package/tests/hydration/compiled/client/events.js +18 -63
- package/tests/hydration/compiled/client/for.js +90 -183
- package/tests/hydration/compiled/client/head.js +10 -25
- package/tests/hydration/compiled/client/hmr.js +10 -13
- package/tests/hydration/compiled/client/html.js +251 -380
- package/tests/hydration/compiled/client/if-children.js +35 -45
- package/tests/hydration/compiled/client/if.js +2 -2
- package/tests/hydration/compiled/client/mixed-control-flow.js +24 -72
- package/tests/hydration/compiled/client/nested-control-flow.js +115 -391
- package/tests/hydration/compiled/client/portal.js +8 -20
- package/tests/hydration/compiled/client/reactivity.js +14 -47
- package/tests/hydration/compiled/client/return.js +2 -5
- package/tests/hydration/compiled/client/try.js +4 -4
- package/tests/hydration/compiled/server/basic.js +64 -31
- package/tests/hydration/compiled/server/composite.js +62 -29
- package/tests/hydration/compiled/server/hmr.js +24 -37
- package/tests/hydration/compiled/server/html.js +472 -611
- package/tests/hydration/compiled/server/if-children.js +77 -103
- package/tests/hydration/compiled/server/portal.js +8 -8
- package/tests/hydration/components/basic.ripple +15 -5
- package/tests/hydration/components/composite.ripple +13 -1
- package/tests/hydration/components/hmr.ripple +1 -3
- package/tests/hydration/components/html.ripple +13 -35
- package/tests/hydration/components/if-children.ripple +4 -8
- package/tests/hydration/composite.test.js +11 -0
- package/tests/server/basic.attributes.test.ripple +50 -0
- package/tests/server/basic.components.test.ripple +22 -28
- package/tests/server/basic.test.ripple +12 -0
- package/tests/server/compiler.test.ripple +25 -8
- package/tests/server/composite.props.test.ripple +1 -3
- package/tests/server/style-identifier.test.ripple +2 -4
- package/tests/utils/compiler-compat-config.test.js +38 -0
- package/tests/utils/vite-plugin-config.test.js +113 -0
- package/tsconfig.typecheck.json +2 -1
- package/types/index.d.ts +8 -11
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
determine_namespace_for_children,
|
|
54
54
|
index_to_key,
|
|
55
55
|
is_element_dynamic,
|
|
56
|
+
is_children_template_expression,
|
|
56
57
|
is_inside_left_side_assignment,
|
|
57
58
|
hash,
|
|
58
59
|
flatten_switch_consequent,
|
|
@@ -60,6 +61,7 @@ import {
|
|
|
60
61
|
is_ripple_import,
|
|
61
62
|
replace_lazy_param_pattern,
|
|
62
63
|
ripple_import_requires_block,
|
|
64
|
+
jsx_to_ripple_node,
|
|
63
65
|
} from '../../../utils.js';
|
|
64
66
|
import {
|
|
65
67
|
CSS_HASH_IDENTIFIER,
|
|
@@ -857,6 +859,8 @@ const visitors = {
|
|
|
857
859
|
// Handle standalone lazy destructuring: &[data] = track(0); → const lazy0 = track(0);
|
|
858
860
|
if (
|
|
859
861
|
node.expression.type === 'AssignmentExpression' &&
|
|
862
|
+
(node.expression.left.type === 'ObjectPattern' ||
|
|
863
|
+
node.expression.left.type === 'ArrayPattern') &&
|
|
860
864
|
node.expression.left.lazy &&
|
|
861
865
|
node.expression.left.metadata?.lazy_id
|
|
862
866
|
) {
|
|
@@ -1139,6 +1143,16 @@ const visitors = {
|
|
|
1139
1143
|
TsxCompat(node, context) {
|
|
1140
1144
|
const { state, visit } = context;
|
|
1141
1145
|
|
|
1146
|
+
// to_ts mode: produce a JSX fragment
|
|
1147
|
+
if (state.to_ts) {
|
|
1148
|
+
const children = /** @type {AST.TsxCompat['children']} */ (
|
|
1149
|
+
node.children
|
|
1150
|
+
.map((child) => visit(/** @type {AST.Node} */ (child), state))
|
|
1151
|
+
.filter((child) => child.type !== 'JSXText' || child.value.trim() !== '')
|
|
1152
|
+
);
|
|
1153
|
+
return b.jsx_fragment(children);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1142
1156
|
state.template?.push('<!>');
|
|
1143
1157
|
|
|
1144
1158
|
const normalized_children = node.children.filter((child) => {
|
|
@@ -1176,6 +1190,58 @@ const visitors = {
|
|
|
1176
1190
|
);
|
|
1177
1191
|
},
|
|
1178
1192
|
|
|
1193
|
+
Tsx(node, context) {
|
|
1194
|
+
const { state, visit } = context;
|
|
1195
|
+
|
|
1196
|
+
// to_ts mode: produce a JSX fragment
|
|
1197
|
+
if (state.to_ts) {
|
|
1198
|
+
const children = /** @type {AST.Tsx['children']} */ (
|
|
1199
|
+
node.children
|
|
1200
|
+
.map((child) => visit(/** @type {AST.Node} */ (child), state))
|
|
1201
|
+
.filter((child) => child.type !== 'JSXText' || child.value.trim() !== '')
|
|
1202
|
+
);
|
|
1203
|
+
return b.jsx_fragment(children);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
const children_filtered = node.children
|
|
1207
|
+
.map((child) => jsx_to_ripple_node(/** @type {AST.Node} */ (child)))
|
|
1208
|
+
.flat()
|
|
1209
|
+
.filter(
|
|
1210
|
+
(child) => child != null && child.type !== 'EmptyStatement' && child.type !== 'Component',
|
|
1211
|
+
);
|
|
1212
|
+
|
|
1213
|
+
const children_component = b.component(b.id('render_children'), [], children_filtered);
|
|
1214
|
+
|
|
1215
|
+
const element = b.call(
|
|
1216
|
+
'_$_.ripple_element',
|
|
1217
|
+
/** @type {AST.Expression} */ (
|
|
1218
|
+
visit(children_component, {
|
|
1219
|
+
...state,
|
|
1220
|
+
namespace: state.namespace,
|
|
1221
|
+
is_ripple_element: true,
|
|
1222
|
+
})
|
|
1223
|
+
),
|
|
1224
|
+
);
|
|
1225
|
+
|
|
1226
|
+
// Template body context: push to template and schedule init
|
|
1227
|
+
if (state.flush_node) {
|
|
1228
|
+
state.template?.push('<!>');
|
|
1229
|
+
|
|
1230
|
+
const id = state.flush_node(false);
|
|
1231
|
+
|
|
1232
|
+
const call = b.call('_$_.expression', id, b.thunk(element));
|
|
1233
|
+
state.init?.push(
|
|
1234
|
+
state.namespace !== DEFAULT_NAMESPACE
|
|
1235
|
+
? b.stmt(b.call('_$_.with_ns', b.literal(state.namespace), b.thunk(call)))
|
|
1236
|
+
: b.stmt(call),
|
|
1237
|
+
);
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// Expression context: return the ripple_element directly as an expression value
|
|
1242
|
+
return element;
|
|
1243
|
+
},
|
|
1244
|
+
|
|
1179
1245
|
Element(node, context) {
|
|
1180
1246
|
const { state, visit } = context;
|
|
1181
1247
|
|
|
@@ -1573,11 +1639,13 @@ const visitors = {
|
|
|
1573
1639
|
child.type === 'TryStatement' ||
|
|
1574
1640
|
child.type === 'ForOfStatement' ||
|
|
1575
1641
|
child.type === 'SwitchStatement' ||
|
|
1642
|
+
child.type === 'Tsx' ||
|
|
1576
1643
|
child.type === 'TsxCompat' ||
|
|
1577
1644
|
child.type === 'Html' ||
|
|
1578
1645
|
(child.type === 'Element' &&
|
|
1579
1646
|
(child.id.type !== 'Identifier' || !is_element_dom_element(child))) ||
|
|
1580
|
-
(child.type === '
|
|
1647
|
+
((child.type === 'RippleExpression' || child.type === 'Text') &&
|
|
1648
|
+
child.expression.type !== 'Literal'),
|
|
1581
1649
|
);
|
|
1582
1650
|
|
|
1583
1651
|
if (needs_pop) {
|
|
@@ -1611,7 +1679,7 @@ const visitors = {
|
|
|
1611
1679
|
const is_spreading = node.attributes.some((attr) => attr.type === 'SpreadAttribute');
|
|
1612
1680
|
/** @type {(AST.Property | AST.SpreadElement)[]} */
|
|
1613
1681
|
const props = [];
|
|
1614
|
-
/** @type {AST.
|
|
1682
|
+
/** @type {AST.Property | null} */
|
|
1615
1683
|
let children_prop = null;
|
|
1616
1684
|
|
|
1617
1685
|
for (const attr of node.attributes) {
|
|
@@ -1621,7 +1689,9 @@ const visitors = {
|
|
|
1621
1689
|
let property =
|
|
1622
1690
|
attr.value === null
|
|
1623
1691
|
? b.literal(true)
|
|
1624
|
-
: /** @type {AST.Expression} */ (
|
|
1692
|
+
: /** @type {AST.Expression} */ (
|
|
1693
|
+
visit(attr.value, { ...state, flush_node: null, metadata })
|
|
1694
|
+
);
|
|
1625
1695
|
|
|
1626
1696
|
if (attr.name.name === 'class' && node.metadata.scoped && state.component?.css) {
|
|
1627
1697
|
if (property.type === 'Literal') {
|
|
@@ -1633,7 +1703,15 @@ const visitors = {
|
|
|
1633
1703
|
|
|
1634
1704
|
if (metadata.tracking || attr.name.tracked) {
|
|
1635
1705
|
if (attr.name.name === 'children') {
|
|
1636
|
-
children_prop = b.
|
|
1706
|
+
children_prop = b.prop(
|
|
1707
|
+
'get',
|
|
1708
|
+
b.id('children'),
|
|
1709
|
+
b.function(
|
|
1710
|
+
null,
|
|
1711
|
+
[],
|
|
1712
|
+
b.block([b.return(b.call('_$_.normalize_children', property))]),
|
|
1713
|
+
),
|
|
1714
|
+
);
|
|
1637
1715
|
continue;
|
|
1638
1716
|
}
|
|
1639
1717
|
|
|
@@ -1645,6 +1723,15 @@ const visitors = {
|
|
|
1645
1723
|
),
|
|
1646
1724
|
);
|
|
1647
1725
|
} else {
|
|
1726
|
+
if (attr.name.name === 'children') {
|
|
1727
|
+
children_prop = b.prop(
|
|
1728
|
+
'init',
|
|
1729
|
+
b.id('children'),
|
|
1730
|
+
b.call('_$_.normalize_children', property),
|
|
1731
|
+
);
|
|
1732
|
+
continue;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1648
1735
|
props.push(b.prop('init', b.key(attr.name.name), property));
|
|
1649
1736
|
}
|
|
1650
1737
|
} else {
|
|
@@ -1652,7 +1739,9 @@ const visitors = {
|
|
|
1652
1739
|
b.prop(
|
|
1653
1740
|
'init',
|
|
1654
1741
|
b.key(attr.name.name),
|
|
1655
|
-
/** @type {AST.Expression} */ (
|
|
1742
|
+
/** @type {AST.Expression} */ (
|
|
1743
|
+
visit(/** @type {AST.Node} */ (attr.value), { ...state, flush_node: null })
|
|
1744
|
+
),
|
|
1656
1745
|
),
|
|
1657
1746
|
);
|
|
1658
1747
|
}
|
|
@@ -1660,7 +1749,13 @@ const visitors = {
|
|
|
1660
1749
|
props.push(
|
|
1661
1750
|
b.spread(
|
|
1662
1751
|
/** @type {AST.Expression} */
|
|
1663
|
-
(
|
|
1752
|
+
(
|
|
1753
|
+
visit(attr.argument, {
|
|
1754
|
+
...state,
|
|
1755
|
+
flush_node: null,
|
|
1756
|
+
metadata: { ...state.metadata },
|
|
1757
|
+
})
|
|
1758
|
+
),
|
|
1664
1759
|
),
|
|
1665
1760
|
);
|
|
1666
1761
|
} else if (attr.type === 'RefAttribute') {
|
|
@@ -1671,7 +1766,9 @@ const visitors = {
|
|
|
1671
1766
|
b.prop(
|
|
1672
1767
|
'init',
|
|
1673
1768
|
b.id(ref_id),
|
|
1674
|
-
/** @type {AST.Expression} */ (
|
|
1769
|
+
/** @type {AST.Expression} */ (
|
|
1770
|
+
visit(attr.argument, { ...state, flush_node: null, metadata })
|
|
1771
|
+
),
|
|
1675
1772
|
true,
|
|
1676
1773
|
),
|
|
1677
1774
|
);
|
|
@@ -1694,60 +1791,63 @@ const visitors = {
|
|
|
1694
1791
|
}
|
|
1695
1792
|
}
|
|
1696
1793
|
|
|
1697
|
-
const children_filtered =
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
if (child.type === 'Component') {
|
|
1701
|
-
// in this case, id cannot be null
|
|
1702
|
-
// as these are direct children of the component
|
|
1703
|
-
const id = /** @type {AST.Identifier} */ (child.id);
|
|
1704
|
-
props.push(
|
|
1705
|
-
b.prop(
|
|
1706
|
-
'init',
|
|
1707
|
-
id,
|
|
1708
|
-
/** @type {AST.Expression} */ (
|
|
1709
|
-
visit(child, { ...state, namespace: child_namespace })
|
|
1710
|
-
),
|
|
1711
|
-
),
|
|
1712
|
-
);
|
|
1713
|
-
} else {
|
|
1714
|
-
children_filtered.push(child);
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1794
|
+
const children_filtered = node.children.filter(
|
|
1795
|
+
(child) => child.type !== 'EmptyStatement' && child.type !== 'Component',
|
|
1796
|
+
);
|
|
1717
1797
|
|
|
1718
1798
|
if (children_filtered.length > 0) {
|
|
1719
1799
|
const component_scope = state.scopes.get(node);
|
|
1720
|
-
const children_component = b.component(b.id('
|
|
1721
|
-
|
|
1722
|
-
const children =
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1800
|
+
const children_component = b.component(b.id('render_children'), [], children_filtered);
|
|
1801
|
+
|
|
1802
|
+
const children = b.call(
|
|
1803
|
+
'_$_.ripple_element',
|
|
1804
|
+
/** @type {AST.Expression} */ (
|
|
1805
|
+
visit(children_component, {
|
|
1806
|
+
...state,
|
|
1807
|
+
...(apply_parent_css_scope ||
|
|
1808
|
+
(is_dynamic_element && node.metadata.scoped && state.component?.css)
|
|
1809
|
+
? {
|
|
1810
|
+
applyParentCssScope:
|
|
1811
|
+
apply_parent_css_scope ||
|
|
1812
|
+
/** @type {AST.CSS.StyleSheet} */ (state.component?.css).hash,
|
|
1813
|
+
}
|
|
1814
|
+
: {}),
|
|
1815
|
+
scope: /** @type {ScopeInterface} */ (component_scope),
|
|
1816
|
+
namespace: child_namespace,
|
|
1817
|
+
is_ripple_element: true,
|
|
1818
|
+
})
|
|
1819
|
+
),
|
|
1736
1820
|
);
|
|
1737
1821
|
|
|
1738
1822
|
if (children_prop) {
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1823
|
+
if (children_prop.kind === 'get') {
|
|
1824
|
+
/** @type {AST.ReturnStatement} */ (
|
|
1825
|
+
/** @type {AST.FunctionExpression} */ (children_prop.value).body.body[0]
|
|
1826
|
+
).argument = b.logical(
|
|
1827
|
+
'??',
|
|
1828
|
+
/** @type {AST.Expression} */ (
|
|
1829
|
+
/** @type {AST.ReturnStatement} */ (
|
|
1830
|
+
/** @type {AST.FunctionExpression} */ (children_prop.value).body.body[0]
|
|
1831
|
+
).argument
|
|
1832
|
+
),
|
|
1833
|
+
children,
|
|
1834
|
+
);
|
|
1835
|
+
} else {
|
|
1836
|
+
children_prop.value = b.logical(
|
|
1837
|
+
'??',
|
|
1838
|
+
/** @type {AST.Expression} */ (children_prop.value),
|
|
1839
|
+
children,
|
|
1840
|
+
);
|
|
1841
|
+
}
|
|
1746
1842
|
} else {
|
|
1747
|
-
|
|
1843
|
+
children_prop = b.prop('init', b.id('children'), children);
|
|
1748
1844
|
}
|
|
1749
1845
|
}
|
|
1750
1846
|
|
|
1847
|
+
if (children_prop) {
|
|
1848
|
+
props.push(children_prop);
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1751
1851
|
const metadata = { tracking: false, await: false };
|
|
1752
1852
|
// We visit, but only to gather metadata
|
|
1753
1853
|
b.call(/** @type {AST.Expression} */ (visit(node.id, { ...state, metadata })));
|
|
@@ -1932,30 +2032,46 @@ const visitors = {
|
|
|
1932
2032
|
}
|
|
1933
2033
|
|
|
1934
2034
|
const component_scope = context.state.scopes.get(node) || context.state.scope;
|
|
1935
|
-
const
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
2035
|
+
const is_ripple_element = context.state.is_ripple_element;
|
|
2036
|
+
const is_synthetic_children = node.id?.name === 'render_children';
|
|
2037
|
+
const transformed_body = transform_body(node.body, {
|
|
2038
|
+
...context,
|
|
2039
|
+
state: {
|
|
2040
|
+
...context.state,
|
|
2041
|
+
flush_node: null,
|
|
2042
|
+
component: node,
|
|
2043
|
+
metadata,
|
|
2044
|
+
scope: component_scope,
|
|
2045
|
+
is_ripple_element: false,
|
|
2046
|
+
applyParentCssScope: is_synthetic_children ? context.state.applyParentCssScope : undefined,
|
|
2047
|
+
},
|
|
2048
|
+
});
|
|
2049
|
+
|
|
2050
|
+
// RippleElement render functions don't need push/pop component context
|
|
2051
|
+
// since they inherit context from where they're used
|
|
2052
|
+
const body_statements = is_ripple_element
|
|
2053
|
+
? transformed_body
|
|
2054
|
+
: [
|
|
2055
|
+
b.stmt(b.call('_$_.push_component')),
|
|
2056
|
+
...transformed_body,
|
|
2057
|
+
b.stmt(b.call('_$_.pop_component')),
|
|
2058
|
+
];
|
|
1949
2059
|
|
|
1950
2060
|
if (node.css !== null && node.css) {
|
|
1951
2061
|
context.state.stylesheets.push(node.css);
|
|
1952
2062
|
}
|
|
1953
2063
|
|
|
2064
|
+
// RippleElement render functions use simpler params: [__anchor, __block]
|
|
2065
|
+
// Regular components use: [__anchor, props, __block] or [__anchor, _, __block]
|
|
2066
|
+
const params = is_ripple_element
|
|
2067
|
+
? [b.id('__anchor'), b.id('__block')]
|
|
2068
|
+
: node.params.length > 0
|
|
2069
|
+
? [b.id('__anchor'), props, b.id('__block')]
|
|
2070
|
+
: [b.id('__anchor'), b.id('_'), b.id('__block')];
|
|
2071
|
+
|
|
1954
2072
|
const func = b.function(
|
|
1955
2073
|
node.id,
|
|
1956
|
-
|
|
1957
|
-
? [b.id('__anchor'), props, b.id('__block')]
|
|
1958
|
-
: [b.id('__anchor'), b.id('_'), b.id('__block')],
|
|
2074
|
+
params,
|
|
1959
2075
|
b.block([
|
|
1960
2076
|
...style_statements,
|
|
1961
2077
|
...(prop_statements ?? []),
|
|
@@ -2638,7 +2754,7 @@ function join_template(items) {
|
|
|
2638
2754
|
function transform_ts_child(node, context) {
|
|
2639
2755
|
const { state, visit } = context;
|
|
2640
2756
|
|
|
2641
|
-
if (node.type === 'Text') {
|
|
2757
|
+
if (node.type === 'RippleExpression' || node.type === 'Text') {
|
|
2642
2758
|
state.init?.push(b.stmt(/** @type {AST.Expression} */ (visit(node.expression, { ...state }))));
|
|
2643
2759
|
} else if (node.type === 'Html') {
|
|
2644
2760
|
// Do we need to do something special here?
|
|
@@ -2800,17 +2916,37 @@ function transform_ts_child(node, context) {
|
|
|
2800
2916
|
}
|
|
2801
2917
|
}
|
|
2802
2918
|
|
|
2803
|
-
if (
|
|
2919
|
+
if (
|
|
2920
|
+
/** @type {AST.Node} */ (node.id).type !== 'MemberExpression' &&
|
|
2921
|
+
/** @type {AST.Identifier} */ (node.id).tracked
|
|
2922
|
+
) {
|
|
2804
2923
|
// This is just temporary until we remove capitalization
|
|
2805
2924
|
// The `is_capitalized` was never handled for MemberExpression
|
|
2806
2925
|
// but it should've been for the `object` part because it starts the tag
|
|
2807
2926
|
// But the plan is to only rely on source_name and creating a const for the tag with ['#v']
|
|
2927
|
+
const source_name = /** @type {AST.Identifier} */ (node.id).name;
|
|
2928
|
+
const capitalized_name = source_name.charAt(0).toUpperCase() + source_name.slice(1);
|
|
2929
|
+
|
|
2930
|
+
// node.id and node.openingElement.name are the SAME object (convert_from_jsx mutates
|
|
2931
|
+
// the JSXIdentifier to an Identifier in-place). Capitalize the name directly so that
|
|
2932
|
+
// the generated JSX uses <Tag> (uppercase) matching the capitalized variable declaration,
|
|
2933
|
+
// preventing the TypeScript "declared but never read" false-negative (ts6133).
|
|
2934
|
+
/** @type {AST.Identifier} */ (node.id).name = capitalized_name;
|
|
2935
|
+
if (!node.id.metadata) node.id.metadata = /** @type {any} */ ({});
|
|
2936
|
+
node.id.metadata.is_capitalized = true;
|
|
2937
|
+
node.id.metadata.source_name = source_name;
|
|
2938
|
+
|
|
2808
2939
|
node.openingElement.metadata = {
|
|
2809
2940
|
...node.openingElement.metadata,
|
|
2810
2941
|
is_capitalized: true,
|
|
2811
2942
|
};
|
|
2812
2943
|
|
|
2813
2944
|
if (!node.selfClosing && !node.unclosed) {
|
|
2945
|
+
// closingElement.name is a separate JSXIdentifier (not the same object as node.id)
|
|
2946
|
+
// so we need to capitalize it separately
|
|
2947
|
+
if (node.closingElement.name && 'name' in node.closingElement.name) {
|
|
2948
|
+
/** @type {{ name: string }} */ (node.closingElement.name).name = capitalized_name;
|
|
2949
|
+
}
|
|
2814
2950
|
node.closingElement.metadata = {
|
|
2815
2951
|
...node.closingElement.metadata,
|
|
2816
2952
|
is_capitalized: true,
|
|
@@ -3026,6 +3162,18 @@ function transform_ts_child(node, context) {
|
|
|
3026
3162
|
);
|
|
3027
3163
|
|
|
3028
3164
|
state.init?.push(b.stmt(b.jsx_fragment(children)));
|
|
3165
|
+
} else if (node.type === 'Tsx') {
|
|
3166
|
+
const children = /** @type {AST.Tsx['children']} */ (
|
|
3167
|
+
node.children
|
|
3168
|
+
.map((child) => visit(/** @type {AST.Node} */ (child), state))
|
|
3169
|
+
.filter((child) => child.type !== 'JSXText' || child.value.trim() !== '')
|
|
3170
|
+
);
|
|
3171
|
+
|
|
3172
|
+
const result = b.jsx_fragment(children);
|
|
3173
|
+
if (!state.init) {
|
|
3174
|
+
return result;
|
|
3175
|
+
}
|
|
3176
|
+
state.init.push(b.stmt(result));
|
|
3029
3177
|
} else if (node.type === 'JSXExpressionContainer') {
|
|
3030
3178
|
// JSX comments {/* ... */} are JSXExpressionContainer with JSXEmptyExpression
|
|
3031
3179
|
// These should be preserved in the output as-is for prettier to handle
|
|
@@ -3064,8 +3212,10 @@ function transform_ts_child(node, context) {
|
|
|
3064
3212
|
function is_template_or_control_flow(node) {
|
|
3065
3213
|
return (
|
|
3066
3214
|
node.type === 'Element' ||
|
|
3215
|
+
node.type === 'RippleExpression' ||
|
|
3067
3216
|
node.type === 'Text' ||
|
|
3068
3217
|
node.type === 'Html' ||
|
|
3218
|
+
node.type === 'Tsx' ||
|
|
3069
3219
|
node.type === 'TsxCompat' ||
|
|
3070
3220
|
node.type === 'IfStatement' ||
|
|
3071
3221
|
node.type === 'ForOfStatement' ||
|
|
@@ -3157,12 +3307,16 @@ function element_has_dynamic_content(element) {
|
|
|
3157
3307
|
child.type === 'TryStatement' ||
|
|
3158
3308
|
child.type === 'ForOfStatement' ||
|
|
3159
3309
|
child.type === 'SwitchStatement' ||
|
|
3310
|
+
child.type === 'Tsx' ||
|
|
3160
3311
|
child.type === 'TsxCompat' ||
|
|
3161
3312
|
child.type === 'Html'
|
|
3162
3313
|
) {
|
|
3163
3314
|
return true;
|
|
3164
3315
|
}
|
|
3165
|
-
if (
|
|
3316
|
+
if (
|
|
3317
|
+
(child.type === 'RippleExpression' || child.type === 'Text') &&
|
|
3318
|
+
child.expression.type !== 'Literal'
|
|
3319
|
+
) {
|
|
3166
3320
|
return true;
|
|
3167
3321
|
}
|
|
3168
3322
|
// Non-DOM element (component)
|
|
@@ -3329,11 +3483,29 @@ function transform_children(children, context) {
|
|
|
3329
3483
|
node.type === 'TryStatement' ||
|
|
3330
3484
|
node.type === 'ForOfStatement' ||
|
|
3331
3485
|
node.type === 'SwitchStatement' ||
|
|
3486
|
+
node.type === 'Tsx' ||
|
|
3332
3487
|
node.type === 'TsxCompat' ||
|
|
3333
3488
|
node.type === 'Html' ||
|
|
3334
3489
|
(node.type === 'Element' &&
|
|
3335
3490
|
(node.id.type !== 'Identifier' || !is_element_dom_element(node))),
|
|
3336
3491
|
) ||
|
|
3492
|
+
(normalized.filter(
|
|
3493
|
+
(node) => node.type !== 'VariableDeclaration' && node.type !== 'EmptyStatement',
|
|
3494
|
+
).length === 1 &&
|
|
3495
|
+
normalized.some(
|
|
3496
|
+
(node) =>
|
|
3497
|
+
node.type === 'RippleExpression' &&
|
|
3498
|
+
is_children_template_expression(node.expression, state.scope),
|
|
3499
|
+
)) ||
|
|
3500
|
+
// At root level, non-literal expressions need a fragment template so the
|
|
3501
|
+
// anchor has a parent node. Without a parent, expression()'s .before() call
|
|
3502
|
+
// is a no-op when the value is a RippleElement.
|
|
3503
|
+
(root &&
|
|
3504
|
+
normalized.some(
|
|
3505
|
+
(node) =>
|
|
3506
|
+
node.type === 'RippleExpression' &&
|
|
3507
|
+
/** @type {AST.RippleExpression} */ (node).expression.type !== 'Literal',
|
|
3508
|
+
)) ||
|
|
3337
3509
|
normalized.filter(
|
|
3338
3510
|
(node) => node.type !== 'VariableDeclaration' && node.type !== 'EmptyStatement',
|
|
3339
3511
|
).length > 1;
|
|
@@ -3348,9 +3520,11 @@ function transform_children(children, context) {
|
|
|
3348
3520
|
return b.id(
|
|
3349
3521
|
node.type == 'Element' && is_element_dom_element(node)
|
|
3350
3522
|
? state.scope.generate(/** @type {AST.Identifier} */ (node.id).name)
|
|
3351
|
-
: node.type == '
|
|
3352
|
-
? state.scope.generate('
|
|
3353
|
-
:
|
|
3523
|
+
: node.type == 'RippleExpression'
|
|
3524
|
+
? state.scope.generate('expression')
|
|
3525
|
+
: node.type == 'Text'
|
|
3526
|
+
? state.scope.generate('text')
|
|
3527
|
+
: state.scope.generate('node'),
|
|
3354
3528
|
/** @type {AST.NodeWithLocation} */ (node.type === 'Element' ? node.openingElement : node),
|
|
3355
3529
|
);
|
|
3356
3530
|
};
|
|
@@ -3504,11 +3678,11 @@ function transform_children(children, context) {
|
|
|
3504
3678
|
/** @type {AST.Expression | undefined} */
|
|
3505
3679
|
let expression = undefined;
|
|
3506
3680
|
let is_create_text_only = false;
|
|
3507
|
-
if (node.type === 'Text' || node.type === 'Html') {
|
|
3681
|
+
if (node.type === 'RippleExpression' || node.type === 'Text' || node.type === 'Html') {
|
|
3508
3682
|
metadata = { tracking: false, await: false };
|
|
3509
3683
|
expression = /** @type {AST.Expression} */ (visit(node.expression, { ...state, metadata }));
|
|
3510
3684
|
is_create_text_only =
|
|
3511
|
-
node.type
|
|
3685
|
+
node.type !== 'Html' && normalized.length === 1 && expression.type === 'Literal';
|
|
3512
3686
|
}
|
|
3513
3687
|
|
|
3514
3688
|
if (initial === null && root && !is_create_text_only) {
|
|
@@ -3600,11 +3774,13 @@ function transform_children(children, context) {
|
|
|
3600
3774
|
child.type === 'TryStatement' ||
|
|
3601
3775
|
child.type === 'ForOfStatement' ||
|
|
3602
3776
|
child.type === 'SwitchStatement' ||
|
|
3777
|
+
child.type === 'Tsx' ||
|
|
3603
3778
|
child.type === 'TsxCompat' ||
|
|
3604
3779
|
child.type === 'Html' ||
|
|
3605
3780
|
(child.type === 'Element' &&
|
|
3606
3781
|
(child.id.type !== 'Identifier' || !is_element_dom_element(child))) ||
|
|
3607
|
-
(child.type === '
|
|
3782
|
+
((child.type === 'RippleExpression' || child.type === 'Text') &&
|
|
3783
|
+
child.expression.type !== 'Literal'),
|
|
3608
3784
|
);
|
|
3609
3785
|
|
|
3610
3786
|
// Add pop() if we have DOM element children AND the Element visitor didn't already add pop()
|
|
@@ -3620,7 +3796,7 @@ function transform_children(children, context) {
|
|
|
3620
3796
|
// Components always generate sibling()
|
|
3621
3797
|
needs_sibling_call = true;
|
|
3622
3798
|
}
|
|
3623
|
-
} else if (next_node.type === 'Text') {
|
|
3799
|
+
} else if (next_node.type === 'RippleExpression' || next_node.type === 'Text') {
|
|
3624
3800
|
// Only dynamic text generates sibling()
|
|
3625
3801
|
needs_sibling_call = next_node.expression.type !== 'Literal';
|
|
3626
3802
|
} else if (
|
|
@@ -3629,6 +3805,7 @@ function transform_children(children, context) {
|
|
|
3629
3805
|
next_node.type === 'TryStatement' ||
|
|
3630
3806
|
next_node.type === 'ForOfStatement' ||
|
|
3631
3807
|
next_node.type === 'SwitchStatement' ||
|
|
3808
|
+
next_node.type === 'Tsx' ||
|
|
3632
3809
|
next_node.type === 'TsxCompat'
|
|
3633
3810
|
) {
|
|
3634
3811
|
needs_sibling_call = true;
|
|
@@ -3640,7 +3817,7 @@ function transform_children(children, context) {
|
|
|
3640
3817
|
}
|
|
3641
3818
|
}
|
|
3642
3819
|
}
|
|
3643
|
-
} else if (node.type === 'TsxCompat') {
|
|
3820
|
+
} else if (node.type === 'TsxCompat' || node.type === 'Tsx') {
|
|
3644
3821
|
skipped = 0;
|
|
3645
3822
|
|
|
3646
3823
|
visit(node, {
|
|
@@ -3666,6 +3843,50 @@ function transform_children(children, context) {
|
|
|
3666
3843
|
),
|
|
3667
3844
|
),
|
|
3668
3845
|
});
|
|
3846
|
+
} else if (node.type === 'RippleExpression') {
|
|
3847
|
+
const expr = /** @type {AST.Expression} */ (expression);
|
|
3848
|
+
|
|
3849
|
+
if (expr.type === 'Literal') {
|
|
3850
|
+
if (normalized.length === 1) {
|
|
3851
|
+
skipped++;
|
|
3852
|
+
if (
|
|
3853
|
+
/** @type {NonNullable<TransformClientState['template']>} */ (state.template).length >
|
|
3854
|
+
0
|
|
3855
|
+
) {
|
|
3856
|
+
state.template?.push(escape_html(expr.value));
|
|
3857
|
+
} else {
|
|
3858
|
+
const id = flush_node(true);
|
|
3859
|
+
state.init?.push(b.var(/** @type {AST.Identifier} */ (id), b.call('_$_.text', expr)));
|
|
3860
|
+
state.final?.push(b.stmt(b.call('_$_.append', b.id('__anchor'), id)));
|
|
3861
|
+
}
|
|
3862
|
+
} else {
|
|
3863
|
+
skipped++;
|
|
3864
|
+
state.template?.push(escape_html(expr.value));
|
|
3865
|
+
}
|
|
3866
|
+
} else if (
|
|
3867
|
+
normalized.length === 1 &&
|
|
3868
|
+
!is_children_template_expression(node.expression, state.scope)
|
|
3869
|
+
) {
|
|
3870
|
+
skipped++;
|
|
3871
|
+
state.template?.push(' ');
|
|
3872
|
+
const id = flush_node(true);
|
|
3873
|
+
const call = b.call('_$_.expression', id, b.thunk(expr));
|
|
3874
|
+
state.init?.push(
|
|
3875
|
+
state.namespace !== DEFAULT_NAMESPACE
|
|
3876
|
+
? b.stmt(b.call('_$_.with_ns', b.literal(state.namespace), b.thunk(call)))
|
|
3877
|
+
: b.stmt(call),
|
|
3878
|
+
);
|
|
3879
|
+
} else {
|
|
3880
|
+
skipped = 0;
|
|
3881
|
+
state.template?.push('<!>');
|
|
3882
|
+
const id = flush_node(false);
|
|
3883
|
+
const call = b.call('_$_.expression', id, b.thunk(expr));
|
|
3884
|
+
state.init?.push(
|
|
3885
|
+
state.namespace !== DEFAULT_NAMESPACE
|
|
3886
|
+
? b.stmt(b.call('_$_.with_ns', b.literal(state.namespace), b.thunk(call)))
|
|
3887
|
+
: b.stmt(call),
|
|
3888
|
+
);
|
|
3889
|
+
}
|
|
3669
3890
|
} else if (node.type === 'Text') {
|
|
3670
3891
|
if (metadata?.tracking) {
|
|
3671
3892
|
skipped = 0;
|
|
@@ -4267,7 +4488,10 @@ function create_tsx_with_typescript_support(comments) {
|
|
|
4267
4488
|
// Shorthand object properties require an Identifier value. When the
|
|
4268
4489
|
// transformed value is a tracked MemberExpression (for example
|
|
4269
4490
|
// @value), emit longhand to keep valid output.
|
|
4270
|
-
if (
|
|
4491
|
+
if (
|
|
4492
|
+
node.value.type === 'MemberExpression' &&
|
|
4493
|
+
/** @type {AST.MemberExpression & { tracked?: boolean }} */ (node.value).tracked
|
|
4494
|
+
) {
|
|
4271
4495
|
context.visit(node.key);
|
|
4272
4496
|
context.write(': ');
|
|
4273
4497
|
context.visit(node.value);
|