ripple 0.3.8 → 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.
Files changed (79) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +2 -2
  3. package/src/compiler/phases/1-parse/index.js +38 -172
  4. package/src/compiler/phases/2-analyze/index.js +308 -115
  5. package/src/compiler/phases/2-analyze/prune.js +13 -5
  6. package/src/compiler/phases/3-transform/client/index.js +197 -213
  7. package/src/compiler/phases/3-transform/segments.js +0 -7
  8. package/src/compiler/phases/3-transform/server/index.js +77 -170
  9. package/src/compiler/types/acorn.d.ts +1 -1
  10. package/src/compiler/types/estree.d.ts +1 -1
  11. package/src/compiler/types/import.d.ts +0 -2
  12. package/src/compiler/types/index.d.ts +14 -18
  13. package/src/compiler/types/parse.d.ts +3 -9
  14. package/src/compiler/utils.js +154 -21
  15. package/src/runtime/element.js +39 -0
  16. package/src/runtime/index-client.js +2 -13
  17. package/src/runtime/index-server.js +2 -2
  18. package/src/runtime/internal/client/bindings.js +3 -1
  19. package/src/runtime/internal/client/composite.js +11 -6
  20. package/src/runtime/internal/client/events.js +1 -1
  21. package/src/runtime/internal/client/expression.js +218 -0
  22. package/src/runtime/internal/client/head.js +3 -4
  23. package/src/runtime/internal/client/index.js +4 -1
  24. package/src/runtime/internal/client/portal.js +12 -6
  25. package/src/runtime/internal/client/runtime.js +0 -52
  26. package/src/runtime/internal/server/index.js +57 -56
  27. package/tests/client/basic/basic.components.test.ripple +85 -87
  28. package/tests/client/basic/basic.errors.test.ripple +28 -4
  29. package/tests/client/basic/basic.reactivity.test.ripple +10 -155
  30. package/tests/client/basic/basic.rendering.test.ripple +23 -8
  31. package/tests/client/capture-error.js +12 -0
  32. package/tests/client/compiler/compiler.basic.test.ripple +107 -18
  33. package/tests/client/composite/composite.props.test.ripple +5 -9
  34. package/tests/client/composite/composite.reactivity.test.ripple +35 -36
  35. package/tests/client/composite/composite.render.test.ripple +45 -13
  36. package/tests/client/css/global-additional-cases.test.ripple +3 -3
  37. package/tests/client/dynamic-elements.test.ripple +3 -4
  38. package/tests/client/lazy-destructuring.test.ripple +69 -12
  39. package/tests/client/svg.test.ripple +4 -4
  40. package/tests/hydration/basic.test.js +23 -0
  41. package/tests/hydration/compiled/client/basic.js +118 -66
  42. package/tests/hydration/compiled/client/composite.js +90 -37
  43. package/tests/hydration/compiled/client/events.js +18 -18
  44. package/tests/hydration/compiled/client/for.js +62 -62
  45. package/tests/hydration/compiled/client/head.js +10 -10
  46. package/tests/hydration/compiled/client/hmr.js +13 -10
  47. package/tests/hydration/compiled/client/html.js +274 -236
  48. package/tests/hydration/compiled/client/if-children.js +41 -35
  49. package/tests/hydration/compiled/client/if.js +2 -2
  50. package/tests/hydration/compiled/client/mixed-control-flow.js +12 -12
  51. package/tests/hydration/compiled/client/nested-control-flow.js +46 -46
  52. package/tests/hydration/compiled/client/portal.js +8 -8
  53. package/tests/hydration/compiled/client/reactivity.js +14 -14
  54. package/tests/hydration/compiled/client/return.js +2 -2
  55. package/tests/hydration/compiled/client/try.js +4 -4
  56. package/tests/hydration/compiled/server/basic.js +64 -31
  57. package/tests/hydration/compiled/server/composite.js +62 -29
  58. package/tests/hydration/compiled/server/hmr.js +24 -37
  59. package/tests/hydration/compiled/server/html.js +472 -611
  60. package/tests/hydration/compiled/server/if-children.js +77 -103
  61. package/tests/hydration/compiled/server/portal.js +8 -8
  62. package/tests/hydration/components/basic.ripple +15 -5
  63. package/tests/hydration/components/composite.ripple +13 -1
  64. package/tests/hydration/components/hmr.ripple +1 -3
  65. package/tests/hydration/components/html.ripple +13 -35
  66. package/tests/hydration/components/if-children.ripple +4 -8
  67. package/tests/hydration/composite.test.js +11 -0
  68. package/tests/server/basic.attributes.test.ripple +50 -0
  69. package/tests/server/basic.components.test.ripple +22 -28
  70. package/tests/server/basic.test.ripple +12 -0
  71. package/tests/server/compiler.test.ripple +43 -4
  72. package/tests/server/composite.props.test.ripple +5 -9
  73. package/tests/server/dynamic-elements.test.ripple +3 -4
  74. package/tests/server/lazy-destructuring.test.ripple +68 -12
  75. package/tests/server/style-identifier.test.ripple +2 -4
  76. package/tsconfig.typecheck.json +4 -0
  77. package/types/index.d.ts +9 -21
  78. package/tests/client/__snapshots__/tracked-expression.test.ripple.snap +0 -34
  79. package/tests/client/tracked-expression.test.ripple +0 -26
