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
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
is_inside_component,
|
|
24
24
|
is_void_element,
|
|
25
25
|
normalize_children,
|
|
26
|
+
is_children_template_expression,
|
|
26
27
|
is_binding_function,
|
|
27
28
|
is_element_dynamic,
|
|
28
29
|
is_ripple_track_call,
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
hash,
|
|
32
33
|
flatten_switch_consequent,
|
|
33
34
|
get_ripple_namespace_call_name,
|
|
35
|
+
jsx_to_ripple_node,
|
|
34
36
|
} from '../../../utils.js';
|
|
35
37
|
import { escape } from '../../../../utils/escaping.js';
|
|
36
38
|
import { is_event_attribute } from '../../../../utils/events.js';
|
|
@@ -51,8 +53,10 @@ import { BLOCK_CLOSE, BLOCK_OPEN } from '../../../../constants.js';
|
|
|
51
53
|
function is_template_or_control_flow(node) {
|
|
52
54
|
return (
|
|
53
55
|
node.type === 'Element' ||
|
|
56
|
+
node.type === 'RippleExpression' ||
|
|
54
57
|
node.type === 'Text' ||
|
|
55
58
|
node.type === 'Html' ||
|
|
59
|
+
node.type === 'Tsx' ||
|
|
56
60
|
node.type === 'TsxCompat' ||
|
|
57
61
|
node.type === 'IfStatement' ||
|
|
58
62
|
node.type === 'ForOfStatement' ||
|
|
@@ -197,7 +201,7 @@ function transform_children(children, context) {
|
|
|
197
201
|
}
|
|
198
202
|
}
|
|
199
203
|
} else {
|
|
200
|
-
visit(node, { ...state, return_flags });
|
|
204
|
+
visit(node, { ...state, return_flags, template_child: true });
|
|
201
205
|
}
|
|
202
206
|
};
|
|
203
207
|
|
|
@@ -400,14 +404,22 @@ const visitors = {
|
|
|
400
404
|
b.stmt(b.call('_$_.push_component')),
|
|
401
405
|
...transform_body(node.body, {
|
|
402
406
|
...context,
|
|
403
|
-
state: {
|
|
407
|
+
state: {
|
|
408
|
+
...context.state,
|
|
409
|
+
component: node,
|
|
410
|
+
metadata,
|
|
411
|
+
applyParentCssScope:
|
|
412
|
+
node.id?.name === 'render_children' ? context.state.applyParentCssScope : undefined,
|
|
413
|
+
},
|
|
404
414
|
}),
|
|
405
415
|
b.stmt(b.call('_$_.pop_component')),
|
|
406
416
|
);
|
|
407
417
|
|
|
408
418
|
let component_fn = b.function(
|
|
409
419
|
node.id,
|
|
410
|
-
node.params.length > 0
|
|
420
|
+
node.params.length > 0
|
|
421
|
+
? [b.id('__output'), /** @type {AST.Pattern} */ (props_param_output)]
|
|
422
|
+
: [b.id('__output')],
|
|
411
423
|
b.block([
|
|
412
424
|
...(metadata.await
|
|
413
425
|
? [b.return(b.call('_$_.async', b.thunk(b.block(body_statements), true)))]
|
|
@@ -805,6 +817,8 @@ const visitors = {
|
|
|
805
817
|
// Handle standalone lazy destructuring: &[data] = track(0); → const lazy0 = track(0);
|
|
806
818
|
if (
|
|
807
819
|
node.expression.type === 'AssignmentExpression' &&
|
|
820
|
+
(node.expression.left.type === 'ObjectPattern' ||
|
|
821
|
+
node.expression.left.type === 'ArrayPattern') &&
|
|
808
822
|
node.expression.left.lazy &&
|
|
809
823
|
node.expression.left.metadata?.lazy_id
|
|
810
824
|
) {
|
|
@@ -1082,7 +1096,7 @@ const visitors = {
|
|
|
1082
1096
|
} else {
|
|
1083
1097
|
/** @type {(AST.Property | AST.SpreadElement)[]} */
|
|
1084
1098
|
const props = [];
|
|
1085
|
-
/** @type {AST.
|
|
1099
|
+
/** @type {AST.Property | null} */
|
|
1086
1100
|
let children_prop = null;
|
|
1087
1101
|
|
|
1088
1102
|
const apply_parent_css_scope = state.applyParentCssScope;
|
|
@@ -1102,7 +1116,11 @@ const visitors = {
|
|
|
1102
1116
|
);
|
|
1103
1117
|
|
|
1104
1118
|
if (attr.name.name === 'children') {
|
|
1105
|
-
children_prop =
|
|
1119
|
+
children_prop = b.prop(
|
|
1120
|
+
'init',
|
|
1121
|
+
b.id('children'),
|
|
1122
|
+
b.call('_$_.normalize_children', property),
|
|
1123
|
+
);
|
|
1106
1124
|
continue;
|
|
1107
1125
|
}
|
|
1108
1126
|
|
|
@@ -1119,50 +1137,44 @@ const visitors = {
|
|
|
1119
1137
|
}
|
|
1120
1138
|
}
|
|
1121
1139
|
|
|
1122
|
-
const children_filtered =
|
|
1140
|
+
const children_filtered = node.children.filter(
|
|
1141
|
+
(child) => child.type !== 'EmptyStatement' && child.type !== 'Component',
|
|
1142
|
+
);
|
|
1123
1143
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1144
|
+
if (children_filtered.length > 0) {
|
|
1145
|
+
const component_scope = /** @type {ScopeInterface} */ (context.state.scopes.get(node));
|
|
1146
|
+
const children = b.call(
|
|
1147
|
+
'_$_.ripple_element',
|
|
1148
|
+
/** @type {AST.Expression} */ (
|
|
1149
|
+
visit(b.component(b.id('render_children'), [], children_filtered), {
|
|
1150
|
+
...context.state,
|
|
1151
|
+
...(apply_parent_css_scope ||
|
|
1152
|
+
(is_element_dynamic(node) && node.metadata.scoped && state.component?.css)
|
|
1153
|
+
? {
|
|
1154
|
+
applyParentCssScope:
|
|
1155
|
+
apply_parent_css_scope ||
|
|
1156
|
+
/** @type {AST.CSS.StyleSheet} */ (state.component?.css).hash,
|
|
1157
|
+
}
|
|
1158
|
+
: {}),
|
|
1159
|
+
scope: component_scope,
|
|
1160
|
+
namespace: child_namespace,
|
|
1161
|
+
})
|
|
1162
|
+
),
|
|
1163
|
+
);
|
|
1164
|
+
|
|
1165
|
+
if (children_prop) {
|
|
1166
|
+
children_prop.value = b.logical(
|
|
1167
|
+
'??',
|
|
1168
|
+
/** @type {AST.Expression} */ (children_prop.value),
|
|
1169
|
+
children,
|
|
1137
1170
|
);
|
|
1138
1171
|
} else {
|
|
1139
|
-
|
|
1172
|
+
children_prop = b.prop('init', b.id('children'), children);
|
|
1140
1173
|
}
|
|
1141
1174
|
}
|
|
1142
1175
|
|
|
1143
1176
|
if (children_prop) {
|
|
1144
|
-
props.push(
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
if (children_filtered.length > 0) {
|
|
1148
|
-
const component_scope = /** @type {ScopeInterface} */ (context.state.scopes.get(node));
|
|
1149
|
-
const children = /** @type {AST.Expression} */ (
|
|
1150
|
-
visit(b.component(b.id('children'), [], children_filtered), {
|
|
1151
|
-
...context.state,
|
|
1152
|
-
...(apply_parent_css_scope ||
|
|
1153
|
-
(is_element_dynamic(node) && node.metadata.scoped && state.component?.css)
|
|
1154
|
-
? {
|
|
1155
|
-
applyParentCssScope:
|
|
1156
|
-
apply_parent_css_scope ||
|
|
1157
|
-
/** @type {AST.CSS.StyleSheet} */ (state.component?.css).hash,
|
|
1158
|
-
}
|
|
1159
|
-
: {}),
|
|
1160
|
-
scope: component_scope,
|
|
1161
|
-
namespace: child_namespace,
|
|
1162
|
-
})
|
|
1163
|
-
);
|
|
1164
|
-
|
|
1165
|
-
props.push(b.prop('init', b.id('children'), children));
|
|
1177
|
+
props.push(children_prop);
|
|
1166
1178
|
}
|
|
1167
1179
|
|
|
1168
1180
|
// For SSR, determine if we should await based on component metadata
|
|
@@ -1615,9 +1627,10 @@ const visitors = {
|
|
|
1615
1627
|
return context.next();
|
|
1616
1628
|
},
|
|
1617
1629
|
|
|
1618
|
-
|
|
1630
|
+
RippleExpression(node, { visit, state }) {
|
|
1619
1631
|
const metadata = { await: false };
|
|
1620
1632
|
let expression = /** @type {AST.Expression} */ (visit(node.expression, { ...state, metadata }));
|
|
1633
|
+
const is_children_expression = is_children_template_expression(node.expression, state.scope);
|
|
1621
1634
|
|
|
1622
1635
|
if (expression.type === 'Literal') {
|
|
1623
1636
|
state.init?.push(
|
|
@@ -1625,6 +1638,8 @@ const visitors = {
|
|
|
1625
1638
|
b.call(b.member(b.id('__output'), b.id('push')), b.literal(escape(expression.value))),
|
|
1626
1639
|
),
|
|
1627
1640
|
);
|
|
1641
|
+
} else if (is_children_expression) {
|
|
1642
|
+
state.init?.push(b.stmt(b.call('_$_.render_expression', b.id('__output'), expression)));
|
|
1628
1643
|
} else {
|
|
1629
1644
|
state.init?.push(
|
|
1630
1645
|
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.call('_$_.escape', expression))),
|
|
@@ -1632,6 +1647,56 @@ const visitors = {
|
|
|
1632
1647
|
}
|
|
1633
1648
|
},
|
|
1634
1649
|
|
|
1650
|
+
Text(node, context) {
|
|
1651
|
+
const metadata = { await: false };
|
|
1652
|
+
let expression = /** @type {AST.Expression} */ (
|
|
1653
|
+
context.visit(node.expression, { ...context.state, metadata })
|
|
1654
|
+
);
|
|
1655
|
+
|
|
1656
|
+
if (expression.type === 'Literal') {
|
|
1657
|
+
context.state.init?.push(
|
|
1658
|
+
b.stmt(
|
|
1659
|
+
b.call(b.member(b.id('__output'), b.id('push')), b.literal(escape(expression.value))),
|
|
1660
|
+
),
|
|
1661
|
+
);
|
|
1662
|
+
} else {
|
|
1663
|
+
context.state.init?.push(
|
|
1664
|
+
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.call('_$_.escape', expression))),
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
},
|
|
1668
|
+
|
|
1669
|
+
Tsx(node, { visit, state }) {
|
|
1670
|
+
const converted_children = node.children
|
|
1671
|
+
.map((child) => jsx_to_ripple_node(/** @type {AST.Node} */ (child)))
|
|
1672
|
+
.flat()
|
|
1673
|
+
.filter((child) => child != null);
|
|
1674
|
+
|
|
1675
|
+
/** @type {AST.Statement[]} */
|
|
1676
|
+
const init = [];
|
|
1677
|
+
transform_children(
|
|
1678
|
+
converted_children,
|
|
1679
|
+
/** @type {TransformServerContext} */ ({
|
|
1680
|
+
visit,
|
|
1681
|
+
state: {
|
|
1682
|
+
...state,
|
|
1683
|
+
init,
|
|
1684
|
+
},
|
|
1685
|
+
}),
|
|
1686
|
+
);
|
|
1687
|
+
|
|
1688
|
+
if (state.template_child) {
|
|
1689
|
+
// Template body: push children statements inline
|
|
1690
|
+
if (init.length > 0) {
|
|
1691
|
+
state.init?.push(b.block(init));
|
|
1692
|
+
}
|
|
1693
|
+
} else {
|
|
1694
|
+
// Expression context: return ripple_element(render_fn)
|
|
1695
|
+
const render_fn = b.function(b.id('render_children'), [b.id('__output')], b.block(init));
|
|
1696
|
+
return b.call('_$_.ripple_element', render_fn);
|
|
1697
|
+
}
|
|
1698
|
+
},
|
|
1699
|
+
|
|
1635
1700
|
Html(node, { visit, state }) {
|
|
1636
1701
|
const metadata = { await: false };
|
|
1637
1702
|
const expression = /** @type {AST.Expression} */ (
|
|
@@ -22,6 +22,7 @@ interface BaseNodeMetaData {
|
|
|
22
22
|
inside_component_top_level?: boolean;
|
|
23
23
|
returns?: AST.ReturnStatement[];
|
|
24
24
|
has_return?: boolean;
|
|
25
|
+
has_throw?: boolean;
|
|
25
26
|
is_reactive?: boolean;
|
|
26
27
|
lone_return?: boolean;
|
|
27
28
|
forceMapping?: boolean;
|
|
@@ -116,7 +117,9 @@ declare module 'estree' {
|
|
|
116
117
|
|
|
117
118
|
// We mark the whole node as marked when member is @[expression]
|
|
118
119
|
// Otherwise, we only mark Identifier nodes
|
|
119
|
-
interface MemberExpression {
|
|
120
|
+
interface MemberExpression {
|
|
121
|
+
tracked?: boolean;
|
|
122
|
+
}
|
|
120
123
|
|
|
121
124
|
interface SimpleLiteral extends AST.LiteralNode {}
|
|
122
125
|
interface RegExpLiteral extends AST.LiteralNode {}
|
|
@@ -133,7 +136,9 @@ declare module 'estree' {
|
|
|
133
136
|
// Include TypeScript node types and Ripple-specific nodes in NodeMap
|
|
134
137
|
interface NodeMap {
|
|
135
138
|
Component: Component;
|
|
139
|
+
Tsx: Tsx;
|
|
136
140
|
TsxCompat: TsxCompat;
|
|
141
|
+
RippleExpression: RippleExpression;
|
|
137
142
|
Html: Html;
|
|
138
143
|
Element: Element;
|
|
139
144
|
Text: TextNode;
|
|
@@ -269,6 +274,16 @@ declare module 'estree' {
|
|
|
269
274
|
typeParameters?: AST.TSTypeParameterDeclaration;
|
|
270
275
|
}
|
|
271
276
|
|
|
277
|
+
interface Tsx extends AST.BaseNode {
|
|
278
|
+
type: 'Tsx';
|
|
279
|
+
attributes: Array<any>;
|
|
280
|
+
children: ESTreeJSX.JSXElement['children'];
|
|
281
|
+
selfClosing?: boolean;
|
|
282
|
+
unclosed?: boolean;
|
|
283
|
+
openingElement: ESTreeJSX.JSXOpeningElement;
|
|
284
|
+
closingElement: ESTreeJSX.JSXClosingElement;
|
|
285
|
+
}
|
|
286
|
+
|
|
272
287
|
interface TsxCompat extends AST.BaseNode {
|
|
273
288
|
type: 'TsxCompat';
|
|
274
289
|
kind: string;
|
|
@@ -282,7 +297,13 @@ declare module 'estree' {
|
|
|
282
297
|
|
|
283
298
|
interface Html extends AST.BaseNode {
|
|
284
299
|
type: 'Html';
|
|
285
|
-
expression: Expression;
|
|
300
|
+
expression: AST.Expression;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export interface RippleExpression extends AST.BaseExpression {
|
|
304
|
+
type: 'RippleExpression';
|
|
305
|
+
expression: AST.Expression;
|
|
306
|
+
loc?: AST.SourceLocation;
|
|
286
307
|
}
|
|
287
308
|
|
|
288
309
|
interface Element extends AST.BaseNode {
|
|
@@ -399,7 +420,7 @@ declare module 'estree' {
|
|
|
399
420
|
|
|
400
421
|
export type RippleStatement = AST.Statement | TSESTree.Statement;
|
|
401
422
|
|
|
402
|
-
export type NodeWithChildren = AST.Element | AST.TsxCompat;
|
|
423
|
+
export type NodeWithChildren = AST.Element | AST.Tsx | AST.TsxCompat;
|
|
403
424
|
|
|
404
425
|
export namespace CSS {
|
|
405
426
|
export interface BaseNode extends AST.NodeWithMaybeComments {
|
|
@@ -601,6 +622,7 @@ declare module 'estree-jsx' {
|
|
|
601
622
|
|
|
602
623
|
interface JSXExpressionContainer {
|
|
603
624
|
html?: boolean;
|
|
625
|
+
text?: boolean;
|
|
604
626
|
}
|
|
605
627
|
|
|
606
628
|
interface JSXMemberExpression {
|
|
@@ -1262,6 +1284,7 @@ export interface AnalysisState extends BaseState {
|
|
|
1262
1284
|
elements?: AST.Element[];
|
|
1263
1285
|
function_depth?: number;
|
|
1264
1286
|
loose?: boolean;
|
|
1287
|
+
configured_compat_kinds?: Set<string>;
|
|
1265
1288
|
metadata: BaseStateMetaData & {
|
|
1266
1289
|
styleClasses?: StyleClasses;
|
|
1267
1290
|
};
|
|
@@ -1282,6 +1305,7 @@ export interface TransformServerState extends BaseState {
|
|
|
1282
1305
|
applyParentCssScope?: AST.CSS.StyleSheet['hash'];
|
|
1283
1306
|
dev?: boolean;
|
|
1284
1307
|
return_flags?: Map<AST.ReturnStatement, { name: string; tracked: boolean }>;
|
|
1308
|
+
template_child?: boolean;
|
|
1285
1309
|
}
|
|
1286
1310
|
|
|
1287
1311
|
type UpdateList = Array<
|
|
@@ -1315,6 +1339,7 @@ export interface TransformClientState extends BaseState {
|
|
|
1315
1339
|
applyParentCssScope?: AST.CSS.StyleSheet['hash'];
|
|
1316
1340
|
skip_children_traversal: boolean;
|
|
1317
1341
|
return_flags?: Map<AST.ReturnStatement, { name: string; tracked: boolean }>;
|
|
1342
|
+
is_ripple_element?: boolean;
|
|
1318
1343
|
}
|
|
1319
1344
|
|
|
1320
1345
|
/** Override zimmerframe types and provide our own */
|
|
@@ -1166,7 +1166,7 @@ export namespace Parse {
|
|
|
1166
1166
|
|
|
1167
1167
|
parseServerBlock(): AST.ServerBlock;
|
|
1168
1168
|
|
|
1169
|
-
parseElement(): AST.Element | AST.TsxCompat;
|
|
1169
|
+
parseElement(): AST.Element | AST.Tsx | AST.TsxCompat;
|
|
1170
1170
|
|
|
1171
1171
|
parseTemplateBody(
|
|
1172
1172
|
body: (AST.Statement | AST.Node | ESTreeJSX.JSXText | ESTreeJSX.JSXElement['children'])[],
|
|
@@ -1195,6 +1195,8 @@ export namespace Parse {
|
|
|
1195
1195
|
topLevel?: boolean,
|
|
1196
1196
|
exports?: AST.ExportSpecifier,
|
|
1197
1197
|
):
|
|
1198
|
+
| AST.RippleExpression
|
|
1199
|
+
| AST.Html
|
|
1198
1200
|
| AST.TextNode
|
|
1199
1201
|
| ESTreeJSX.JSXEmptyExpression
|
|
1200
1202
|
| ESTreeJSX.JSXExpressionContainer
|
package/src/compiler/utils.js
CHANGED
|
@@ -631,7 +631,22 @@ export function normalize_children(children, context) {
|
|
|
631
631
|
const child = normalized[i];
|
|
632
632
|
const prev_child = normalized[i - 1];
|
|
633
633
|
|
|
634
|
-
if (
|
|
634
|
+
if (
|
|
635
|
+
(child.type === 'RippleExpression' || child.type === 'Text') &&
|
|
636
|
+
(prev_child?.type === 'RippleExpression' || prev_child?.type === 'Text')
|
|
637
|
+
) {
|
|
638
|
+
if (
|
|
639
|
+
(child.type === 'RippleExpression' &&
|
|
640
|
+
is_children_template_expression(child.expression, context.state.scope)) ||
|
|
641
|
+
(prev_child.type === 'RippleExpression' &&
|
|
642
|
+
is_children_template_expression(prev_child.expression, context.state.scope))
|
|
643
|
+
) {
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (prev_child.type === 'Text' || child.type === 'Text') {
|
|
648
|
+
prev_child.type = 'Text';
|
|
649
|
+
}
|
|
635
650
|
if (child.expression.type === 'Literal' && prev_child.expression.type === 'Literal') {
|
|
636
651
|
prev_child.expression = b.literal(
|
|
637
652
|
prev_child.expression.value + String(child.expression.value),
|
|
@@ -650,6 +665,91 @@ export function normalize_children(children, context) {
|
|
|
650
665
|
return normalized;
|
|
651
666
|
}
|
|
652
667
|
|
|
668
|
+
/**
|
|
669
|
+
* @param {AST.Expression} expression
|
|
670
|
+
* @returns {AST.Expression}
|
|
671
|
+
*/
|
|
672
|
+
export function unwrap_template_expression(expression) {
|
|
673
|
+
/** @type {AST.Expression} */
|
|
674
|
+
let node = expression;
|
|
675
|
+
|
|
676
|
+
while (true) {
|
|
677
|
+
if (
|
|
678
|
+
node.type === 'ParenthesizedExpression' ||
|
|
679
|
+
node.type === 'TSAsExpression' ||
|
|
680
|
+
node.type === 'TSSatisfiesExpression' ||
|
|
681
|
+
node.type === 'TSNonNullExpression' ||
|
|
682
|
+
node.type === 'TSInstantiationExpression'
|
|
683
|
+
) {
|
|
684
|
+
node = /** @type {AST.Expression} */ (node.expression);
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (node.type === 'ChainExpression') {
|
|
689
|
+
node = /** @type {AST.Expression} */ (node.expression);
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return node;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* @param {AST.Expression} expression
|
|
701
|
+
* @param {ScopeInterface | null | undefined} scope
|
|
702
|
+
* @param {ScopeInterface | null} [component_scope]
|
|
703
|
+
* @returns {boolean}
|
|
704
|
+
*/
|
|
705
|
+
export function is_children_template_expression(expression, scope, component_scope = null) {
|
|
706
|
+
if (scope == null) {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const unwrapped = unwrap_template_expression(expression);
|
|
711
|
+
|
|
712
|
+
if (unwrapped.type === 'MemberExpression') {
|
|
713
|
+
let property_name = null;
|
|
714
|
+
|
|
715
|
+
if (!unwrapped.computed && unwrapped.property.type === 'Identifier') {
|
|
716
|
+
property_name = unwrapped.property.name;
|
|
717
|
+
} else if (
|
|
718
|
+
unwrapped.computed &&
|
|
719
|
+
unwrapped.property.type === 'Literal' &&
|
|
720
|
+
typeof unwrapped.property.value === 'string'
|
|
721
|
+
) {
|
|
722
|
+
property_name = unwrapped.property.value;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (property_name === 'children') {
|
|
726
|
+
const target = unwrap_template_expression(/** @type {AST.Expression} */ (unwrapped.object));
|
|
727
|
+
|
|
728
|
+
if (target.type === 'Identifier') {
|
|
729
|
+
const binding = scope.get(target.name);
|
|
730
|
+
return (
|
|
731
|
+
binding?.declaration_kind === 'param' &&
|
|
732
|
+
(component_scope === null || binding.scope === component_scope)
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (unwrapped.type !== 'Identifier' || unwrapped.name !== 'children') {
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const binding = scope.get(unwrapped.name);
|
|
743
|
+
return (
|
|
744
|
+
(binding?.declaration_kind === 'param' ||
|
|
745
|
+
binding?.kind === 'prop' ||
|
|
746
|
+
binding?.kind === 'prop_fallback' ||
|
|
747
|
+
binding?.kind === 'lazy' ||
|
|
748
|
+
binding?.kind === 'lazy_fallback') &&
|
|
749
|
+
(component_scope === null || binding.scope === component_scope)
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
|
|
653
753
|
/**
|
|
654
754
|
* @param {AST.Node} node
|
|
655
755
|
* @param {AST.Node[]} normalized
|
|
@@ -987,3 +1087,177 @@ export function get_ripple_namespace_call_name(name) {
|
|
|
987
1087
|
export function ripple_import_requires_block(name) {
|
|
988
1088
|
return name !== 'effect' && name !== 'untrack' && name !== 'Context';
|
|
989
1089
|
}
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Converts a JSXMemberExpression to an AST MemberExpression.
|
|
1093
|
+
* e.g., <Foo.Bar.Baz> → MemberExpression(MemberExpression(Foo, Bar), Baz)
|
|
1094
|
+
* @param {import('estree-jsx').JSXMemberExpression} jsx_member
|
|
1095
|
+
* @returns {AST.MemberExpression}
|
|
1096
|
+
*/
|
|
1097
|
+
function jsx_member_expression_to_member_expression(jsx_member) {
|
|
1098
|
+
/** @type {AST.Expression} */
|
|
1099
|
+
let object;
|
|
1100
|
+
|
|
1101
|
+
if (jsx_member.object.type === 'JSXMemberExpression') {
|
|
1102
|
+
// Recursively convert nested member expressions
|
|
1103
|
+
object = jsx_member_expression_to_member_expression(jsx_member.object);
|
|
1104
|
+
} else {
|
|
1105
|
+
// Base case: JSXIdentifier
|
|
1106
|
+
object = /** @type {AST.Identifier} */ ({
|
|
1107
|
+
type: 'Identifier',
|
|
1108
|
+
name: jsx_member.object.name,
|
|
1109
|
+
start: jsx_member.object.start,
|
|
1110
|
+
end: jsx_member.object.end,
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
return /** @type {AST.MemberExpression} */ ({
|
|
1115
|
+
type: 'MemberExpression',
|
|
1116
|
+
object,
|
|
1117
|
+
property: /** @type {AST.Identifier} */ ({
|
|
1118
|
+
type: 'Identifier',
|
|
1119
|
+
name: jsx_member.property.name,
|
|
1120
|
+
start: jsx_member.property.start,
|
|
1121
|
+
end: jsx_member.property.end,
|
|
1122
|
+
}),
|
|
1123
|
+
computed: false,
|
|
1124
|
+
optional: false,
|
|
1125
|
+
start: jsx_member.start,
|
|
1126
|
+
end: jsx_member.end,
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* Converts a JSX AST node (JSXElement, JSXText, etc.) to a Ripple AST node
|
|
1132
|
+
* (Element, Text, RippleExpression) for processing inside `<tsx>` blocks.
|
|
1133
|
+
* @param {AST.Node} node
|
|
1134
|
+
* @returns {AST.Node | AST.Node[] | null}
|
|
1135
|
+
*/
|
|
1136
|
+
export function jsx_to_ripple_node(node) {
|
|
1137
|
+
if (node.type === 'JSXElement') {
|
|
1138
|
+
const opening = node.openingElement;
|
|
1139
|
+
const name = opening.name;
|
|
1140
|
+
|
|
1141
|
+
/** @type {AST.Identifier | AST.MemberExpression} */
|
|
1142
|
+
let id;
|
|
1143
|
+
|
|
1144
|
+
if (name.type === 'JSXIdentifier') {
|
|
1145
|
+
id = /** @type {AST.Identifier} */ ({
|
|
1146
|
+
type: 'Identifier',
|
|
1147
|
+
name: name.name,
|
|
1148
|
+
start: name.start,
|
|
1149
|
+
end: name.end,
|
|
1150
|
+
});
|
|
1151
|
+
} else if (name.type === 'JSXMemberExpression') {
|
|
1152
|
+
// Convert JSXMemberExpression to MemberExpression
|
|
1153
|
+
// e.g., <Foo.Bar.Baz> → MemberExpression(MemberExpression(Foo, Bar), Baz)
|
|
1154
|
+
id = jsx_member_expression_to_member_expression(name);
|
|
1155
|
+
} else if (name.type === 'JSXNamespacedName') {
|
|
1156
|
+
// For JSXNamespacedName like <namespace:element>, create an identifier with the full name
|
|
1157
|
+
id = /** @type {AST.Identifier} */ ({
|
|
1158
|
+
type: 'Identifier',
|
|
1159
|
+
name: name.namespace.name + ':' + name.name.name,
|
|
1160
|
+
start: name.start,
|
|
1161
|
+
end: name.end,
|
|
1162
|
+
});
|
|
1163
|
+
} else {
|
|
1164
|
+
// Fallback - should not reach here
|
|
1165
|
+
id = /** @type {AST.Identifier} */ ({
|
|
1166
|
+
type: 'Identifier',
|
|
1167
|
+
name: 'unknown',
|
|
1168
|
+
start: /** @type {any} */ (name).start,
|
|
1169
|
+
end: /** @type {any} */ (name).end,
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
const attributes = opening.attributes
|
|
1174
|
+
.map((attr) => {
|
|
1175
|
+
if (attr.type === 'JSXAttribute') {
|
|
1176
|
+
const is_dynamic = attr.value && attr.value.type === 'JSXExpressionContainer';
|
|
1177
|
+
return /** @type {AST.Node} */ ({
|
|
1178
|
+
type: 'Attribute',
|
|
1179
|
+
name: {
|
|
1180
|
+
type: 'Identifier',
|
|
1181
|
+
name:
|
|
1182
|
+
attr.name.type === 'JSXIdentifier'
|
|
1183
|
+
? attr.name.name
|
|
1184
|
+
: attr.name.namespace.name + ':' + attr.name.name.name,
|
|
1185
|
+
tracked: is_dynamic,
|
|
1186
|
+
start: attr.name.start,
|
|
1187
|
+
end: attr.name.end,
|
|
1188
|
+
},
|
|
1189
|
+
value: attr.value
|
|
1190
|
+
? attr.value.type === 'JSXExpressionContainer'
|
|
1191
|
+
? attr.value.expression
|
|
1192
|
+
: attr.value
|
|
1193
|
+
: null,
|
|
1194
|
+
shorthand: false,
|
|
1195
|
+
start: attr.start,
|
|
1196
|
+
end: attr.end,
|
|
1197
|
+
});
|
|
1198
|
+
} else if (attr.type === 'JSXSpreadAttribute') {
|
|
1199
|
+
return /** @type {AST.Node} */ ({
|
|
1200
|
+
type: 'SpreadAttribute',
|
|
1201
|
+
argument: attr.argument,
|
|
1202
|
+
start: attr.start,
|
|
1203
|
+
end: attr.end,
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
return null;
|
|
1207
|
+
})
|
|
1208
|
+
.filter(Boolean);
|
|
1209
|
+
|
|
1210
|
+
const children = /** @type {AST.Node[]} */ (
|
|
1211
|
+
/** @type {AST.Node[]} */ (node.children).map(jsx_to_ripple_node).flat().filter(Boolean)
|
|
1212
|
+
);
|
|
1213
|
+
|
|
1214
|
+
return /** @type {AST.Element} */ (
|
|
1215
|
+
/** @type {unknown} */ ({
|
|
1216
|
+
type: 'Element',
|
|
1217
|
+
id,
|
|
1218
|
+
attributes,
|
|
1219
|
+
children,
|
|
1220
|
+
selfClosing: opening.selfClosing,
|
|
1221
|
+
metadata: { scoped: false, path: /** @type {string[]} */ ([]) },
|
|
1222
|
+
start: node.start,
|
|
1223
|
+
end: node.end,
|
|
1224
|
+
})
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
if (node.type === 'JSXText') {
|
|
1229
|
+
if (node.value.trim() === '') return null;
|
|
1230
|
+
return /** @type {AST.Node} */ ({
|
|
1231
|
+
type: 'Text',
|
|
1232
|
+
expression: {
|
|
1233
|
+
type: 'Literal',
|
|
1234
|
+
value: node.value,
|
|
1235
|
+
raw: JSON.stringify(node.value),
|
|
1236
|
+
start: node.start,
|
|
1237
|
+
end: node.end,
|
|
1238
|
+
},
|
|
1239
|
+
metadata: {},
|
|
1240
|
+
start: node.start,
|
|
1241
|
+
end: node.end,
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
if (node.type === 'JSXExpressionContainer') {
|
|
1246
|
+
if (node.expression.type === 'JSXEmptyExpression') return null;
|
|
1247
|
+
return /** @type {AST.Node} */ ({
|
|
1248
|
+
type: 'RippleExpression',
|
|
1249
|
+
expression: node.expression,
|
|
1250
|
+
metadata: {},
|
|
1251
|
+
start: node.start,
|
|
1252
|
+
end: node.end,
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
if (node.type === 'JSXFragment') {
|
|
1257
|
+
return /** @type {AST.Node[]} */ (
|
|
1258
|
+
/** @type {AST.Node[]} */ (node.children).map(jsx_to_ripple_node).flat().filter(Boolean)
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
return node;
|
|
1263
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const RIPPLE_ELEMENT = Symbol.for('ripple.element');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {{
|
|
5
|
+
* render: Function;
|
|
6
|
+
* [RIPPLE_ELEMENT]: true;
|
|
7
|
+
* }} RippleElement
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {Function} render
|
|
12
|
+
* @returns {RippleElement}
|
|
13
|
+
*/
|
|
14
|
+
export function ripple_element(render) {
|
|
15
|
+
return {
|
|
16
|
+
render,
|
|
17
|
+
[RIPPLE_ELEMENT]: true,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {any} value
|
|
23
|
+
* @returns {value is RippleElement}
|
|
24
|
+
*/
|
|
25
|
+
export function is_ripple_element(value) {
|
|
26
|
+
return value != null && value[RIPPLE_ELEMENT] === true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {any} value
|
|
31
|
+
* @returns {any}
|
|
32
|
+
*/
|
|
33
|
+
export function normalize_children(value) {
|
|
34
|
+
if (value == null || is_ripple_element(value) || typeof value !== 'function') {
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return ripple_element(value);
|
|
39
|
+
}
|