ripple 0.3.9 → 0.3.10
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 +12 -0
- package/package.json +2 -2
- package/src/compiler/phases/1-parse/index.js +25 -15
- package/src/compiler/phases/2-analyze/index.js +35 -88
- package/src/compiler/phases/2-analyze/prune.js +13 -5
- package/src/compiler/phases/3-transform/client/index.js +188 -56
- package/src/compiler/phases/3-transform/server/index.js +62 -40
- package/src/compiler/types/index.d.ts +9 -1
- package/src/compiler/types/parse.d.ts +2 -0
- package/src/compiler/utils.js +101 -1
- package/src/runtime/element.js +39 -0
- package/src/runtime/internal/client/composite.js +10 -6
- package/src/runtime/internal/client/expression.js +218 -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/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 +23 -8
- 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 +45 -13
- package/tests/client/css/global-additional-cases.test.ripple +3 -3
- package/tests/client/svg.test.ripple +4 -4
- package/tests/hydration/basic.test.js +23 -0
- package/tests/hydration/compiled/client/basic.js +118 -66
- package/tests/hydration/compiled/client/composite.js +90 -37
- package/tests/hydration/compiled/client/events.js +18 -18
- package/tests/hydration/compiled/client/for.js +62 -62
- package/tests/hydration/compiled/client/head.js +10 -10
- package/tests/hydration/compiled/client/hmr.js +13 -10
- package/tests/hydration/compiled/client/html.js +274 -236
- package/tests/hydration/compiled/client/if-children.js +41 -35
- package/tests/hydration/compiled/client/if.js +2 -2
- package/tests/hydration/compiled/client/mixed-control-flow.js +12 -12
- package/tests/hydration/compiled/client/nested-control-flow.js +46 -46
- package/tests/hydration/compiled/client/portal.js +8 -8
- package/tests/hydration/compiled/client/reactivity.js +14 -14
- package/tests/hydration/compiled/client/return.js +2 -2
- 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/types/index.d.ts +9 -2
|
@@ -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,
|
|
@@ -1577,7 +1578,8 @@ const visitors = {
|
|
|
1577
1578
|
child.type === 'Html' ||
|
|
1578
1579
|
(child.type === 'Element' &&
|
|
1579
1580
|
(child.id.type !== 'Identifier' || !is_element_dom_element(child))) ||
|
|
1580
|
-
(child.type === '
|
|
1581
|
+
((child.type === 'RippleExpression' || child.type === 'Text') &&
|
|
1582
|
+
child.expression.type !== 'Literal'),
|
|
1581
1583
|
);
|
|
1582
1584
|
|
|
1583
1585
|
if (needs_pop) {
|
|
@@ -1611,7 +1613,7 @@ const visitors = {
|
|
|
1611
1613
|
const is_spreading = node.attributes.some((attr) => attr.type === 'SpreadAttribute');
|
|
1612
1614
|
/** @type {(AST.Property | AST.SpreadElement)[]} */
|
|
1613
1615
|
const props = [];
|
|
1614
|
-
/** @type {AST.
|
|
1616
|
+
/** @type {AST.Property | null} */
|
|
1615
1617
|
let children_prop = null;
|
|
1616
1618
|
|
|
1617
1619
|
for (const attr of node.attributes) {
|
|
@@ -1633,7 +1635,15 @@ const visitors = {
|
|
|
1633
1635
|
|
|
1634
1636
|
if (metadata.tracking || attr.name.tracked) {
|
|
1635
1637
|
if (attr.name.name === 'children') {
|
|
1636
|
-
children_prop = b.
|
|
1638
|
+
children_prop = b.prop(
|
|
1639
|
+
'get',
|
|
1640
|
+
b.id('children'),
|
|
1641
|
+
b.function(
|
|
1642
|
+
null,
|
|
1643
|
+
[],
|
|
1644
|
+
b.block([b.return(b.call('_$_.normalize_children', property))]),
|
|
1645
|
+
),
|
|
1646
|
+
);
|
|
1637
1647
|
continue;
|
|
1638
1648
|
}
|
|
1639
1649
|
|
|
@@ -1645,6 +1655,15 @@ const visitors = {
|
|
|
1645
1655
|
),
|
|
1646
1656
|
);
|
|
1647
1657
|
} else {
|
|
1658
|
+
if (attr.name.name === 'children') {
|
|
1659
|
+
children_prop = b.prop(
|
|
1660
|
+
'init',
|
|
1661
|
+
b.id('children'),
|
|
1662
|
+
b.call('_$_.normalize_children', property),
|
|
1663
|
+
);
|
|
1664
|
+
continue;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1648
1667
|
props.push(b.prop('init', b.key(attr.name.name), property));
|
|
1649
1668
|
}
|
|
1650
1669
|
} else {
|
|
@@ -1694,60 +1713,62 @@ const visitors = {
|
|
|
1694
1713
|
}
|
|
1695
1714
|
}
|
|
1696
1715
|
|
|
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
|
-
}
|
|
1716
|
+
const children_filtered = node.children.filter(
|
|
1717
|
+
(child) => child.type !== 'EmptyStatement' && child.type !== 'Component',
|
|
1718
|
+
);
|
|
1717
1719
|
|
|
1718
1720
|
if (children_filtered.length > 0) {
|
|
1719
1721
|
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
|
-
|
|
1722
|
+
const children_component = b.component(b.id('render_children'), [], children_filtered);
|
|
1723
|
+
|
|
1724
|
+
const children = b.call(
|
|
1725
|
+
'_$_.ripple_element',
|
|
1726
|
+
/** @type {AST.Expression} */ (
|
|
1727
|
+
visit(children_component, {
|
|
1728
|
+
...state,
|
|
1729
|
+
...(apply_parent_css_scope ||
|
|
1730
|
+
(is_dynamic_element && node.metadata.scoped && state.component?.css)
|
|
1731
|
+
? {
|
|
1732
|
+
applyParentCssScope:
|
|
1733
|
+
apply_parent_css_scope ||
|
|
1734
|
+
/** @type {AST.CSS.StyleSheet} */ (state.component?.css).hash,
|
|
1735
|
+
}
|
|
1736
|
+
: {}),
|
|
1737
|
+
scope: /** @type {ScopeInterface} */ (component_scope),
|
|
1738
|
+
namespace: child_namespace,
|
|
1739
|
+
})
|
|
1740
|
+
),
|
|
1736
1741
|
);
|
|
1737
1742
|
|
|
1738
1743
|
if (children_prop) {
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1744
|
+
if (children_prop.kind === 'get') {
|
|
1745
|
+
/** @type {AST.ReturnStatement} */ (
|
|
1746
|
+
/** @type {AST.FunctionExpression} */ (children_prop.value).body.body[0]
|
|
1747
|
+
).argument = b.logical(
|
|
1748
|
+
'??',
|
|
1749
|
+
/** @type {AST.Expression} */ (
|
|
1750
|
+
/** @type {AST.ReturnStatement} */ (
|
|
1751
|
+
/** @type {AST.FunctionExpression} */ (children_prop.value).body.body[0]
|
|
1752
|
+
).argument
|
|
1753
|
+
),
|
|
1754
|
+
children,
|
|
1755
|
+
);
|
|
1756
|
+
} else {
|
|
1757
|
+
children_prop.value = b.logical(
|
|
1758
|
+
'??',
|
|
1759
|
+
/** @type {AST.Expression} */ (children_prop.value),
|
|
1760
|
+
children,
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1746
1763
|
} else {
|
|
1747
|
-
|
|
1764
|
+
children_prop = b.prop('init', b.id('children'), children);
|
|
1748
1765
|
}
|
|
1749
1766
|
}
|
|
1750
1767
|
|
|
1768
|
+
if (children_prop) {
|
|
1769
|
+
props.push(children_prop);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1751
1772
|
const metadata = { tracking: false, await: false };
|
|
1752
1773
|
// We visit, but only to gather metadata
|
|
1753
1774
|
b.call(/** @type {AST.Expression} */ (visit(node.id, { ...state, metadata })));
|
|
@@ -2638,7 +2659,7 @@ function join_template(items) {
|
|
|
2638
2659
|
function transform_ts_child(node, context) {
|
|
2639
2660
|
const { state, visit } = context;
|
|
2640
2661
|
|
|
2641
|
-
if (node.type === 'Text') {
|
|
2662
|
+
if (node.type === 'RippleExpression' || node.type === 'Text') {
|
|
2642
2663
|
state.init?.push(b.stmt(/** @type {AST.Expression} */ (visit(node.expression, { ...state }))));
|
|
2643
2664
|
} else if (node.type === 'Html') {
|
|
2644
2665
|
// Do we need to do something special here?
|
|
@@ -2805,12 +2826,29 @@ function transform_ts_child(node, context) {
|
|
|
2805
2826
|
// The `is_capitalized` was never handled for MemberExpression
|
|
2806
2827
|
// but it should've been for the `object` part because it starts the tag
|
|
2807
2828
|
// But the plan is to only rely on source_name and creating a const for the tag with ['#v']
|
|
2829
|
+
const source_name = /** @type {AST.Identifier} */ (node.id).name;
|
|
2830
|
+
const capitalized_name = source_name.charAt(0).toUpperCase() + source_name.slice(1);
|
|
2831
|
+
|
|
2832
|
+
// node.id and node.openingElement.name are the SAME object (convert_from_jsx mutates
|
|
2833
|
+
// the JSXIdentifier to an Identifier in-place). Capitalize the name directly so that
|
|
2834
|
+
// the generated JSX uses <Tag> (uppercase) matching the capitalized variable declaration,
|
|
2835
|
+
// preventing the TypeScript "declared but never read" false-negative (ts6133).
|
|
2836
|
+
/** @type {AST.Identifier} */ (node.id).name = capitalized_name;
|
|
2837
|
+
if (!node.id.metadata) node.id.metadata = /** @type {any} */ ({});
|
|
2838
|
+
node.id.metadata.is_capitalized = true;
|
|
2839
|
+
node.id.metadata.source_name = source_name;
|
|
2840
|
+
|
|
2808
2841
|
node.openingElement.metadata = {
|
|
2809
2842
|
...node.openingElement.metadata,
|
|
2810
2843
|
is_capitalized: true,
|
|
2811
2844
|
};
|
|
2812
2845
|
|
|
2813
2846
|
if (!node.selfClosing && !node.unclosed) {
|
|
2847
|
+
// closingElement.name is a separate JSXIdentifier (not the same object as node.id)
|
|
2848
|
+
// so we need to capitalize it separately
|
|
2849
|
+
if (node.closingElement.name && 'name' in node.closingElement.name) {
|
|
2850
|
+
/** @type {{ name: string }} */ (node.closingElement.name).name = capitalized_name;
|
|
2851
|
+
}
|
|
2814
2852
|
node.closingElement.metadata = {
|
|
2815
2853
|
...node.closingElement.metadata,
|
|
2816
2854
|
is_capitalized: true,
|
|
@@ -3064,6 +3102,7 @@ function transform_ts_child(node, context) {
|
|
|
3064
3102
|
function is_template_or_control_flow(node) {
|
|
3065
3103
|
return (
|
|
3066
3104
|
node.type === 'Element' ||
|
|
3105
|
+
node.type === 'RippleExpression' ||
|
|
3067
3106
|
node.type === 'Text' ||
|
|
3068
3107
|
node.type === 'Html' ||
|
|
3069
3108
|
node.type === 'TsxCompat' ||
|
|
@@ -3162,7 +3201,10 @@ function element_has_dynamic_content(element) {
|
|
|
3162
3201
|
) {
|
|
3163
3202
|
return true;
|
|
3164
3203
|
}
|
|
3165
|
-
if (
|
|
3204
|
+
if (
|
|
3205
|
+
(child.type === 'RippleExpression' || child.type === 'Text') &&
|
|
3206
|
+
child.expression.type !== 'Literal'
|
|
3207
|
+
) {
|
|
3166
3208
|
return true;
|
|
3167
3209
|
}
|
|
3168
3210
|
// Non-DOM element (component)
|
|
@@ -3334,6 +3376,14 @@ function transform_children(children, context) {
|
|
|
3334
3376
|
(node.type === 'Element' &&
|
|
3335
3377
|
(node.id.type !== 'Identifier' || !is_element_dom_element(node))),
|
|
3336
3378
|
) ||
|
|
3379
|
+
(normalized.filter(
|
|
3380
|
+
(node) => node.type !== 'VariableDeclaration' && node.type !== 'EmptyStatement',
|
|
3381
|
+
).length === 1 &&
|
|
3382
|
+
normalized.some(
|
|
3383
|
+
(node) =>
|
|
3384
|
+
node.type === 'RippleExpression' &&
|
|
3385
|
+
is_children_template_expression(node.expression, state.scope),
|
|
3386
|
+
)) ||
|
|
3337
3387
|
normalized.filter(
|
|
3338
3388
|
(node) => node.type !== 'VariableDeclaration' && node.type !== 'EmptyStatement',
|
|
3339
3389
|
).length > 1;
|
|
@@ -3348,9 +3398,11 @@ function transform_children(children, context) {
|
|
|
3348
3398
|
return b.id(
|
|
3349
3399
|
node.type == 'Element' && is_element_dom_element(node)
|
|
3350
3400
|
? state.scope.generate(/** @type {AST.Identifier} */ (node.id).name)
|
|
3351
|
-
: node.type == '
|
|
3352
|
-
? state.scope.generate('
|
|
3353
|
-
:
|
|
3401
|
+
: node.type == 'RippleExpression'
|
|
3402
|
+
? state.scope.generate('expression')
|
|
3403
|
+
: node.type == 'Text'
|
|
3404
|
+
? state.scope.generate('text')
|
|
3405
|
+
: state.scope.generate('node'),
|
|
3354
3406
|
/** @type {AST.NodeWithLocation} */ (node.type === 'Element' ? node.openingElement : node),
|
|
3355
3407
|
);
|
|
3356
3408
|
};
|
|
@@ -3504,11 +3556,11 @@ function transform_children(children, context) {
|
|
|
3504
3556
|
/** @type {AST.Expression | undefined} */
|
|
3505
3557
|
let expression = undefined;
|
|
3506
3558
|
let is_create_text_only = false;
|
|
3507
|
-
if (node.type === 'Text' || node.type === 'Html') {
|
|
3559
|
+
if (node.type === 'RippleExpression' || node.type === 'Text' || node.type === 'Html') {
|
|
3508
3560
|
metadata = { tracking: false, await: false };
|
|
3509
3561
|
expression = /** @type {AST.Expression} */ (visit(node.expression, { ...state, metadata }));
|
|
3510
3562
|
is_create_text_only =
|
|
3511
|
-
node.type
|
|
3563
|
+
node.type !== 'Html' && normalized.length === 1 && expression.type === 'Literal';
|
|
3512
3564
|
}
|
|
3513
3565
|
|
|
3514
3566
|
if (initial === null && root && !is_create_text_only) {
|
|
@@ -3604,7 +3656,8 @@ function transform_children(children, context) {
|
|
|
3604
3656
|
child.type === 'Html' ||
|
|
3605
3657
|
(child.type === 'Element' &&
|
|
3606
3658
|
(child.id.type !== 'Identifier' || !is_element_dom_element(child))) ||
|
|
3607
|
-
(child.type === '
|
|
3659
|
+
((child.type === 'RippleExpression' || child.type === 'Text') &&
|
|
3660
|
+
child.expression.type !== 'Literal'),
|
|
3608
3661
|
);
|
|
3609
3662
|
|
|
3610
3663
|
// Add pop() if we have DOM element children AND the Element visitor didn't already add pop()
|
|
@@ -3620,7 +3673,7 @@ function transform_children(children, context) {
|
|
|
3620
3673
|
// Components always generate sibling()
|
|
3621
3674
|
needs_sibling_call = true;
|
|
3622
3675
|
}
|
|
3623
|
-
} else if (next_node.type === 'Text') {
|
|
3676
|
+
} else if (next_node.type === 'RippleExpression' || next_node.type === 'Text') {
|
|
3624
3677
|
// Only dynamic text generates sibling()
|
|
3625
3678
|
needs_sibling_call = next_node.expression.type !== 'Literal';
|
|
3626
3679
|
} else if (
|
|
@@ -3666,6 +3719,85 @@ function transform_children(children, context) {
|
|
|
3666
3719
|
),
|
|
3667
3720
|
),
|
|
3668
3721
|
});
|
|
3722
|
+
} else if (node.type === 'RippleExpression') {
|
|
3723
|
+
const expr = /** @type {AST.Expression} */ (expression);
|
|
3724
|
+
const is_children_expression = is_children_template_expression(
|
|
3725
|
+
node.expression,
|
|
3726
|
+
state.scope,
|
|
3727
|
+
);
|
|
3728
|
+
|
|
3729
|
+
if (expr.type === 'Literal') {
|
|
3730
|
+
if (normalized.length === 1) {
|
|
3731
|
+
skipped++;
|
|
3732
|
+
if (
|
|
3733
|
+
/** @type {NonNullable<TransformClientState['template']>} */ (state.template).length >
|
|
3734
|
+
0
|
|
3735
|
+
) {
|
|
3736
|
+
state.template?.push(escape_html(expr.value));
|
|
3737
|
+
} else {
|
|
3738
|
+
const id = flush_node(true);
|
|
3739
|
+
state.init?.push(b.var(/** @type {AST.Identifier} */ (id), b.call('_$_.text', expr)));
|
|
3740
|
+
state.final?.push(b.stmt(b.call('_$_.append', b.id('__anchor'), id)));
|
|
3741
|
+
}
|
|
3742
|
+
} else {
|
|
3743
|
+
skipped++;
|
|
3744
|
+
state.template?.push(escape_html(expr.value));
|
|
3745
|
+
}
|
|
3746
|
+
} else if (is_children_expression) {
|
|
3747
|
+
skipped = 0;
|
|
3748
|
+
state.template?.push('<!>');
|
|
3749
|
+
const id = flush_node(false);
|
|
3750
|
+
state.update?.push({
|
|
3751
|
+
operation: () => {
|
|
3752
|
+
const call = b.call('_$_.expression', id, b.thunk(expr));
|
|
3753
|
+
return state.namespace !== DEFAULT_NAMESPACE
|
|
3754
|
+
? b.stmt(b.call('_$_.with_ns', b.literal(state.namespace), b.thunk(call)))
|
|
3755
|
+
: b.stmt(call);
|
|
3756
|
+
},
|
|
3757
|
+
});
|
|
3758
|
+
if (metadata?.await) {
|
|
3759
|
+
/** @type {NonNullable<TransformClientState['update']>} */ (state.update).async = true;
|
|
3760
|
+
}
|
|
3761
|
+
} else if (metadata?.tracking) {
|
|
3762
|
+
skipped = 0;
|
|
3763
|
+
state.template?.push(' ');
|
|
3764
|
+
const id = flush_node(true);
|
|
3765
|
+
state.update?.push({
|
|
3766
|
+
operation: (key) => b.stmt(b.call('_$_.set_text', id, key)),
|
|
3767
|
+
expression: expr,
|
|
3768
|
+
identity: node.expression,
|
|
3769
|
+
initial: b.literal(' '),
|
|
3770
|
+
});
|
|
3771
|
+
if (metadata.await) {
|
|
3772
|
+
/** @type {NonNullable<TransformClientState['update']>} */ (state.update).async = true;
|
|
3773
|
+
}
|
|
3774
|
+
} else if (normalized.length === 1) {
|
|
3775
|
+
skipped++;
|
|
3776
|
+
const id = flush_node(true);
|
|
3777
|
+
state.template?.push(' ');
|
|
3778
|
+
state.init?.push(
|
|
3779
|
+
b.stmt(
|
|
3780
|
+
b.assignment(
|
|
3781
|
+
'=',
|
|
3782
|
+
b.member(/** @type {AST.Identifier} */ (id), b.id('nodeValue')),
|
|
3783
|
+
expr,
|
|
3784
|
+
),
|
|
3785
|
+
),
|
|
3786
|
+
);
|
|
3787
|
+
} else {
|
|
3788
|
+
skipped++;
|
|
3789
|
+
state.template?.push(' ');
|
|
3790
|
+
const id = flush_node(true);
|
|
3791
|
+
state.update?.push({
|
|
3792
|
+
operation: (key) => b.stmt(b.call('_$_.set_text', id, key)),
|
|
3793
|
+
expression: expr,
|
|
3794
|
+
identity: node.expression,
|
|
3795
|
+
initial: b.literal(' '),
|
|
3796
|
+
});
|
|
3797
|
+
if (metadata?.await) {
|
|
3798
|
+
/** @type {NonNullable<TransformClientState['update']>} */ (state.update).async = true;
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3669
3801
|
} else if (node.type === 'Text') {
|
|
3670
3802
|
if (metadata?.tracking) {
|
|
3671
3803
|
skipped = 0;
|
|
@@ -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,
|
|
@@ -51,6 +52,7 @@ import { BLOCK_CLOSE, BLOCK_OPEN } from '../../../../constants.js';
|
|
|
51
52
|
function is_template_or_control_flow(node) {
|
|
52
53
|
return (
|
|
53
54
|
node.type === 'Element' ||
|
|
55
|
+
node.type === 'RippleExpression' ||
|
|
54
56
|
node.type === 'Text' ||
|
|
55
57
|
node.type === 'Html' ||
|
|
56
58
|
node.type === 'TsxCompat' ||
|
|
@@ -1082,7 +1084,7 @@ const visitors = {
|
|
|
1082
1084
|
} else {
|
|
1083
1085
|
/** @type {(AST.Property | AST.SpreadElement)[]} */
|
|
1084
1086
|
const props = [];
|
|
1085
|
-
/** @type {AST.
|
|
1087
|
+
/** @type {AST.Property | null} */
|
|
1086
1088
|
let children_prop = null;
|
|
1087
1089
|
|
|
1088
1090
|
const apply_parent_css_scope = state.applyParentCssScope;
|
|
@@ -1102,7 +1104,11 @@ const visitors = {
|
|
|
1102
1104
|
);
|
|
1103
1105
|
|
|
1104
1106
|
if (attr.name.name === 'children') {
|
|
1105
|
-
children_prop =
|
|
1107
|
+
children_prop = b.prop(
|
|
1108
|
+
'init',
|
|
1109
|
+
b.id('children'),
|
|
1110
|
+
b.call('_$_.normalize_children', property),
|
|
1111
|
+
);
|
|
1106
1112
|
continue;
|
|
1107
1113
|
}
|
|
1108
1114
|
|
|
@@ -1119,50 +1125,44 @@ const visitors = {
|
|
|
1119
1125
|
}
|
|
1120
1126
|
}
|
|
1121
1127
|
|
|
1122
|
-
const children_filtered =
|
|
1128
|
+
const children_filtered = node.children.filter(
|
|
1129
|
+
(child) => child.type !== 'EmptyStatement' && child.type !== 'Component',
|
|
1130
|
+
);
|
|
1123
1131
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1132
|
+
if (children_filtered.length > 0) {
|
|
1133
|
+
const component_scope = /** @type {ScopeInterface} */ (context.state.scopes.get(node));
|
|
1134
|
+
const children = b.call(
|
|
1135
|
+
'_$_.ripple_element',
|
|
1136
|
+
/** @type {AST.Expression} */ (
|
|
1137
|
+
visit(b.component(b.id('render_children'), [], children_filtered), {
|
|
1138
|
+
...context.state,
|
|
1139
|
+
...(apply_parent_css_scope ||
|
|
1140
|
+
(is_element_dynamic(node) && node.metadata.scoped && state.component?.css)
|
|
1141
|
+
? {
|
|
1142
|
+
applyParentCssScope:
|
|
1143
|
+
apply_parent_css_scope ||
|
|
1144
|
+
/** @type {AST.CSS.StyleSheet} */ (state.component?.css).hash,
|
|
1145
|
+
}
|
|
1146
|
+
: {}),
|
|
1147
|
+
scope: component_scope,
|
|
1148
|
+
namespace: child_namespace,
|
|
1149
|
+
})
|
|
1150
|
+
),
|
|
1151
|
+
);
|
|
1152
|
+
|
|
1153
|
+
if (children_prop) {
|
|
1154
|
+
children_prop.value = b.logical(
|
|
1155
|
+
'??',
|
|
1156
|
+
/** @type {AST.Expression} */ (children_prop.value),
|
|
1157
|
+
children,
|
|
1137
1158
|
);
|
|
1138
1159
|
} else {
|
|
1139
|
-
|
|
1160
|
+
children_prop = b.prop('init', b.id('children'), children);
|
|
1140
1161
|
}
|
|
1141
1162
|
}
|
|
1142
1163
|
|
|
1143
1164
|
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));
|
|
1165
|
+
props.push(children_prop);
|
|
1166
1166
|
}
|
|
1167
1167
|
|
|
1168
1168
|
// For SSR, determine if we should await based on component metadata
|
|
@@ -1615,9 +1615,10 @@ const visitors = {
|
|
|
1615
1615
|
return context.next();
|
|
1616
1616
|
},
|
|
1617
1617
|
|
|
1618
|
-
|
|
1618
|
+
RippleExpression(node, { visit, state }) {
|
|
1619
1619
|
const metadata = { await: false };
|
|
1620
1620
|
let expression = /** @type {AST.Expression} */ (visit(node.expression, { ...state, metadata }));
|
|
1621
|
+
const is_children_expression = is_children_template_expression(node.expression, state.scope);
|
|
1621
1622
|
|
|
1622
1623
|
if (expression.type === 'Literal') {
|
|
1623
1624
|
state.init?.push(
|
|
@@ -1625,6 +1626,8 @@ const visitors = {
|
|
|
1625
1626
|
b.call(b.member(b.id('__output'), b.id('push')), b.literal(escape(expression.value))),
|
|
1626
1627
|
),
|
|
1627
1628
|
);
|
|
1629
|
+
} else if (is_children_expression) {
|
|
1630
|
+
state.init?.push(b.stmt(b.call('_$_.render_expression', b.id('__output'), expression)));
|
|
1628
1631
|
} else {
|
|
1629
1632
|
state.init?.push(
|
|
1630
1633
|
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.call('_$_.escape', expression))),
|
|
@@ -1632,6 +1635,25 @@ const visitors = {
|
|
|
1632
1635
|
}
|
|
1633
1636
|
},
|
|
1634
1637
|
|
|
1638
|
+
Text(node, context) {
|
|
1639
|
+
const metadata = { await: false };
|
|
1640
|
+
let expression = /** @type {AST.Expression} */ (
|
|
1641
|
+
context.visit(node.expression, { ...context.state, metadata })
|
|
1642
|
+
);
|
|
1643
|
+
|
|
1644
|
+
if (expression.type === 'Literal') {
|
|
1645
|
+
context.state.init?.push(
|
|
1646
|
+
b.stmt(
|
|
1647
|
+
b.call(b.member(b.id('__output'), b.id('push')), b.literal(escape(expression.value))),
|
|
1648
|
+
),
|
|
1649
|
+
);
|
|
1650
|
+
} else {
|
|
1651
|
+
context.state.init?.push(
|
|
1652
|
+
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.call('_$_.escape', expression))),
|
|
1653
|
+
);
|
|
1654
|
+
}
|
|
1655
|
+
},
|
|
1656
|
+
|
|
1635
1657
|
Html(node, { visit, state }) {
|
|
1636
1658
|
const metadata = { await: false };
|
|
1637
1659
|
const expression = /** @type {AST.Expression} */ (
|
|
@@ -134,6 +134,7 @@ declare module 'estree' {
|
|
|
134
134
|
interface NodeMap {
|
|
135
135
|
Component: Component;
|
|
136
136
|
TsxCompat: TsxCompat;
|
|
137
|
+
RippleExpression: RippleExpression;
|
|
137
138
|
Html: Html;
|
|
138
139
|
Element: Element;
|
|
139
140
|
Text: TextNode;
|
|
@@ -282,7 +283,13 @@ declare module 'estree' {
|
|
|
282
283
|
|
|
283
284
|
interface Html extends AST.BaseNode {
|
|
284
285
|
type: 'Html';
|
|
285
|
-
expression: Expression;
|
|
286
|
+
expression: AST.Expression;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export interface RippleExpression extends AST.BaseExpression {
|
|
290
|
+
type: 'RippleExpression';
|
|
291
|
+
expression: AST.Expression;
|
|
292
|
+
loc?: AST.SourceLocation;
|
|
286
293
|
}
|
|
287
294
|
|
|
288
295
|
interface Element extends AST.BaseNode {
|
|
@@ -601,6 +608,7 @@ declare module 'estree-jsx' {
|
|
|
601
608
|
|
|
602
609
|
interface JSXExpressionContainer {
|
|
603
610
|
html?: boolean;
|
|
611
|
+
text?: boolean;
|
|
604
612
|
}
|
|
605
613
|
|
|
606
614
|
interface JSXMemberExpression {
|
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
|