@@ -53,11 +53,13 @@ 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,
59
60
  get_ripple_namespace_call_name,
60
61
  is_ripple_import,
62
+ replace_lazy_param_pattern,
61
63
  ripple_import_requires_block,
62
64
  } from '../../../utils.js';
63
65
  import {
@@ -111,16 +113,12 @@ function visit_function(node, context) {
111
113
  // Replace lazy destructuring params with generated identifiers
112
114
  const transformed_params = node.params.map((param) => {
113
115
  const pattern = param.type === 'AssignmentPattern' ? param.left : param;
114
- if (
115
- (pattern.type === 'ObjectPattern' || pattern.type === 'ArrayPattern') &&
116
- pattern.lazy &&
117
- pattern.metadata?.lazy_id
118
- ) {
119
- const id = b.id(pattern.metadata.lazy_id);
116
+ if (pattern.type === 'ObjectPattern' || pattern.type === 'ArrayPattern') {
117
+ const transformed_pattern = replace_lazy_param_pattern(pattern);
120
118
  if (param.type === 'AssignmentPattern') {
121
- return /** @type {AST.AssignmentPattern} */ ({ ...param, left: id });
119
+ return /** @type {AST.AssignmentPattern} */ ({ ...param, left: transformed_pattern });
122
120
  }
123
- return id;
121
+ return transformed_pattern;
124
122
  }
125
123
  return param;
126
124
  });
@@ -633,7 +631,7 @@ const visitors = {
633
631
 
634
632
  const matched_track_call = !context.state.to_ts ? is_ripple_track_call(callee, context) : null;
635
633
  if (matched_track_call) {
636
- const track_method_name = matched_track_call === 'trackSplit' ? 'track_split' : 'track';
634
+ const track_method_name = 'track';
637
635
 
638
636
  if (callee.type === 'Identifier' && callee.name === 'track') {
639
637
  if (node.arguments.length === 0) {
@@ -803,71 +801,11 @@ const visitors = {
803
801
  return b.call('_$_.with_scope', b.id('__block'), b.thunk(new_node));
804
802
  },
805
803
 
806
- TrackedExpression(node, context) {
807
- if (context.state.to_ts) {
808
- const visited = /** @type {AST.Expression} */ (context.visit(node.argument));
809
- const member = b.member(
810
- visited,
811
- b.literal('#v'),
812
- true,
813
- !is_inside_left_side_assignment(node),
814
- /** @type {AST.NodeWithLocation} */ (node),
815
- );
816
- member.tracked = true;
817
- return member;
818
- }
819
- return b.call('_$_.get', /** @type {AST.Expression} */ (context.visit(node.argument)));
820
- },
821
-
822
804
  MemberExpression(node, context) {
823
805
  if (context.state.metadata?.tracking === false) {
824
806
  context.state.metadata.tracking = true;
825
807
  }
826
808
 
827
- if (
828
- node.tracked ||
829
- ((node.property.type === 'Identifier' || node.property.type === 'Literal') &&
830
- node.property.tracked)
831
- ) {
832
- if (context.state.to_ts) {
833
- // In TypeScript mode, transform @user.@name or @user.@['name'] or @user?.@name
834
- // to user['#v'].name['#v'] or user['#v']['name']['#v'] or user['#v']?.name['#v']
835
- const visited_object = /** @type {AST.Expression} */ (context.visit(node.object));
836
- const visited_property = /** @type {AST.Expression} */ (context.visit(node.property));
837
-
838
- // Build the member access: object.property or object[property]
839
- const member = b.member(
840
- visited_object,
841
- visited_property,
842
- node.computed,
843
- node.optional,
844
- /** @type {AST.NodeWithLocation} */ (node),
845
- );
846
-
847
- // Wrap with ['#v'] access
848
- const member_expanded = b.member(
849
- member,
850
- b.literal('#v'),
851
- true,
852
- !is_inside_left_side_assignment(node),
853
- /** @type {AST.NodeWithLocation} */ (node),
854
- );
855
- member_expanded.tracked = true;
856
- return member_expanded;
857
- } else {
858
- if (!context.state.to_ts) {
859
- return b.call(
860
- '_$_.get_property',
861
- /** @type {AST.Expression} */ (context.visit(node.object)),
862
- node.computed
863
- ? /** @type {AST.Expression} */ (context.visit(node.property))
864
- : b.literal(/** @type {AST.Identifier} */ (node.property).name),
865
- node.optional ? b.true : undefined,
866
- );
867
- }
868
- }
869
- }
870
-
871
809
  if (node.object.type === 'MemberExpression' && node.object.optional) {
872
810
  const metadata = { tracking: false, await: false };
873
811
 
@@ -1640,7 +1578,8 @@ const visitors = {
1640
1578
  child.type === 'Html' ||
1641
1579
  (child.type === 'Element' &&
1642
1580
  (child.id.type !== 'Identifier' || !is_element_dom_element(child))) ||
1643
- (child.type === 'Text' && child.expression.type !== 'Literal'),
1581
+ ((child.type === 'RippleExpression' || child.type === 'Text') &&
1582
+ child.expression.type !== 'Literal'),
1644
1583
  );
1645
1584
 
1646
1585
  if (needs_pop) {
@@ -1674,7 +1613,7 @@ const visitors = {
1674
1613
  const is_spreading = node.attributes.some((attr) => attr.type === 'SpreadAttribute');
1675
1614
  /** @type {(AST.Property | AST.SpreadElement)[]} */
1676
1615
  const props = [];
1677
- /** @type {AST.Expression | AST.BlockStatement | null} */
1616
+ /** @type {AST.Property | null} */
1678
1617
  let children_prop = null;
1679
1618
 
1680
1619
  for (const attr of node.attributes) {
@@ -1696,7 +1635,15 @@ const visitors = {
1696
1635
 
1697
1636
  if (metadata.tracking || attr.name.tracked) {
1698
1637
  if (attr.name.name === 'children') {
1699
- children_prop = b.thunk(property);
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
+ );
1700
1647
  continue;
1701
1648
  }
1702
1649
 
@@ -1708,6 +1655,15 @@ const visitors = {
1708
1655
  ),
1709
1656
  );
1710
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
+
1711
1667
  props.push(b.prop('init', b.key(attr.name.name), property));
1712
1668
  }
1713
1669
  } else {
@@ -1757,60 +1713,62 @@ const visitors = {
1757
1713
  }
1758
1714
  }
1759
1715
 
1760
- const children_filtered = [];
1761
-
1762
- for (const child of node.children) {
1763
- if (child.type === 'Component') {
1764
- // in this case, id cannot be null
1765
- // as these are direct children of the component
1766
- const id = /** @type {AST.Identifier} */ (child.id);
1767
- props.push(
1768
- b.prop(
1769
- 'init',
1770
- id,
1771
- /** @type {AST.Expression} */ (
1772
- visit(child, { ...state, namespace: child_namespace })
1773
- ),
1774
- ),
1775
- );
1776
- } else {
1777
- children_filtered.push(child);
1778
- }
1779
- }
1716
+ const children_filtered = node.children.filter(
1717
+ (child) => child.type !== 'EmptyStatement' && child.type !== 'Component',
1718
+ );
1780
1719
 
1781
1720
  if (children_filtered.length > 0) {
1782
1721
  const component_scope = state.scopes.get(node);
1783
- const children_component = b.component(b.id('children'), [], children_filtered);
1784
-
1785
- const children = /** @type {AST.Expression} */ (
1786
- visit(children_component, {
1787
- ...state,
1788
- ...(apply_parent_css_scope ||
1789
- (is_dynamic_element && node.metadata.scoped && state.component?.css)
1790
- ? {
1791
- applyParentCssScope:
1792
- apply_parent_css_scope ||
1793
- /** @type {AST.CSS.StyleSheet} */ (state.component?.css).hash,
1794
- }
1795
- : {}),
1796
- scope: /** @type {ScopeInterface} */ (component_scope),
1797
- namespace: child_namespace,
1798
- })
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
+ ),
1799
1741
  );
1800
1742
 
1801
1743
  if (children_prop) {
1802
- /** @type {AST.ArrowFunctionExpression} */ (children_prop).body = b.logical(
1803
- '??',
1804
- /** @type {AST.Expression} */ (
1805
- /** @type {AST.ArrowFunctionExpression} */ (children_prop).body
1806
- ),
1807
- children,
1808
- );
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
+ }
1809
1763
  } else {
1810
- props.push(b.prop('init', b.id('children'), children));
1764
+ children_prop = b.prop('init', b.id('children'), children);
1811
1765
  }
1812
1766
  }
1813
1767
 
1768
+ if (children_prop) {
1769
+ props.push(children_prop);
1770
+ }
1771
+
1814
1772
  const metadata = { tracking: false, await: false };
1815
1773
  // We visit, but only to gather metadata
1816
1774
  b.call(/** @type {AST.Expression} */ (visit(node.id, { ...state, metadata })));
@@ -1986,7 +1944,9 @@ const visitors = {
1986
1944
  delete props_param.typeAnnotation;
1987
1945
  if (!props_param.lazy) {
1988
1946
  // Non-lazy destructuring: use the pattern directly as the function param
1989
- props = props_param;
1947
+ props = /** @type {AST.ObjectPattern | AST.ArrayPattern} */ (
1948
+ replace_lazy_param_pattern(props_param)
1949
+ );
1990
1950
  }
1991
1951
  // Lazy destructuring: props stays as __props, bindings resolved via transforms
1992
1952
  }
@@ -2059,56 +2019,6 @@ const visitors = {
2059
2019
  }
2060
2020
  }
2061
2021
 
2062
- if (
2063
- left.type === 'MemberExpression' &&
2064
- (left.tracked || (left.property.type === 'Identifier' && left.property.tracked))
2065
- ) {
2066
- const operator = node.operator;
2067
- const right = node.right;
2068
-
2069
- if (operator !== '=' && context.state.metadata?.tracking === false) {
2070
- context.state.metadata.tracking = true;
2071
- }
2072
-
2073
- return b.call(
2074
- '_$_.set_property',
2075
- /** @type {AST.Expression} */ (
2076
- context.visit(left.object, { ...context.state, metadata: { tracking: false } })
2077
- ),
2078
- left.computed
2079
- ? /** @type {AST.Expression} */ (context.visit(left.property))
2080
- : b.literal(/** @type {AST.Identifier} */ (left.property).name),
2081
- operator === '='
2082
- ? /** @type {AST.Expression} */ (context.visit(right))
2083
- : b.binary(
2084
- operator === '+=' ? '+' : operator === '-=' ? '-' : operator === '*=' ? '*' : '/',
2085
- /** @type {AST.Expression} */ (context.visit(left)),
2086
- /** @type {AST.Expression} */ (context.visit(right)),
2087
- ),
2088
- );
2089
- }
2090
-
2091
- if (left.type === 'Identifier' && left.tracked) {
2092
- const operator = node.operator;
2093
- const right = node.right;
2094
-
2095
- return b.call(
2096
- '_$_.set',
2097
- /** @type {AST.Expression} */ (
2098
- context.visit(left, { ...context.state, metadata: { tracking: null } })
2099
- ),
2100
- operator === '='
2101
- ? /** @type {AST.Expression} */ (context.visit(right))
2102
- : b.binary(
2103
- operator === '+=' ? '+' : operator === '-=' ? '-' : operator === '*=' ? '*' : '/',
2104
- /** @type {AST.Expression} */ (
2105
- context.visit(left, { ...context.state, metadata: { tracking: false } })
2106
- ),
2107
- /** @type {AST.Expression} */ (context.visit(right)),
2108
- ),
2109
- );
2110
- }
2111
-
2112
2022
  return visit_assignment_expression(node, context, build_assignment) ?? context.next();
2113
2023
  },
2114
2024
 
@@ -2126,43 +2036,6 @@ const visitors = {
2126
2036
  }
2127
2037
  }
2128
2038
 
2129
- if (
2130
- argument.type === 'MemberExpression' &&
2131
- (argument.tracked || (argument.property.type === 'Identifier' && argument.property.tracked))
2132
- ) {
2133
- if (context.state.metadata?.tracking === false) {
2134
- context.state.metadata.tracking = true;
2135
- }
2136
-
2137
- return b.call(
2138
- node.prefix ? '_$_.update_pre_property' : '_$_.update_property',
2139
- /** @type {AST.Expression} */
2140
- (context.visit(argument.object, { ...context.state, metadata: { tracking: false } })),
2141
- argument.computed
2142
- ? /** @type {AST.Expression} */ (context.visit(argument.property))
2143
- : b.literal(/** @type {AST.Identifier} */ (argument.property).name),
2144
- node.operator === '--' ? b.literal(-1) : undefined,
2145
- );
2146
- }
2147
-
2148
- if (argument.type === 'Identifier' && argument.tracked) {
2149
- return b.call(
2150
- node.prefix ? '_$_.update_pre' : '_$_.update',
2151
- /** @type {AST.Expression} */
2152
- (context.visit(argument, { ...context.state, metadata: { tracking: null } })),
2153
- node.operator === '--' ? b.literal(-1) : undefined,
2154
- );
2155
- }
2156
-
2157
- if (argument.type === 'TrackedExpression') {
2158
- return b.call(
2159
- node.prefix ? '_$_.update_pre' : '_$_.update',
2160
- /** @type {AST.Expression} */
2161
- (context.visit(argument.argument, { ...context.state, metadata: { tracking: null } })),
2162
- node.operator === '--' ? b.literal(-1) : undefined,
2163
- );
2164
- }
2165
-
2166
2039
  const left = object(/** @type {AST.MemberExpression | AST.Identifier} */ (argument));
2167
2040
  const binding = left && context.state.scope.get(left.name);
2168
2041
  const transformers = left && binding?.transform;
@@ -2786,7 +2659,7 @@ function join_template(items) {
2786
2659
  function transform_ts_child(node, context) {
2787
2660
  const { state, visit } = context;
2788
2661
 
2789
- if (node.type === 'Text') {
2662
+ if (node.type === 'RippleExpression' || node.type === 'Text') {
2790
2663
  state.init?.push(b.stmt(/** @type {AST.Expression} */ (visit(node.expression, { ...state }))));
2791
2664
  } else if (node.type === 'Html') {
2792
2665
  // Do we need to do something special here?
@@ -2953,12 +2826,29 @@ function transform_ts_child(node, context) {
2953
2826
  // The `is_capitalized` was never handled for MemberExpression
2954
2827
  // but it should've been for the `object` part because it starts the tag
2955
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
+
2956
2841
  node.openingElement.metadata = {
2957
2842
  ...node.openingElement.metadata,
2958
2843
  is_capitalized: true,
2959
2844
  };
2960
2845
 
2961
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
+ }
2962
2852
  node.closingElement.metadata = {
2963
2853
  ...node.closingElement.metadata,
2964
2854
  is_capitalized: true,
@@ -3212,6 +3102,7 @@ function transform_ts_child(node, context) {
3212
3102
  function is_template_or_control_flow(node) {
3213
3103
  return (
3214
3104
  node.type === 'Element' ||
3105
+ node.type === 'RippleExpression' ||
3215
3106
  node.type === 'Text' ||
3216
3107
  node.type === 'Html' ||
3217
3108
  node.type === 'TsxCompat' ||
@@ -3310,7 +3201,10 @@ function element_has_dynamic_content(element) {
3310
3201
  ) {
3311
3202
  return true;
3312
3203
  }
3313
- if (child.type === 'Text' && child.expression.type !== 'Literal') {
3204
+ if (
3205
+ (child.type === 'RippleExpression' || child.type === 'Text') &&
3206
+ child.expression.type !== 'Literal'
3207
+ ) {
3314
3208
  return true;
3315
3209
  }
3316
3210
  // Non-DOM element (component)
@@ -3482,6 +3376,14 @@ function transform_children(children, context) {
3482
3376
  (node.type === 'Element' &&
3483
3377
  (node.id.type !== 'Identifier' || !is_element_dom_element(node))),
3484
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
+ )) ||
3485
3387
  normalized.filter(
3486
3388
  (node) => node.type !== 'VariableDeclaration' && node.type !== 'EmptyStatement',
3487
3389
  ).length > 1;
@@ -3496,9 +3398,11 @@ function transform_children(children, context) {
3496
3398
  return b.id(
3497
3399
  node.type == 'Element' && is_element_dom_element(node)
3498
3400
  ? state.scope.generate(/** @type {AST.Identifier} */ (node.id).name)
3499
- : node.type == 'Text'
3500
- ? state.scope.generate('text')
3501
- : state.scope.generate('node'),
3401
+ : node.type == 'RippleExpression'
3402
+ ? state.scope.generate('expression')
3403
+ : node.type == 'Text'
3404
+ ? state.scope.generate('text')
3405
+ : state.scope.generate('node'),
3502
3406
  /** @type {AST.NodeWithLocation} */ (node.type === 'Element' ? node.openingElement : node),
3503
3407
  );
3504
3408
  };
@@ -3652,11 +3556,11 @@ function transform_children(children, context) {
3652
3556
  /** @type {AST.Expression | undefined} */
3653
3557
  let expression = undefined;
3654
3558
  let is_create_text_only = false;
3655
- if (node.type === 'Text' || node.type === 'Html') {
3559
+ if (node.type === 'RippleExpression' || node.type === 'Text' || node.type === 'Html') {
3656
3560
  metadata = { tracking: false, await: false };
3657
3561
  expression = /** @type {AST.Expression} */ (visit(node.expression, { ...state, metadata }));
3658
3562
  is_create_text_only =
3659
- node.type === 'Text' && normalized.length === 1 && expression.type === 'Literal';
3563
+ node.type !== 'Html' && normalized.length === 1 && expression.type === 'Literal';
3660
3564
  }
3661
3565
 
3662
3566
  if (initial === null && root && !is_create_text_only) {
@@ -3752,7 +3656,8 @@ function transform_children(children, context) {
3752
3656
  child.type === 'Html' ||
3753
3657
  (child.type === 'Element' &&
3754
3658
  (child.id.type !== 'Identifier' || !is_element_dom_element(child))) ||
3755
- (child.type === 'Text' && child.expression.type !== 'Literal'),
3659
+ ((child.type === 'RippleExpression' || child.type === 'Text') &&
3660
+ child.expression.type !== 'Literal'),
3756
3661
  );
3757
3662
 
3758
3663
  // Add pop() if we have DOM element children AND the Element visitor didn't already add pop()
@@ -3768,7 +3673,7 @@ function transform_children(children, context) {
3768
3673
  // Components always generate sibling()
3769
3674
  needs_sibling_call = true;
3770
3675
  }
3771
- } else if (next_node.type === 'Text') {
3676
+ } else if (next_node.type === 'RippleExpression' || next_node.type === 'Text') {
3772
3677
  // Only dynamic text generates sibling()
3773
3678
  needs_sibling_call = next_node.expression.type !== 'Literal';
3774
3679
  } else if (
@@ -3814,6 +3719,85 @@ function transform_children(children, context) {
3814
3719
  ),
3815
3720
  ),
3816
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
+ }
3817
3801
  } else if (node.type === 'Text') {
3818
3802
  if (metadata?.tracking) {
3819
3803
  skipped = 0;
@@ -1123,13 +1123,6 @@ export function convert_source_map_to_mappings(
1123
1123
  mapping_data_verify_only,
1124
1124
  );
1125
1125
 
1126
- if (node.tracked) {
1127
- mapping.generatedLengths[0] = mapping.generatedLengths[0] + "['#v']".length;
1128
- if (node.optional) {
1129
- mapping.generatedLengths[0] = mapping.generatedLengths[0] + '.?'.length;
1130
- }
1131
- }
1132
-
1133
1126
  mappings.push(mapping);
1134
1127
  }
1135
1128