ripple 0.3.12 → 0.3.14

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 (217) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/package.json +11 -30
  3. package/src/compiler/types/import.d.ts +0 -12
  4. package/src/helpers.d.ts +2 -0
  5. package/src/runtime/array.js +38 -38
  6. package/src/runtime/create-subscriber.js +2 -2
  7. package/src/runtime/index-client.js +15 -13
  8. package/src/runtime/index-server.js +18 -11
  9. package/src/runtime/internal/client/bindings.js +4 -6
  10. package/src/runtime/internal/client/blocks.js +19 -23
  11. package/src/runtime/internal/client/constants.js +20 -9
  12. package/src/runtime/internal/client/events.js +8 -3
  13. package/src/runtime/internal/client/hmr.js +5 -17
  14. package/src/runtime/internal/client/index.js +14 -4
  15. package/src/runtime/internal/client/runtime.js +436 -173
  16. package/src/runtime/internal/client/try.js +334 -156
  17. package/src/runtime/internal/client/types.d.ts +26 -0
  18. package/src/runtime/internal/server/blocks.js +181 -0
  19. package/src/runtime/internal/server/constants.js +7 -0
  20. package/src/runtime/internal/server/index.js +774 -150
  21. package/src/runtime/internal/server/types.d.ts +35 -0
  22. package/src/runtime/media-query.js +34 -33
  23. package/src/runtime/object.js +7 -10
  24. package/src/runtime/proxy.js +2 -3
  25. package/src/runtime/reactive-value.js +23 -21
  26. package/src/server/index.js +1 -1
  27. package/src/utils/ast.js +1 -1
  28. package/src/utils/async.js +35 -0
  29. package/src/utils/attributes.js +43 -0
  30. package/src/utils/builders.js +5 -3
  31. package/tests/client/__snapshots__/computed-properties.test.rsrx.snap +49 -0
  32. package/tests/client/__snapshots__/for.test.rsrx.snap +319 -0
  33. package/tests/client/__snapshots__/html.test.rsrx.snap +40 -0
  34. package/tests/client/_etc.test.rsrx +7 -0
  35. package/tests/client/array/{array.static.test.ripple → array.static.test.rsrx} +18 -20
  36. package/tests/client/async-suspend.test.rsrx +662 -0
  37. package/tests/client/basic/__snapshots__/basic.attributes.test.rsrx.snap +60 -0
  38. package/tests/client/basic/__snapshots__/basic.rendering.test.rsrx.snap +59 -0
  39. package/tests/client/basic/{basic.errors.test.ripple → basic.errors.test.rsrx} +3 -3
  40. package/tests/client/basic/{basic.styling.test.ripple → basic.styling.test.rsrx} +1 -1
  41. package/tests/client/compiler/__snapshots__/compiler.assignments.test.rsrx.snap +12 -0
  42. package/tests/client/compiler/__snapshots__/compiler.typescript.test.rsrx.snap +46 -0
  43. package/tests/client/compiler/{compiler.assignments.test.ripple → compiler.assignments.test.rsrx} +1 -1
  44. package/tests/client/compiler/{compiler.attributes.test.ripple → compiler.attributes.test.rsrx} +1 -1
  45. package/tests/client/compiler/{compiler.basic.test.ripple → compiler.basic.test.rsrx} +13 -13
  46. package/tests/client/compiler/{compiler.tracked-access.test.ripple → compiler.tracked-access.test.rsrx} +1 -1
  47. package/tests/client/compiler/{compiler.try-in-function.test.ripple → compiler.try-in-function.test.rsrx} +9 -7
  48. package/tests/client/compiler/{compiler.typescript.test.ripple → compiler.typescript.test.rsrx} +1 -1
  49. package/tests/client/composite/__snapshots__/composite.render.test.rsrx.snap +37 -0
  50. package/tests/client/css/{global-additional-cases.test.ripple → global-additional-cases.test.rsrx} +1 -1
  51. package/tests/client/css/{global-advanced-selectors.test.ripple → global-advanced-selectors.test.rsrx} +1 -1
  52. package/tests/client/css/{global-at-rules.test.ripple → global-at-rules.test.rsrx} +1 -1
  53. package/tests/client/css/{global-basic.test.ripple → global-basic.test.rsrx} +1 -1
  54. package/tests/client/css/{global-classes-ids.test.ripple → global-classes-ids.test.rsrx} +1 -1
  55. package/tests/client/css/{global-combinators.test.ripple → global-combinators.test.rsrx} +1 -1
  56. package/tests/client/css/{global-complex-nesting.test.ripple → global-complex-nesting.test.rsrx} +1 -1
  57. package/tests/client/css/{global-edge-cases.test.ripple → global-edge-cases.test.rsrx} +1 -1
  58. package/tests/client/css/{global-keyframes.test.ripple → global-keyframes.test.rsrx} +1 -1
  59. package/tests/client/css/{global-nested.test.ripple → global-nested.test.rsrx} +1 -1
  60. package/tests/client/css/{global-pseudo.test.ripple → global-pseudo.test.rsrx} +1 -1
  61. package/tests/client/css/{global-scoping.test.ripple → global-scoping.test.rsrx} +1 -1
  62. package/tests/client/css/{style-identifier.test.ripple → style-identifier.test.rsrx} +1 -1
  63. package/tests/client/{function-overload.test.ripple → function-overload.test.rsrx} +1 -1
  64. package/tests/client/{return.test.ripple → return.test.rsrx} +1 -1
  65. package/tests/client/try.test.rsrx +1702 -0
  66. package/tests/hydration/build-components.js +6 -4
  67. package/tests/hydration/compiled/client/head.js +11 -11
  68. package/tests/hydration/compiled/client/mixed-control-flow.js +55 -70
  69. package/tests/hydration/compiled/client/nested-control-flow.js +72 -88
  70. package/tests/hydration/compiled/client/try.js +42 -54
  71. package/tests/hydration/compiled/server/basic.js +491 -369
  72. package/tests/hydration/compiled/server/composite.js +153 -128
  73. package/tests/hydration/compiled/server/events.js +166 -145
  74. package/tests/hydration/compiled/server/for.js +821 -677
  75. package/tests/hydration/compiled/server/head.js +200 -165
  76. package/tests/hydration/compiled/server/hmr.js +62 -54
  77. package/tests/hydration/compiled/server/html-in-template.js +64 -55
  78. package/tests/hydration/compiled/server/html.js +1477 -1360
  79. package/tests/hydration/compiled/server/if-children.js +448 -408
  80. package/tests/hydration/compiled/server/if.js +204 -171
  81. package/tests/hydration/compiled/server/mixed-control-flow.js +237 -195
  82. package/tests/hydration/compiled/server/nested-control-flow.js +533 -467
  83. package/tests/hydration/compiled/server/portal.js +94 -107
  84. package/tests/hydration/compiled/server/reactivity.js +87 -64
  85. package/tests/hydration/compiled/server/return.js +1424 -1174
  86. package/tests/hydration/compiled/server/switch.js +268 -238
  87. package/tests/hydration/compiled/server/try.js +98 -87
  88. package/tests/hydration/components/{mixed-control-flow.ripple → mixed-control-flow.rsrx} +2 -2
  89. package/tests/hydration/components/{try.ripple → try.rsrx} +4 -2
  90. package/tests/hydration/mixed-control-flow.test.js +14 -0
  91. package/tests/hydration/nested-control-flow.test.js +50 -48
  92. package/tests/hydration/try.test.js +25 -0
  93. package/tests/server/__snapshots__/compiler.test.ripple.snap +0 -32
  94. package/tests/server/__snapshots__/compiler.test.rsrx.snap +95 -0
  95. package/tests/server/{compiler.test.ripple → compiler.test.rsrx} +0 -17
  96. package/tests/server/{html-nesting-validation.test.ripple → html-nesting-validation.test.rsrx} +3 -3
  97. package/tests/server/streaming-ssr.test.rsrx +115 -0
  98. package/tests/server/{style-identifier.test.ripple → style-identifier.test.rsrx} +1 -1
  99. package/tests/server/try.test.rsrx +503 -0
  100. package/tests/setup-server.js +1 -1
  101. package/tests/utils/compiler-compat-config.test.js +4 -4
  102. package/tests/utils/vite-plugin-config.test.js +1 -1
  103. package/tests/utils/vite-plugin-hmr.test.js +5 -5
  104. package/tsconfig.json +2 -0
  105. package/types/index.d.ts +13 -23
  106. package/types/server.d.ts +43 -16
  107. package/src/compiler/comment-utils.js +0 -91
  108. package/src/compiler/errors.js +0 -77
  109. package/src/compiler/identifier-utils.js +0 -80
  110. package/src/compiler/index.d.ts +0 -127
  111. package/src/compiler/index.js +0 -89
  112. package/src/compiler/phases/1-parse/index.js +0 -2964
  113. package/src/compiler/phases/1-parse/style.js +0 -704
  114. package/src/compiler/phases/2-analyze/css-analyze.js +0 -160
  115. package/src/compiler/phases/2-analyze/index.js +0 -2238
  116. package/src/compiler/phases/2-analyze/prune.js +0 -1131
  117. package/src/compiler/phases/2-analyze/validation.js +0 -168
  118. package/src/compiler/phases/3-transform/client/index.js +0 -5301
  119. package/src/compiler/phases/3-transform/segments.js +0 -2129
  120. package/src/compiler/phases/3-transform/server/index.js +0 -1899
  121. package/src/compiler/phases/3-transform/stylesheet.js +0 -545
  122. package/src/compiler/scope.js +0 -476
  123. package/src/compiler/source-map-utils.js +0 -358
  124. package/src/compiler/types/acorn.d.ts +0 -11
  125. package/src/compiler/types/estree-jsx.d.ts +0 -11
  126. package/src/compiler/types/estree.d.ts +0 -11
  127. package/src/compiler/types/index.d.ts +0 -1404
  128. package/src/compiler/types/parse.d.ts +0 -1721
  129. package/src/compiler/utils.js +0 -1263
  130. package/tests/client/_etc.test.ripple +0 -5
  131. package/tests/client/async-suspend.test.ripple +0 -94
  132. package/tests/client/try.test.ripple +0 -196
  133. package/tests/server/streaming-ssr.test.ripple +0 -68
  134. package/tests/server/try.test.ripple +0 -82
  135. /package/tests/client/array/{array.copy-within.test.ripple → array.copy-within.test.rsrx} +0 -0
  136. /package/tests/client/array/{array.derived.test.ripple → array.derived.test.rsrx} +0 -0
  137. /package/tests/client/array/{array.iteration.test.ripple → array.iteration.test.rsrx} +0 -0
  138. /package/tests/client/array/{array.mutations.test.ripple → array.mutations.test.rsrx} +0 -0
  139. /package/tests/client/array/{array.to-methods.test.ripple → array.to-methods.test.rsrx} +0 -0
  140. /package/tests/client/basic/{basic.attributes.test.ripple → basic.attributes.test.rsrx} +0 -0
  141. /package/tests/client/basic/{basic.collections.test.ripple → basic.collections.test.rsrx} +0 -0
  142. /package/tests/client/basic/{basic.components.test.ripple → basic.components.test.rsrx} +0 -0
  143. /package/tests/client/basic/{basic.events.test.ripple → basic.events.test.rsrx} +0 -0
  144. /package/tests/client/basic/{basic.get-set.test.ripple → basic.get-set.test.rsrx} +0 -0
  145. /package/tests/client/basic/{basic.hmr.test.ripple → basic.hmr.test.rsrx} +0 -0
  146. /package/tests/client/basic/{basic.reactivity.test.ripple → basic.reactivity.test.rsrx} +0 -0
  147. /package/tests/client/basic/{basic.rendering.test.ripple → basic.rendering.test.rsrx} +0 -0
  148. /package/tests/client/basic/{basic.utilities.test.ripple → basic.utilities.test.rsrx} +0 -0
  149. /package/tests/client/{boundaries.test.ripple → boundaries.test.rsrx} +0 -0
  150. /package/tests/client/compiler/{compiler.regex.test.ripple → compiler.regex.test.rsrx} +0 -0
  151. /package/tests/client/composite/{composite.dynamic-components.test.ripple → composite.dynamic-components.test.rsrx} +0 -0
  152. /package/tests/client/composite/{composite.generics.test.ripple → composite.generics.test.rsrx} +0 -0
  153. /package/tests/client/composite/{composite.props.test.ripple → composite.props.test.rsrx} +0 -0
  154. /package/tests/client/composite/{composite.reactivity.test.ripple → composite.reactivity.test.rsrx} +0 -0
  155. /package/tests/client/composite/{composite.render.test.ripple → composite.render.test.rsrx} +0 -0
  156. /package/tests/client/{computed-properties.test.ripple → computed-properties.test.rsrx} +0 -0
  157. /package/tests/client/{context.test.ripple → context.test.rsrx} +0 -0
  158. /package/tests/client/{date.test.ripple → date.test.rsrx} +0 -0
  159. /package/tests/client/{dynamic-elements.test.ripple → dynamic-elements.test.rsrx} +0 -0
  160. /package/tests/client/{events.test.ripple → events.test.rsrx} +0 -0
  161. /package/tests/client/{for.test.ripple → for.test.rsrx} +0 -0
  162. /package/tests/client/{function-overload-import.ripple → function-overload-import.rsrx} +0 -0
  163. /package/tests/client/{head.test.ripple → head.test.rsrx} +0 -0
  164. /package/tests/client/{html.test.ripple → html.test.rsrx} +0 -0
  165. /package/tests/client/{input-value.test.ripple → input-value.test.rsrx} +0 -0
  166. /package/tests/client/{lazy-destructuring.test.ripple → lazy-destructuring.test.rsrx} +0 -0
  167. /package/tests/client/{map.test.ripple → map.test.rsrx} +0 -0
  168. /package/tests/client/{media-query.test.ripple → media-query.test.rsrx} +0 -0
  169. /package/tests/client/{object.test.ripple → object.test.rsrx} +0 -0
  170. /package/tests/client/{portal.test.ripple → portal.test.rsrx} +0 -0
  171. /package/tests/client/{ref.test.ripple → ref.test.rsrx} +0 -0
  172. /package/tests/client/{set.test.ripple → set.test.rsrx} +0 -0
  173. /package/tests/client/{svg.test.ripple → svg.test.rsrx} +0 -0
  174. /package/tests/client/{switch.test.ripple → switch.test.rsrx} +0 -0
  175. /package/tests/client/{tsx.test.ripple → tsx.test.rsrx} +0 -0
  176. /package/tests/client/{typescript-generics.test.ripple → typescript-generics.test.rsrx} +0 -0
  177. /package/tests/client/url/{url.derived.test.ripple → url.derived.test.rsrx} +0 -0
  178. /package/tests/client/url/{url.parsing.test.ripple → url.parsing.test.rsrx} +0 -0
  179. /package/tests/client/url/{url.partial-removal.test.ripple → url.partial-removal.test.rsrx} +0 -0
  180. /package/tests/client/url/{url.reactivity.test.ripple → url.reactivity.test.rsrx} +0 -0
  181. /package/tests/client/url/{url.serialization.test.ripple → url.serialization.test.rsrx} +0 -0
  182. /package/tests/client/url-search-params/{url-search-params.derived.test.ripple → url-search-params.derived.test.rsrx} +0 -0
  183. /package/tests/client/url-search-params/{url-search-params.initialization.test.ripple → url-search-params.initialization.test.rsrx} +0 -0
  184. /package/tests/client/url-search-params/{url-search-params.iteration.test.ripple → url-search-params.iteration.test.rsrx} +0 -0
  185. /package/tests/client/url-search-params/{url-search-params.mutation.test.ripple → url-search-params.mutation.test.rsrx} +0 -0
  186. /package/tests/client/url-search-params/{url-search-params.retrieval.test.ripple → url-search-params.retrieval.test.rsrx} +0 -0
  187. /package/tests/client/url-search-params/{url-search-params.serialization.test.ripple → url-search-params.serialization.test.rsrx} +0 -0
  188. /package/tests/client/url-search-params/{url-search-params.tracked-url.test.ripple → url-search-params.tracked-url.test.rsrx} +0 -0
  189. /package/tests/hydration/components/{basic.ripple → basic.rsrx} +0 -0
  190. /package/tests/hydration/components/{composite.ripple → composite.rsrx} +0 -0
  191. /package/tests/hydration/components/{events.ripple → events.rsrx} +0 -0
  192. /package/tests/hydration/components/{for.ripple → for.rsrx} +0 -0
  193. /package/tests/hydration/components/{head.ripple → head.rsrx} +0 -0
  194. /package/tests/hydration/components/{hmr.ripple → hmr.rsrx} +0 -0
  195. /package/tests/hydration/components/{html-in-template.ripple → html-in-template.rsrx} +0 -0
  196. /package/tests/hydration/components/{html.ripple → html.rsrx} +0 -0
  197. /package/tests/hydration/components/{if-children.ripple → if-children.rsrx} +0 -0
  198. /package/tests/hydration/components/{if.ripple → if.rsrx} +0 -0
  199. /package/tests/hydration/components/{nested-control-flow.ripple → nested-control-flow.rsrx} +0 -0
  200. /package/tests/hydration/components/{portal.ripple → portal.rsrx} +0 -0
  201. /package/tests/hydration/components/{reactivity.ripple → reactivity.rsrx} +0 -0
  202. /package/tests/hydration/components/{return.ripple → return.rsrx} +0 -0
  203. /package/tests/hydration/components/{switch.ripple → switch.rsrx} +0 -0
  204. /package/tests/server/{await.test.ripple → await.test.rsrx} +0 -0
  205. /package/tests/server/{basic.attributes.test.ripple → basic.attributes.test.rsrx} +0 -0
  206. /package/tests/server/{basic.components.test.ripple → basic.components.test.rsrx} +0 -0
  207. /package/tests/server/{basic.test.ripple → basic.test.rsrx} +0 -0
  208. /package/tests/server/{composite.props.test.ripple → composite.props.test.rsrx} +0 -0
  209. /package/tests/server/{composite.test.ripple → composite.test.rsrx} +0 -0
  210. /package/tests/server/{context.test.ripple → context.test.rsrx} +0 -0
  211. /package/tests/server/{dynamic-elements.test.ripple → dynamic-elements.test.rsrx} +0 -0
  212. /package/tests/server/{for.test.ripple → for.test.rsrx} +0 -0
  213. /package/tests/server/{head.test.ripple → head.test.rsrx} +0 -0
  214. /package/tests/server/{if.test.ripple → if.test.rsrx} +0 -0
  215. /package/tests/server/{lazy-destructuring.test.ripple → lazy-destructuring.test.rsrx} +0 -0
  216. /package/tests/server/{return.test.ripple → return.test.rsrx} +0 -0
  217. /package/tests/server/{switch.test.ripple → switch.test.rsrx} +0 -0
@@ -1,1899 +0,0 @@
1
- /** @import * as AST from 'estree'; */
2
- /** @import { RawSourceMap } from 'source-map'; */
3
- /**
4
- @import {
5
- TransformServerContext,
6
- TransformServerState,
7
- Visitors,
8
- AnalysisResult,
9
- ScopeInterface,
10
- } from '#compiler' */
11
-
12
- import * as b from '../../../../utils/builders.js';
13
- import { walk } from 'zimmerframe';
14
- import ts from 'esrap/languages/ts';
15
- import path from 'node:path';
16
- import { print } from 'esrap';
17
- import is_reference from 'is-reference';
18
- import {
19
- determine_namespace_for_children,
20
- escape_html,
21
- is_boolean_attribute,
22
- is_element_dom_element,
23
- is_inside_component,
24
- is_void_element,
25
- normalize_children,
26
- is_children_template_expression,
27
- is_binding_function,
28
- is_element_dynamic,
29
- is_ripple_track_call,
30
- is_ripple_import,
31
- replace_lazy_param_pattern,
32
- hash,
33
- flatten_switch_consequent,
34
- get_ripple_namespace_call_name,
35
- jsx_to_ripple_node,
36
- } from '../../../utils.js';
37
- import { escape } from '../../../../utils/escaping.js';
38
- import { is_event_attribute } from '../../../../utils/events.js';
39
- import { render_stylesheets } from '../stylesheet.js';
40
- import { createHash } from 'node:crypto';
41
- import {
42
- STYLE_IDENTIFIER,
43
- CSS_HASH_IDENTIFIER,
44
- obfuscate_identifier,
45
- } from '../../../identifier-utils.js';
46
- import { BLOCK_CLOSE, BLOCK_OPEN } from '../../../../constants.js';
47
-
48
- /**
49
- * Checks if a node is template or control-flow content that should be wrapped when return flags are active
50
- * @param {AST.Node} node
51
- * @returns {boolean}
52
- */
53
- function is_template_or_control_flow(node) {
54
- return (
55
- node.type === 'Element' ||
56
- node.type === 'RippleExpression' ||
57
- node.type === 'Text' ||
58
- node.type === 'Html' ||
59
- node.type === 'Tsx' ||
60
- node.type === 'TsxCompat' ||
61
- node.type === 'IfStatement' ||
62
- node.type === 'ForOfStatement' ||
63
- node.type === 'TryStatement' ||
64
- node.type === 'SwitchStatement'
65
- );
66
- }
67
-
68
- /**
69
- * Builds a negated AND condition from return flag names: !__r_1 && !__r_2 && ...
70
- * @param {string[]} flags
71
- * @returns {AST.Expression}
72
- */
73
- function build_return_guard(flags) {
74
- /** @type {AST.Expression} */
75
- let condition = b.unary('!', b.id(flags[0]));
76
- for (let i = 1; i < flags.length; i++) {
77
- condition = b.logical('&&', condition, b.unary('!', b.id(flags[i])));
78
- }
79
- return condition;
80
- }
81
-
82
- /**
83
- * @param {AST.ClassDeclaration | AST.ClassExpression} node
84
- * @param {TransformServerContext} context
85
- * @returns {void}
86
- */
87
- function strip_class_typescript_syntax(node, context) {
88
- delete node.typeParameters;
89
- delete node.superTypeParameters;
90
- delete node.implements;
91
-
92
- if (node.superClass?.type === 'TSInstantiationExpression') {
93
- node.superClass = /** @type {AST.Expression} */ (context.visit(node.superClass.expression));
94
- } else if (node.superClass && 'typeArguments' in node.superClass) {
95
- delete node.superClass.typeArguments;
96
- }
97
- }
98
-
99
- /**
100
- * Collects all unique return statements from the direct children of a body
101
- * @param {AST.Node[]} children
102
- * @returns {AST.ReturnStatement[]}
103
- */
104
- function collect_returns_from_children(children) {
105
- /** @type {AST.ReturnStatement[]} */
106
- const returns = [];
107
- const seen = new Set();
108
- for (const node of children) {
109
- if (node.type === 'ReturnStatement') {
110
- if (!seen.has(node)) {
111
- seen.add(node);
112
- returns.push(node);
113
- }
114
- }
115
- if (node.metadata?.returns) {
116
- for (const ret of node.metadata.returns) {
117
- if (!seen.has(ret)) {
118
- seen.add(ret);
119
- returns.push(ret);
120
- }
121
- }
122
- }
123
- }
124
- return returns;
125
- }
126
-
127
- /**
128
- * @param {AST.Node[]} children
129
- * @param {TransformServerContext} context
130
- */
131
- function transform_children(children, context) {
132
- const { visit, state } = context;
133
- const normalized = normalize_children(children, context);
134
-
135
- const all_returns = collect_returns_from_children(normalized);
136
- /** @type {Map<AST.ReturnStatement, { name: string, tracked: boolean }>} */
137
- const return_flags = new Map([...(state.return_flags || [])]);
138
- /** @type {AST.ReturnStatement[]} */
139
- const new_returns = [];
140
- for (const ret of all_returns) {
141
- if (!return_flags.has(ret)) {
142
- return_flags.set(ret, { name: state.scope.generate('__r'), tracked: false });
143
- new_returns.push(ret);
144
- }
145
- }
146
-
147
- for (const ret of new_returns) {
148
- const info = /** @type {{ name: string, tracked: boolean }} */ (return_flags.get(ret));
149
- state.init?.push(b.var(b.id(info.name), b.false));
150
- }
151
-
152
- // Track accumulated return flags as we process children
153
- /** @type {string[]} */
154
- let accumulated_flags = [];
155
-
156
- /**
157
- * @param {AST.ReturnStatement[] | undefined} returns
158
- */
159
- const push_return_flags = (returns) => {
160
- if (!returns) return;
161
- for (const ret of returns) {
162
- const info = return_flags.get(ret);
163
- if (info && !accumulated_flags.includes(info.name)) {
164
- accumulated_flags.push(info.name);
165
- }
166
- }
167
- };
168
-
169
- /** @param {AST.Node} node */
170
- const process_node = (node) => {
171
- if (node.type === 'BreakStatement') {
172
- state.init?.push(b.break);
173
- return;
174
- }
175
- if (
176
- node.type === 'VariableDeclaration' ||
177
- node.type === 'ExpressionStatement' ||
178
- node.type === 'ThrowStatement' ||
179
- node.type === 'FunctionDeclaration' ||
180
- node.type === 'DebuggerStatement' ||
181
- node.type === 'ClassDeclaration' ||
182
- node.type === 'TSTypeAliasDeclaration' ||
183
- node.type === 'TSInterfaceDeclaration' ||
184
- node.type === 'ReturnStatement' ||
185
- node.type === 'Component'
186
- ) {
187
- const metadata = { await: false };
188
- state.init?.push(
189
- /** @type {AST.Statement} */ (visit(node, { ...state, return_flags, metadata })),
190
- );
191
- if (metadata.await) {
192
- state.init?.push(b.if(b.call('_$_.aborted'), b.return(null)));
193
- if (state.metadata?.await === false) {
194
- state.metadata.await = true;
195
- }
196
- }
197
- if (node.type === 'ReturnStatement') {
198
- const info = return_flags.get(node);
199
- if (info && !accumulated_flags.includes(info.name)) {
200
- accumulated_flags.push(info.name);
201
- }
202
- }
203
- } else {
204
- visit(node, { ...state, return_flags, template_child: true });
205
- }
206
- };
207
-
208
- /** @type {AST.Node[]} */
209
- let pending_group = [];
210
- /** @type {string[]} */
211
- let pending_guard_flags = [];
212
-
213
- const flush_pending_group = () => {
214
- if (pending_group.length === 0) return;
215
-
216
- const group = pending_group;
217
- const guard_flags = pending_guard_flags;
218
- pending_group = [];
219
- pending_guard_flags = [];
220
-
221
- /** @type {AST.Statement[]} */
222
- const wrapped = [];
223
- const saved_init = state.init;
224
- state.init = wrapped;
225
-
226
- for (const group_node of group) {
227
- process_node(group_node);
228
- }
229
-
230
- state.init = saved_init;
231
- if (wrapped.length === 0) return;
232
-
233
- const guard = build_return_guard(guard_flags);
234
- state.init?.push(
235
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_OPEN))),
236
- );
237
- state.init?.push(b.if(guard, b.block(wrapped)));
238
- state.init?.push(
239
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_CLOSE))),
240
- );
241
- };
242
-
243
- for (let idx = 0; idx < normalized.length; idx++) {
244
- const node = normalized[idx];
245
-
246
- if (accumulated_flags.length > 0 && is_template_or_control_flow(node)) {
247
- if (pending_group.length === 0) {
248
- pending_guard_flags = [...accumulated_flags];
249
- }
250
- pending_group.push(node);
251
-
252
- if (node.metadata?.has_return && node.metadata.returns) {
253
- flush_pending_group();
254
- push_return_flags(node.metadata.returns);
255
- }
256
- continue;
257
- }
258
-
259
- flush_pending_group();
260
- process_node(node);
261
- push_return_flags(node.metadata?.has_return ? node.metadata.returns : undefined);
262
- }
263
-
264
- flush_pending_group();
265
-
266
- const head_elements = /** @type {AST.Element[]} */ (
267
- children.filter(
268
- (node) => node.type === 'Element' && node.id.type === 'Identifier' && node.id.name === 'head',
269
- )
270
- );
271
-
272
- if (head_elements.length) {
273
- state.init?.push(
274
- b.stmt(b.assignment('=', b.member(b.id('__output'), b.id('target')), b.literal('head'))),
275
- );
276
- for (let i = 0; i < head_elements.length; i++) {
277
- const head_element = head_elements[i];
278
- // Generate a hash for this head element to match client-side hydration
279
- // Use both filename and index to ensure uniqueness
280
- const hash_source = `${context.state.filename}:head:${i}:${head_element.start ?? 0}`;
281
- const hash_value = hash(hash_source);
282
-
283
- // Emit hydration marker comment with hash
284
- state.init?.push(
285
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(`<!--${hash_value}-->`))),
286
- );
287
-
288
- transform_children(head_element.children, context);
289
-
290
- // No closing marker needed for head elements - the hash is sufficient
291
- }
292
- state.init?.push(
293
- b.stmt(b.assignment('=', b.member(b.id('__output'), b.id('target')), b.literal(null))),
294
- );
295
- }
296
- }
297
-
298
- /**
299
- * @param {AST.Node[]} body
300
- * @param {TransformServerContext} context
301
- * @returns {AST.Statement[]}
302
- */
303
- function transform_body(body, context) {
304
- const { state } = context;
305
- /** @type {TransformServerState} */
306
- const body_state = {
307
- ...state,
308
- init: [],
309
- metadata: state.metadata,
310
- };
311
-
312
- transform_children(body, { ...context, state: body_state });
313
-
314
- return /** @type {AST.Statement[]} */ (body_state.init);
315
- }
316
-
317
- /** @type {Visitors<AST.Node, TransformServerState>} */
318
- const visitors = {
319
- _: (node, { next, state }) => {
320
- const scope = state.scopes.get(node);
321
-
322
- if (scope && scope !== state.scope) {
323
- return next({ ...state, scope });
324
- } else {
325
- return next();
326
- }
327
- },
328
-
329
- Identifier(node, context) {
330
- const parent = /** @type {AST.Node} */ (context.path.at(-1));
331
-
332
- if (is_reference(node, parent)) {
333
- // Apply lazy destructuring binding transforms only
334
- const binding = context.state.scope?.get(node.name);
335
- if (
336
- binding?.transform?.read &&
337
- binding.node !== node &&
338
- (binding.kind === 'lazy' || binding.kind === 'lazy_fallback')
339
- ) {
340
- return binding.transform.read(node);
341
- }
342
-
343
- return node;
344
- }
345
- },
346
-
347
- Component(node, context) {
348
- let props_param_output;
349
-
350
- if (node.params.length > 0) {
351
- let props_param = node.params[0];
352
-
353
- if (props_param.type === 'Identifier') {
354
- delete props_param.typeAnnotation;
355
- props_param_output = props_param;
356
- } else if (props_param.type === 'ObjectPattern' || props_param.type === 'ArrayPattern') {
357
- delete props_param.typeAnnotation;
358
- if (props_param.lazy) {
359
- // Lazy destructuring: use __props identifier, bindings resolved via transforms
360
- props_param_output = b.id('__props');
361
- } else {
362
- props_param_output = replace_lazy_param_pattern(props_param);
363
- }
364
- } else {
365
- props_param_output = props_param;
366
- }
367
- }
368
-
369
- const metadata = { await: false };
370
-
371
- /** @type {AST.Statement[]} */
372
- const body_statements = [];
373
-
374
- if (node.css !== null) {
375
- const hash_id = b.id(CSS_HASH_IDENTIFIER);
376
- const hash = b.var(hash_id, b.literal(node.css.hash));
377
- context.state.stylesheets.push(node.css);
378
-
379
- // Register CSS hash during rendering
380
- body_statements.push(
381
- hash,
382
- b.stmt(b.call(b.member(b.id('__output'), b.id('register_css')), hash_id)),
383
- );
384
-
385
- if (node.metadata.styleIdentifierPresent) {
386
- /** @type {AST.Property[]} */
387
- const properties = [];
388
- if (node.metadata.topScopedClasses && node.metadata.topScopedClasses.size > 0) {
389
- for (const [className] of node.metadata.topScopedClasses) {
390
- properties.push(
391
- b.prop(
392
- 'init',
393
- b.key(className),
394
- b.template([b.quasi('', false), b.quasi(` ${className}`, true)], [hash_id]),
395
- ),
396
- );
397
- }
398
- }
399
- body_statements.push(b.var(b.id(STYLE_IDENTIFIER), b.object(properties)));
400
- }
401
- }
402
-
403
- body_statements.push(
404
- b.stmt(b.call('_$_.push_component')),
405
- ...transform_body(node.body, {
406
- ...context,
407
- state: {
408
- ...context.state,
409
- component: node,
410
- metadata,
411
- applyParentCssScope:
412
- node.id?.name === 'render_children' ? context.state.applyParentCssScope : undefined,
413
- },
414
- }),
415
- b.stmt(b.call('_$_.pop_component')),
416
- );
417
-
418
- let component_fn = b.function(
419
- node.id,
420
- node.params.length > 0
421
- ? [b.id('__output'), /** @type {AST.Pattern} */ (props_param_output)]
422
- : [b.id('__output')],
423
- b.block([
424
- ...(metadata.await
425
- ? [b.return(b.call('_$_.async', b.thunk(b.block(body_statements), true)))]
426
- : body_statements),
427
- ]),
428
- );
429
-
430
- // Mark function as async if needed
431
- if (metadata.await) {
432
- component_fn = b.async(component_fn);
433
- }
434
-
435
- // Anonymous components return a FunctionExpression
436
- if (!node.id) {
437
- // For async anonymous components, we need to set .async on the function
438
- if (metadata.await) {
439
- // Use IIFE pattern: (fn => (fn.async = true, fn))(function() { ... })
440
- return b.call(
441
- b.arrow(
442
- [b.id('fn')],
443
- b.sequence([
444
- b.assignment('=', b.member(b.id('fn'), b.id('async')), b.true),
445
- b.id('fn'),
446
- ]),
447
- ),
448
- component_fn,
449
- );
450
- }
451
- return component_fn;
452
- }
453
-
454
- // Named components return a FunctionDeclaration
455
- const declaration = b.function_declaration(
456
- node.id,
457
- component_fn.params,
458
- component_fn.body,
459
- component_fn.async,
460
- );
461
-
462
- if (metadata.await) {
463
- const parent = context.path.at(-1);
464
- /** @type {AST.RippleProgram['body'] | null} */
465
- let body = null;
466
- /** @type {AST.Component | AST.ExportNamedDeclaration} */
467
- let target_node = node;
468
- if (parent?.type === 'Program' || parent?.type === 'BlockStatement') {
469
- body = parent.body;
470
- } else if (parent?.type === 'ExportNamedDeclaration') {
471
- const grandparent = context.path.at(-2);
472
- if (grandparent?.type === 'Program' || grandparent?.type === 'BlockStatement') {
473
- body = grandparent.body;
474
- target_node = parent;
475
- }
476
- }
477
- if (body !== null) {
478
- const index = body.indexOf(target_node);
479
- if (index >= 0) {
480
- body.splice(
481
- index + 1,
482
- 0,
483
- b.stmt(b.assignment('=', b.member(node.id, b.id('async')), b.true)),
484
- );
485
- }
486
- }
487
- }
488
-
489
- return declaration;
490
- },
491
-
492
- CallExpression(node, context) {
493
- if (!context.state.to_ts) {
494
- delete node.typeArguments;
495
- }
496
-
497
- const callee = node.callee;
498
-
499
- // Handle direct calls to ripple-imported functions: effect(), untrack(), RippleArray(), etc.
500
- if (callee.type === 'Identifier' && is_ripple_import(callee, context)) {
501
- const ripple_runtime_method = get_ripple_namespace_call_name(callee.name);
502
- if (ripple_runtime_method !== null) {
503
- return {
504
- ...node,
505
- callee: b.member(b.id('_$_'), b.id(ripple_runtime_method)),
506
- arguments: /** @type {(AST.Expression | AST.SpreadElement)[]} */ ([
507
- ...node.arguments.map((arg) => context.visit(arg)),
508
- ]),
509
- };
510
- }
511
- }
512
-
513
- const track_call_name = is_ripple_track_call(callee, context);
514
- if (track_call_name) {
515
- const track_method_name = 'track';
516
-
517
- return {
518
- ...node,
519
- callee: b.member(b.id('_$_'), b.id(track_method_name)),
520
- arguments: /** @type {(AST.Expression | AST.SpreadElement)[]} */ (
521
- node.arguments.map((arg) => context.visit(arg))
522
- ),
523
- };
524
- }
525
-
526
- // Handle member calls on ripple imports, like RippleArray.from()
527
- if (
528
- callee.type === 'MemberExpression' &&
529
- callee.object.type === 'Identifier' &&
530
- callee.property.type === 'Identifier' &&
531
- is_ripple_import(callee, context)
532
- ) {
533
- const object = callee.object;
534
- const property = callee.property;
535
- const method_name = get_ripple_namespace_call_name(object.name);
536
- if (method_name !== null) {
537
- return b.member(
538
- b.id('_$_'),
539
- b.member(
540
- b.id(method_name),
541
- b.call(
542
- b.id(property.name),
543
- .../** @type {(AST.Expression | AST.SpreadElement)[]} */ (
544
- node.arguments.map((arg) => context.visit(arg))
545
- ),
546
- ),
547
- ),
548
- );
549
- }
550
- }
551
-
552
- return context.next();
553
- },
554
-
555
- NewExpression(node, context) {
556
- const callee = node.callee;
557
-
558
- if (!context.state.to_ts) {
559
- delete node.typeArguments;
560
- }
561
-
562
- // Transform `new RippleArray(...)`, `new RippleMap(...)`, etc. imported from 'ripple'
563
- if (callee.type === 'Identifier' && is_ripple_import(callee, context)) {
564
- const ripple_runtime_method = get_ripple_namespace_call_name(callee.name);
565
- if (ripple_runtime_method !== null) {
566
- return b.call(
567
- '_$_.' + ripple_runtime_method,
568
- .../** @type {(AST.Expression | AST.SpreadElement)[]} */ (
569
- node.arguments.map((arg) => context.visit(arg))
570
- ),
571
- );
572
- }
573
- }
574
-
575
- return context.next();
576
- },
577
-
578
- PropertyDefinition(node, context) {
579
- if (!context.state.to_ts) {
580
- delete node.typeAnnotation;
581
- }
582
- return context.next();
583
- },
584
-
585
- ClassDeclaration(node, context) {
586
- if (!context.state.to_ts) {
587
- strip_class_typescript_syntax(node, context);
588
- }
589
- return context.next();
590
- },
591
-
592
- ClassExpression(node, context) {
593
- if (!context.state.to_ts) {
594
- strip_class_typescript_syntax(node, context);
595
- }
596
- return context.next();
597
- },
598
-
599
- FunctionDeclaration(node, context) {
600
- if (!context.state.to_ts) {
601
- delete node.returnType;
602
- delete node.typeParameters;
603
- for (let i = 0; i < node.params.length; i++) {
604
- const param = node.params[i];
605
- delete param.typeAnnotation;
606
- // Handle AssignmentPattern (parameters with default values)
607
- if (param.type === 'AssignmentPattern' && param.left) {
608
- delete param.left.typeAnnotation;
609
- }
610
- // Replace lazy destructuring params with generated identifiers
611
- const pattern = param.type === 'AssignmentPattern' ? param.left : param;
612
- if (pattern.type === 'ObjectPattern' || pattern.type === 'ArrayPattern') {
613
- const transformed_pattern = replace_lazy_param_pattern(pattern);
614
- node.params[i] =
615
- param.type === 'AssignmentPattern'
616
- ? /** @type {AST.AssignmentPattern} */ ({ ...param, left: transformed_pattern })
617
- : transformed_pattern;
618
- }
619
- }
620
- }
621
- return context.next();
622
- },
623
-
624
- FunctionExpression(node, context) {
625
- if (!context.state.to_ts) {
626
- delete node.returnType;
627
- delete node.typeParameters;
628
- for (let i = 0; i < node.params.length; i++) {
629
- const param = node.params[i];
630
- delete param.typeAnnotation;
631
- // Handle AssignmentPattern (parameters with default values)
632
- if (param.type === 'AssignmentPattern' && param.left) {
633
- delete param.left.typeAnnotation;
634
- }
635
- // Replace lazy destructuring params with generated identifiers
636
- const pattern = param.type === 'AssignmentPattern' ? param.left : param;
637
- if (pattern.type === 'ObjectPattern' || pattern.type === 'ArrayPattern') {
638
- const transformed_pattern = replace_lazy_param_pattern(pattern);
639
- node.params[i] =
640
- param.type === 'AssignmentPattern'
641
- ? /** @type {AST.AssignmentPattern} */ ({ ...param, left: transformed_pattern })
642
- : transformed_pattern;
643
- }
644
- }
645
- }
646
- return context.next();
647
- },
648
-
649
- BlockStatement(node, context) {
650
- /** @type {AST.Statement[]} */
651
- const statements = [];
652
-
653
- for (const statement of node.body) {
654
- statements.push(/** @type {AST.Statement} */ (context.visit(statement)));
655
- }
656
-
657
- return b.block(statements);
658
- },
659
-
660
- ArrowFunctionExpression(node, context) {
661
- delete node.returnType;
662
- delete node.typeParameters;
663
- for (let i = 0; i < node.params.length; i++) {
664
- const param = node.params[i];
665
- delete param.typeAnnotation;
666
- // Handle AssignmentPattern (parameters with default values)
667
- if (param.type === 'AssignmentPattern' && param.left) {
668
- delete param.left.typeAnnotation;
669
- }
670
- // Replace lazy destructuring params with generated identifiers
671
- const pattern = param.type === 'AssignmentPattern' ? param.left : param;
672
- if (pattern.type === 'ObjectPattern' || pattern.type === 'ArrayPattern') {
673
- const transformed_pattern = replace_lazy_param_pattern(pattern);
674
- node.params[i] =
675
- param.type === 'AssignmentPattern'
676
- ? /** @type {AST.AssignmentPattern} */ ({ ...param, left: transformed_pattern })
677
- : transformed_pattern;
678
- }
679
- }
680
-
681
- return context.next();
682
- },
683
-
684
- TSAsExpression(node, context) {
685
- if (!context.state.to_ts) {
686
- return context.visit(node.expression);
687
- }
688
- return context.next();
689
- },
690
-
691
- TSInstantiationExpression(node, context) {
692
- if (!context.state.to_ts) {
693
- // In JavaScript, just return the expression wrapped in parentheses
694
- return b.sequence(/** @type {AST.Expression[]} */ ([context.visit(node.expression)]));
695
- }
696
- return context.next();
697
- },
698
-
699
- TSTypeAliasDeclaration(_, context) {
700
- if (!context.state.to_ts) {
701
- return b.empty;
702
- }
703
- context.next();
704
- },
705
-
706
- TSInterfaceDeclaration(_, context) {
707
- if (!context.state.to_ts) {
708
- return b.empty;
709
- }
710
- context.next();
711
- },
712
-
713
- ExportNamedDeclaration(node, context) {
714
- if (!context.state.to_ts && node.exportKind === 'type') {
715
- return b.empty;
716
- }
717
- if (!context.state.ancestor_server_block) {
718
- return context.next();
719
- }
720
- const declaration = node.declaration;
721
- /** @type {AST.Statement[]} */
722
- const statements = [];
723
-
724
- if (declaration && declaration.type === 'FunctionDeclaration') {
725
- const name = declaration.id.name;
726
- if (context.state.server_exported_names.includes(name)) {
727
- return b.empty;
728
- }
729
- context.state.server_exported_names.push(name);
730
- return b.stmt(
731
- b.assignment(
732
- '=',
733
- b.member(b.id('_$_server_$_'), b.id(name)),
734
- /** @type {AST.Expression} */
735
- (context.visit(declaration)),
736
- ),
737
- );
738
- } else if (declaration && declaration.type === 'VariableDeclaration') {
739
- for (const decl of declaration.declarations) {
740
- if (decl.init !== undefined && decl.init !== null) {
741
- if (decl.id.type === 'Identifier') {
742
- const name = decl.id.name;
743
- if (
744
- decl.init.type === 'FunctionExpression' ||
745
- decl.init.type === 'ArrowFunctionExpression'
746
- ) {
747
- if (context.state.server_exported_names.includes(name)) {
748
- continue;
749
- }
750
- context.state.server_exported_names.push(name);
751
- statements.push(
752
- b.stmt(
753
- b.assignment(
754
- '=',
755
- b.member(b.id('_$_server_$_'), b.id(name)),
756
- /** @type {AST.Expression} */
757
- (context.visit(decl.init)),
758
- ),
759
- ),
760
- );
761
- } else if (decl.init.type === 'Identifier') {
762
- if (context.state.server_exported_names.includes(name)) {
763
- continue;
764
- }
765
- context.state.server_exported_names.push(name);
766
-
767
- statements.push(
768
- b.stmt(
769
- b.assignment(
770
- '=',
771
- b.member(b.id('_$_server_$_'), b.id(name)),
772
- b.id(decl.init.name),
773
- ),
774
- ),
775
- );
776
- } else {
777
- // TODO allow exporting variables that are not functions
778
- throw new Error('Not implemented');
779
- }
780
- } else {
781
- // TODO allow exporting variables that are not functions
782
- throw new Error('Not implemented');
783
- }
784
- } else {
785
- // TODO allow exporting uninitialized variables
786
- throw new Error('Not implemented');
787
- }
788
- // TODO: allow exporting consts when hydration is supported
789
- }
790
- } else if (node.specifiers) {
791
- for (const specifier of node.specifiers) {
792
- const name = /** @type {AST.Identifier} */ (specifier.local).name;
793
- if (context.state.server_exported_names.includes(name)) {
794
- continue;
795
- }
796
- context.state.server_exported_names.push(name);
797
-
798
- const binding = context.state.scope.get(name);
799
-
800
- if (!binding || !is_binding_function(binding, context.state.scope)) {
801
- continue;
802
- }
803
-
804
- statements.push(
805
- b.stmt(b.assignment('=', b.member(b.id('_$_server_$_'), b.id(name)), specifier.local)),
806
- );
807
- }
808
- } else {
809
- // TODO
810
- throw new Error('Not implemented');
811
- }
812
-
813
- return statements.length ? b.block(statements) : b.empty;
814
- },
815
-
816
- ExpressionStatement(node, context) {
817
- // Handle standalone lazy destructuring: &[data] = track(0); → const lazy0 = track(0);
818
- if (
819
- node.expression.type === 'AssignmentExpression' &&
820
- (node.expression.left.type === 'ObjectPattern' ||
821
- node.expression.left.type === 'ArrayPattern') &&
822
- node.expression.left.lazy &&
823
- node.expression.left.metadata?.lazy_id
824
- ) {
825
- const right = /** @type {AST.Expression} */ (context.visit(node.expression.right));
826
- return b.const(b.id(node.expression.left.metadata.lazy_id), right);
827
- }
828
- return context.next();
829
- },
830
-
831
- VariableDeclaration(node, context) {
832
- for (const declarator of node.declarations) {
833
- if (!context.state.to_ts) {
834
- delete declarator.id.typeAnnotation;
835
-
836
- // Replace lazy destructuring patterns with the generated identifier
837
- if (
838
- (declarator.id.type === 'ObjectPattern' || declarator.id.type === 'ArrayPattern') &&
839
- declarator.id.lazy &&
840
- declarator.id.metadata?.lazy_id
841
- ) {
842
- declarator.id = b.id(declarator.id.metadata.lazy_id);
843
- }
844
- }
845
- }
846
-
847
- return context.next();
848
- },
849
-
850
- Element(node, context) {
851
- const { state, visit } = context;
852
-
853
- const dynamic_name = state.dynamicElementName;
854
- if (dynamic_name) {
855
- state.dynamicElementName = undefined;
856
- }
857
-
858
- const is_dom_element = !!dynamic_name || is_element_dom_element(node);
859
- const is_spreading = node.attributes.some((attr) => attr.type === 'SpreadAttribute');
860
- /** @type {(AST.Property | AST.SpreadElement)[] | null} */
861
- const spread_attributes = is_spreading ? [] : null;
862
- const child_namespace =
863
- !dynamic_name && is_dom_element
864
- ? determine_namespace_for_children(
865
- /** @type {AST.Identifier} */ (node.id).name,
866
- state.namespace,
867
- )
868
- : state.namespace;
869
-
870
- if (is_dom_element) {
871
- const is_void = dynamic_name
872
- ? false
873
- : is_void_element(/** @type {AST.Identifier} */ (node.id).name);
874
- const use_self_closing_syntax = node.selfClosing && (is_void || !!dynamic_name);
875
- const tag_name = dynamic_name
876
- ? dynamic_name
877
- : b.literal(/** @type {AST.Identifier} */ (node.id).name);
878
- /** @type {AST.CSS.StyleSheet['hash'] | null} */
879
- const scoping_hash =
880
- state.applyParentCssScope ??
881
- (node.metadata.scoped && state.component?.css
882
- ? /** @type {AST.CSS.StyleSheet} */ (state.component?.css).hash
883
- : null);
884
-
885
- state.init?.push(
886
- b.stmt(
887
- b.call(
888
- b.member(b.id('__output'), b.id('push')),
889
- dynamic_name
890
- ? b.template([b.quasi('<', false), b.quasi('', false)], [tag_name])
891
- : b.literal('<' + /** @type {AST.Literal} */ (tag_name).value),
892
- ),
893
- ),
894
- );
895
- let class_attribute = null;
896
-
897
- /**
898
- * @param {string} name
899
- * @param {string | number | bigint | boolean | RegExp | null | undefined} value
900
- * @param {'push' | 'unshift'} [spread_method]
901
- */
902
- const handle_static_attr = (name, value, spread_method = 'push') => {
903
- if (is_spreading) {
904
- // For spread attributes, store just the actual value, not the full attribute string
905
- const actual_value =
906
- is_boolean_attribute(name) && value === true
907
- ? b.literal(true)
908
- : b.literal(value === true ? '' : value);
909
-
910
- // spread_attributes cannot be null based on is_spreading === true
911
- /** @type {(AST.Property | AST.SpreadElement)[]} */ (spread_attributes)[spread_method](
912
- b.prop('init', b.literal(name), actual_value),
913
- );
914
- } else {
915
- const attr_str = ` ${name}${
916
- is_boolean_attribute(name) && value === true
917
- ? ''
918
- : `="${value === true ? '' : escape_html(value, true)}"`
919
- }`;
920
-
921
- state.init?.push(
922
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(attr_str))),
923
- );
924
- }
925
- };
926
-
927
- for (const attr of node.attributes) {
928
- if (attr.type === 'Attribute') {
929
- if (attr.name.type === 'Identifier') {
930
- const name = attr.name.name;
931
-
932
- if (attr.value === null) {
933
- handle_static_attr(name, true);
934
- continue;
935
- }
936
-
937
- if (attr.value.type === 'Literal' && name !== 'class') {
938
- handle_static_attr(name, attr.value.value);
939
- continue;
940
- }
941
-
942
- if (name === 'class') {
943
- class_attribute = attr;
944
-
945
- continue;
946
- }
947
-
948
- if (is_event_attribute(name)) {
949
- continue;
950
- }
951
- const metadata = { tracking: false, await: false };
952
- const expression = /** @type {AST.Expression} */ (
953
- visit(attr.value, { ...state, metadata })
954
- );
955
-
956
- state.init?.push(
957
- b.stmt(
958
- b.call(
959
- b.member(b.id('__output'), b.id('push')),
960
- b.call(
961
- '_$_.attr',
962
- b.literal(name),
963
- expression,
964
- b.literal(is_boolean_attribute(name)),
965
- ),
966
- ),
967
- ),
968
- );
969
- }
970
- } else if (attr.type === 'SpreadAttribute') {
971
- spread_attributes?.push(
972
- b.spread(/** @type {AST.Expression} */ (visit(attr.argument, state))),
973
- );
974
- }
975
- }
976
-
977
- if (class_attribute !== null) {
978
- const attr_value = /** @type {AST.Expression} */ (class_attribute.value);
979
- if (attr_value.type === 'Literal') {
980
- let value = attr_value.value;
981
-
982
- if (scoping_hash) {
983
- value = `${scoping_hash} ${value}`;
984
- }
985
-
986
- handle_static_attr(class_attribute.name.name, value);
987
- } else {
988
- const metadata = { tracking: false, await: false };
989
- let expression = /** @type {AST.Expression} */ (
990
- visit(attr_value, { ...state, metadata })
991
- );
992
-
993
- if (scoping_hash) {
994
- // Pass array to clsx so it can handle objects properly
995
- expression = b.array([expression, b.literal(scoping_hash)]);
996
- }
997
-
998
- state.init?.push(
999
- b.stmt(
1000
- b.call(
1001
- b.member(b.id('__output'), b.id('push')),
1002
- b.call('_$_.attr', b.literal('class'), expression),
1003
- ),
1004
- ),
1005
- );
1006
- }
1007
- } else if (scoping_hash) {
1008
- handle_static_attr('class', scoping_hash, is_spreading ? 'unshift' : 'push');
1009
- }
1010
-
1011
- if (spread_attributes !== null && spread_attributes.length > 0) {
1012
- state.init?.push(
1013
- b.stmt(
1014
- b.call(
1015
- b.member(b.id('__output'), b.id('push')),
1016
- b.call(
1017
- '_$_.spread_attrs',
1018
- b.object(spread_attributes),
1019
- scoping_hash ? b.literal(scoping_hash) : undefined,
1020
- ),
1021
- ),
1022
- ),
1023
- );
1024
- }
1025
-
1026
- state.init?.push(
1027
- b.stmt(
1028
- b.call(
1029
- b.member(b.id('__output'), b.id('push')),
1030
- b.literal(use_self_closing_syntax ? ' />' : '>'),
1031
- ),
1032
- ),
1033
- );
1034
-
1035
- // In dev mode, emit push_element for runtime nesting validation
1036
- if (state.dev && !dynamic_name) {
1037
- const element_name = /** @type {AST.Identifier} */ (node.id).name;
1038
- const loc = node.loc;
1039
- state.init?.push(
1040
- b.stmt(
1041
- b.call(
1042
- '_$_.push_element',
1043
- b.literal(element_name),
1044
- b.literal(state.filename),
1045
- b.literal(loc?.start.line ?? 0),
1046
- b.literal(loc?.start.column ?? 0),
1047
- ),
1048
- ),
1049
- );
1050
- }
1051
-
1052
- if (!is_void) {
1053
- /** @type {AST.Statement[]} */
1054
- const init = [];
1055
- transform_children(
1056
- node.children,
1057
- /** @type {TransformServerContext} */ ({
1058
- visit,
1059
- state: {
1060
- ...state,
1061
- init,
1062
- ...(state.applyParentCssScope ||
1063
- (dynamic_name && node.metadata.scoped && state.component?.css)
1064
- ? {
1065
- applyParentCssScope:
1066
- state.applyParentCssScope ||
1067
- /** @type {AST.CSS.StyleSheet} */ (state.component?.css).hash,
1068
- }
1069
- : {}),
1070
- },
1071
- }),
1072
- );
1073
-
1074
- if (init.length > 0) {
1075
- state.init?.push(b.block(init));
1076
- }
1077
-
1078
- if (!use_self_closing_syntax) {
1079
- state.init?.push(
1080
- b.stmt(
1081
- b.call(
1082
- b.member(b.id('__output'), b.id('push')),
1083
- dynamic_name
1084
- ? b.template([b.quasi('</', false), b.quasi('>', false)], [tag_name])
1085
- : b.literal('</' + /** @type {AST.Literal} */ (tag_name).value + '>'),
1086
- ),
1087
- ),
1088
- );
1089
- }
1090
- }
1091
-
1092
- // In dev mode, emit pop_element after the element is fully rendered
1093
- if (state.dev && !dynamic_name) {
1094
- state.init?.push(b.stmt(b.call('_$_.pop_element')));
1095
- }
1096
- } else {
1097
- /** @type {(AST.Property | AST.SpreadElement)[]} */
1098
- const props = [];
1099
- /** @type {AST.Property | null} */
1100
- let children_prop = null;
1101
-
1102
- const apply_parent_css_scope = state.applyParentCssScope;
1103
-
1104
- for (const attr of node.attributes) {
1105
- if (attr.type === 'Attribute') {
1106
- if (attr.name.type === 'Identifier') {
1107
- const metadata = { tracking: false, await: false };
1108
- let property =
1109
- attr.value === null
1110
- ? b.literal(true)
1111
- : /** @type {AST.Expression} */ (
1112
- visit(/** @type {AST.Expression} */ (attr.value), {
1113
- ...state,
1114
- metadata,
1115
- })
1116
- );
1117
-
1118
- if (attr.name.name === 'children') {
1119
- children_prop = b.prop(
1120
- 'init',
1121
- b.id('children'),
1122
- b.call('_$_.normalize_children', property),
1123
- );
1124
- continue;
1125
- }
1126
-
1127
- props.push(b.prop('init', b.key(attr.name.name), property));
1128
- }
1129
- } else if (attr.type === 'SpreadAttribute') {
1130
- props.push(
1131
- b.spread(
1132
- /** @type {AST.Expression} */ (
1133
- visit(attr.argument, { ...state, metadata: { ...state.metadata } })
1134
- ),
1135
- ),
1136
- );
1137
- }
1138
- }
1139
-
1140
- const children_filtered = node.children.filter(
1141
- (child) => child.type !== 'EmptyStatement' && child.type !== 'Component',
1142
- );
1143
-
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,
1170
- );
1171
- } else {
1172
- children_prop = b.prop('init', b.id('children'), children);
1173
- }
1174
- }
1175
-
1176
- if (children_prop) {
1177
- props.push(children_prop);
1178
- }
1179
-
1180
- // For SSR, determine if we should await based on component metadata
1181
- const args = [b.id('__output'), b.object(props)];
1182
-
1183
- // Check if this is a locally defined component and if it's async
1184
- const component_name = node.id.type === 'Identifier' ? node.id.name : null;
1185
- const local_metadata = component_name
1186
- ? state.component_metadata.find((m) => m.id === component_name)
1187
- : null;
1188
- const comp_id = b.id('comp');
1189
- const args_id = b.id('args');
1190
- const comp_call = b.call(comp_id, b.spread(args_id));
1191
- const comp_call_regular = b.stmt(comp_call);
1192
- const comp_call_await = b.stmt(b.await(comp_call));
1193
-
1194
- /** @type {AST.Statement[]} */
1195
- const init = [];
1196
- const visited_id = /** @type {AST.Expression} */ (visit(node.id, state));
1197
- /** @type {AST.Statement[]} */
1198
- const statements = [
1199
- b.const(comp_id, is_element_dynamic(node) ? b.call('_$_.get', visited_id) : visited_id),
1200
- b.const(args_id, b.array(args)),
1201
- ];
1202
-
1203
- if (local_metadata) {
1204
- if (local_metadata?.async) {
1205
- statements.push(comp_call_await);
1206
-
1207
- if (state.metadata?.await === false) {
1208
- state.metadata.await = true;
1209
- }
1210
- } else {
1211
- statements.push(comp_call_regular);
1212
- }
1213
- } else if (!is_element_dynamic(node)) {
1214
- // it's imported element, so it could be async
1215
- statements.push(
1216
- b.if(
1217
- b.member(comp_id, b.id('async'), false, true),
1218
- b.block([comp_call_await]),
1219
- b.if(comp_id, b.block([comp_call_regular])),
1220
- ),
1221
- );
1222
-
1223
- if (state.metadata?.await === false) {
1224
- state.metadata.await = true;
1225
- }
1226
- } else {
1227
- // if it's a dynamic element, build the element output
1228
- // and store the results in the `init` array
1229
- visit(
1230
- node,
1231
- /** @type {TransformServerState} */ ({
1232
- ...state,
1233
- dynamicElementName: b.template([b.quasi('', false), b.quasi('', false)], [comp_id]),
1234
- init,
1235
- }),
1236
- );
1237
-
1238
- statements.push(
1239
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_OPEN))),
1240
- );
1241
-
1242
- statements.push(
1243
- b.if(
1244
- b.binary('===', b.unary('typeof', comp_id), b.literal('function')),
1245
- b.block([
1246
- b.if(
1247
- b.member(comp_id, b.id('async')),
1248
- b.block([comp_call_await]),
1249
- b.block([comp_call_regular]),
1250
- ),
1251
- ]),
1252
- // make sure that falsy values for dynamic element or component don't get rendered
1253
- b.if(comp_id, b.block(init)),
1254
- ),
1255
- );
1256
-
1257
- statements.push(
1258
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_CLOSE))),
1259
- );
1260
-
1261
- // Mark parent component as async since this child component could potentially be async
1262
- if (state.metadata?.await === false) {
1263
- state.metadata.await = true;
1264
- }
1265
- }
1266
-
1267
- state.init?.push(b.block(statements));
1268
- }
1269
- },
1270
-
1271
- SwitchStatement(node, context) {
1272
- if (!is_inside_component(context)) {
1273
- return context.next();
1274
- }
1275
-
1276
- const cases = [];
1277
-
1278
- for (const switch_case of node.cases) {
1279
- const case_body = [];
1280
-
1281
- if (switch_case.consequent.length !== 0) {
1282
- const flattened_consequent = flatten_switch_consequent(switch_case.consequent);
1283
- const consequent_scope =
1284
- context.state.scopes.get(switch_case.consequent) || context.state.scope;
1285
- const consequent = b.block(
1286
- transform_body(flattened_consequent, {
1287
- ...context,
1288
- state: { ...context.state, scope: consequent_scope },
1289
- }),
1290
- );
1291
- case_body.push(...consequent.body);
1292
- }
1293
-
1294
- cases.push(
1295
- b.switch_case(
1296
- switch_case.test ? /** @type {AST.Expression} */ (context.visit(switch_case.test)) : null,
1297
- case_body,
1298
- ),
1299
- );
1300
- }
1301
-
1302
- context.state.init?.push(
1303
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_OPEN))),
1304
- );
1305
-
1306
- context.state.init?.push(
1307
- b.switch(/** @type {AST.Expression} */ (context.visit(node.discriminant)), cases),
1308
- );
1309
-
1310
- context.state.init?.push(
1311
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_CLOSE))),
1312
- );
1313
- },
1314
-
1315
- ForOfStatement(node, context) {
1316
- if (!is_inside_component(context)) {
1317
- context.next();
1318
- return;
1319
- }
1320
- const body_scope = context.state.scopes.get(node.body);
1321
-
1322
- context.state.init?.push(
1323
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_OPEN))),
1324
- );
1325
-
1326
- const body = transform_body(/** @type {AST.BlockStatement} */ (node.body).body, {
1327
- ...context,
1328
- state: { ...context.state, scope: /** @type {ScopeInterface} */ (body_scope) },
1329
- });
1330
-
1331
- if (node.index) {
1332
- context.state.init?.push(b.var(node.index, b.literal(0)));
1333
- body.push(b.stmt(b.update('++', node.index)));
1334
- }
1335
-
1336
- context.state.init?.push(
1337
- b.for_of(
1338
- /** @type {AST.VariableDeclaration} */ (context.visit(node.left)),
1339
- /** @type {AST.Expression} */
1340
- (context.visit(node.right)),
1341
- b.block(body),
1342
- ),
1343
- );
1344
-
1345
- context.state.init?.push(
1346
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_CLOSE))),
1347
- );
1348
- },
1349
-
1350
- IfStatement(node, context) {
1351
- if (!is_inside_component(context)) {
1352
- context.next();
1353
- return;
1354
- }
1355
-
1356
- const consequent_body =
1357
- node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
1358
-
1359
- const consequent = b.block(
1360
- transform_body(consequent_body, {
1361
- ...context,
1362
- state: {
1363
- ...context.state,
1364
- scope: /** @type {ScopeInterface} */ (
1365
- context.state.scopes.get(node.consequent) || context.state.scope
1366
- ),
1367
- },
1368
- }),
1369
- );
1370
-
1371
- context.state.init?.push(
1372
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_OPEN))),
1373
- );
1374
-
1375
- /** @type {AST.BlockStatement | AST.IfStatement | null} */
1376
- let alternate = null;
1377
- if (node.alternate) {
1378
- const alternate_scope = context.state.scopes.get(node.alternate) || context.state.scope;
1379
- const alternate_body_nodes =
1380
- node.alternate.type === 'IfStatement'
1381
- ? [node.alternate]
1382
- : /** @type {AST.BlockStatement} */ (node.alternate).body;
1383
-
1384
- alternate = b.block(
1385
- transform_body(alternate_body_nodes, {
1386
- ...context,
1387
- state: { ...context.state, scope: alternate_scope },
1388
- }),
1389
- );
1390
- }
1391
-
1392
- context.state.init?.push(
1393
- b.if(/** @type {AST.Expression} */ (context.visit(node.test)), consequent, alternate),
1394
- );
1395
-
1396
- context.state.init?.push(
1397
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_CLOSE))),
1398
- );
1399
- },
1400
-
1401
- ReturnStatement(node, context) {
1402
- if (!is_inside_component(context)) {
1403
- return context.next();
1404
- }
1405
- const info = context.state.return_flags?.get(node);
1406
- if (info) {
1407
- return b.stmt(b.assignment('=', b.id(info.name), b.true));
1408
- }
1409
- return context.next();
1410
- },
1411
-
1412
- AssignmentExpression(node, context) {
1413
- const left = node.left;
1414
-
1415
- // Handle lazy binding assignments (e.g., a = 5 where a is from let &{a} = obj)
1416
- if (left.type === 'Identifier') {
1417
- const binding = context.state.scope?.get(left.name);
1418
- if (binding?.transform?.assign && binding.node !== left) {
1419
- let value = /** @type {AST.Expression} */ (context.visit(node.right));
1420
-
1421
- // For compound operators (+=, -=, *=, /=), expand to read + operation
1422
- if (node.operator !== '=') {
1423
- const operator = node.operator.slice(0, -1); // '+=' -> '+'
1424
- const current = binding.transform.read(left);
1425
- value = b.binary(/** @type {AST.BinaryOperator} */ (operator), current, value);
1426
- }
1427
-
1428
- return binding.transform.assign(left, value);
1429
- }
1430
- }
1431
-
1432
- return context.next();
1433
- },
1434
-
1435
- UpdateExpression(node, context) {
1436
- const argument = node.argument;
1437
-
1438
- // Handle lazy binding updates (e.g., a++ where a is from let &{a} = obj)
1439
- if (argument.type === 'Identifier') {
1440
- const binding = context.state.scope?.get(argument.name);
1441
- if (binding?.transform?.update && binding.node !== argument) {
1442
- return binding.transform.update(node);
1443
- }
1444
- }
1445
- },
1446
-
1447
- ServerIdentifier(node, context) {
1448
- return b.id('_$_server_$_');
1449
- },
1450
-
1451
- StyleIdentifier(node, context) {
1452
- return b.id(STYLE_IDENTIFIER);
1453
- },
1454
-
1455
- ImportDeclaration(node, context) {
1456
- const { state } = context;
1457
-
1458
- if (!state.to_ts && node.importKind === 'type') {
1459
- return b.empty;
1460
- }
1461
-
1462
- if (state.ancestor_server_block) {
1463
- if (!node.specifiers.length) {
1464
- return b.empty;
1465
- }
1466
-
1467
- /** @type {AST.VariableDeclaration[]} */
1468
- const locals = state.server_block_locals;
1469
- for (const spec of node.specifiers) {
1470
- const original_name = spec.local.name;
1471
- const name = obfuscate_identifier(original_name);
1472
- spec.local = b.id(name);
1473
- locals.push(b.const(original_name, b.id(name)));
1474
- }
1475
- state.imports.add(node);
1476
- return b.empty;
1477
- }
1478
-
1479
- return /** @type {AST.ImportDeclaration} */ ({
1480
- ...node,
1481
- specifiers: node.specifiers
1482
- .filter((spec) => /** @type {AST.ImportSpecifier} */ (spec).importKind !== 'type')
1483
- .map((spec) => context.visit(spec)),
1484
- });
1485
- },
1486
-
1487
- TryStatement(node, context) {
1488
- if (!is_inside_component(context)) {
1489
- return context.next();
1490
- }
1491
-
1492
- // If there's a pending block, this is an async operation
1493
- const has_pending = node.pending !== null;
1494
- if (has_pending && context.state.metadata?.await === false) {
1495
- context.state.metadata.await = true;
1496
- }
1497
-
1498
- const metadata = { await: false };
1499
- const body = transform_body(node.block.body, {
1500
- ...context,
1501
- state: { ...context.state, metadata },
1502
- });
1503
-
1504
- // Check if the try block itself contains async operations
1505
- const is_async = metadata.await || has_pending;
1506
-
1507
- if (is_async) {
1508
- if (context.state.metadata?.await === false) {
1509
- context.state.metadata.await = true;
1510
- }
1511
-
1512
- const pending_position_name = node.pending
1513
- ? context.state.scope.generate('__pending_pos')
1514
- : null;
1515
-
1516
- // Render pending block first, saving position so we can remove it after async resolves
1517
- if (node.pending) {
1518
- context.state.init?.push(
1519
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_OPEN))),
1520
- );
1521
- const pending_body = transform_body(node.pending.body, {
1522
- ...context,
1523
- state: {
1524
- ...context.state,
1525
- scope: /** @type {ScopeInterface} */ (context.state.scopes.get(node.pending)),
1526
- },
1527
- });
1528
- context.state.init?.push(
1529
- b.var(
1530
- b.id(/** @type {string} */ (pending_position_name)),
1531
- b.member(b.member(b.id('__output'), b.id('body')), b.id('length')),
1532
- ),
1533
- );
1534
- context.state.init?.push(...pending_body);
1535
- }
1536
-
1537
- // For SSR with pending block: render the resolved content wrapped in async
1538
- // In a streaming SSR implementation, we'd render pending first, then stream resolved
1539
- const handler = node.handler;
1540
- /** @type {AST.Statement[]} */
1541
- let try_statements = body;
1542
- if (handler != null) {
1543
- try_statements = [
1544
- b.try(
1545
- b.block(body),
1546
- b.catch_clause(
1547
- handler.param || b.id('error'),
1548
- b.block(
1549
- transform_body(handler.body.body, {
1550
- ...context,
1551
- state: {
1552
- ...context.state,
1553
- scope: /** @type {ScopeInterface} */ (context.state.scopes.get(handler.body)),
1554
- },
1555
- }),
1556
- ),
1557
- ),
1558
- ),
1559
- ];
1560
- }
1561
-
1562
- // Remove pending content before rendering resolved/catch content
1563
- if (node.pending) {
1564
- try_statements = [
1565
- b.stmt(
1566
- b.assignment(
1567
- '=',
1568
- b.member(b.id('__output'), b.id('body')),
1569
- b.call(
1570
- b.member(b.member(b.id('__output'), b.id('body')), b.id('slice')),
1571
- b.literal(0),
1572
- b.id(/** @type {string} */ (pending_position_name)),
1573
- ),
1574
- ),
1575
- ),
1576
- ...try_statements,
1577
- ];
1578
- }
1579
-
1580
- context.state.init?.push(
1581
- b.stmt(b.await(b.call('_$_.async', b.thunk(b.block(try_statements), true)))),
1582
- );
1583
-
1584
- if (node.pending) {
1585
- context.state.init?.push(
1586
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_CLOSE))),
1587
- );
1588
- }
1589
- } else {
1590
- // No async, just regular try/catch
1591
- if (node.handler != null) {
1592
- const handler_body = transform_body(node.handler.body.body, {
1593
- ...context,
1594
- state: {
1595
- ...context.state,
1596
- scope: /** @type {ScopeInterface} */ (context.state.scopes.get(node.handler.body)),
1597
- },
1598
- });
1599
-
1600
- context.state.init?.push(
1601
- b.try(
1602
- b.block(body),
1603
- b.catch_clause(node.handler.param || b.id('error'), b.block(handler_body)),
1604
- ),
1605
- );
1606
- } else {
1607
- context.state.init?.push(...body);
1608
- }
1609
- }
1610
- },
1611
-
1612
- AwaitExpression(node, context) {
1613
- const { state } = context;
1614
-
1615
- if (state.to_ts) {
1616
- return context.next();
1617
- }
1618
-
1619
- if (state.metadata?.await === false) {
1620
- state.metadata.await = true;
1621
- }
1622
-
1623
- return b.await(/** @type {AST.AwaitExpression} */ (context.visit(node.argument)));
1624
- },
1625
-
1626
- MemberExpression(node, context) {
1627
- return context.next();
1628
- },
1629
-
1630
- RippleExpression(node, { visit, state }) {
1631
- const metadata = { await: false };
1632
- let expression = /** @type {AST.Expression} */ (visit(node.expression, { ...state, metadata }));
1633
- const is_children_expression = is_children_template_expression(node.expression, state.scope);
1634
-
1635
- if (expression.type === 'Literal') {
1636
- state.init?.push(
1637
- b.stmt(
1638
- b.call(b.member(b.id('__output'), b.id('push')), b.literal(escape(expression.value))),
1639
- ),
1640
- );
1641
- } else if (is_children_expression) {
1642
- state.init?.push(b.stmt(b.call('_$_.render_expression', b.id('__output'), expression)));
1643
- } else {
1644
- state.init?.push(
1645
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.call('_$_.escape', expression))),
1646
- );
1647
- }
1648
- },
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
-
1700
- Html(node, { visit, state }) {
1701
- const metadata = { await: false };
1702
- const expression = /** @type {AST.Expression} */ (
1703
- visit(node.expression, { ...state, metadata })
1704
- );
1705
-
1706
- // For literal values, compute hash at build time
1707
- if (expression.type === 'Literal') {
1708
- const value = String(expression.value ?? '');
1709
- const hash_value = hash(value);
1710
- // Push hash comment
1711
- state.init?.push(
1712
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(`<!--${hash_value}-->`))),
1713
- );
1714
- // Push the HTML content
1715
- state.init?.push(b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(value))));
1716
- // Push empty comment as end marker
1717
- state.init?.push(
1718
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal('<!---->'))),
1719
- );
1720
- } else {
1721
- // For dynamic values, compute hash at runtime
1722
- // Create a variable to store the value
1723
- const value_id = state.scope?.generate('html_value');
1724
- if (value_id) {
1725
- state.init?.push(
1726
- b.const(value_id, b.call(b.id('String'), b.logical('??', expression, b.literal('')))),
1727
- );
1728
- // Compute hash at runtime using _$_.hash and push as comment
1729
- state.init?.push(
1730
- b.stmt(
1731
- b.call(
1732
- b.member(b.id('__output'), b.id('push')),
1733
- b.binary(
1734
- '+',
1735
- b.binary('+', b.literal('<!--'), b.call('_$_.hash', b.id(value_id))),
1736
- b.literal('-->'),
1737
- ),
1738
- ),
1739
- ),
1740
- );
1741
- // Push the HTML content
1742
- state.init?.push(b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.id(value_id))));
1743
- // Push empty comment as end marker
1744
- state.init?.push(
1745
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal('<!---->'))),
1746
- );
1747
- }
1748
- }
1749
- },
1750
-
1751
- ScriptContent(node, context) {
1752
- context.state.init?.push(
1753
- b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(node.content))),
1754
- );
1755
- },
1756
-
1757
- ServerBlock(node, context) {
1758
- const exports = node.metadata.exports;
1759
-
1760
- // Convert Imports inside ServerBlock to local variables
1761
- // ImportDeclaration() visitor will add imports to the top of the module
1762
- /** @type {AST.VariableDeclaration[]} */
1763
- const server_block_locals = [];
1764
-
1765
- const block = /** @type {AST.BlockStatement} */ (
1766
- context.visit(node.body, {
1767
- ...context.state,
1768
- ancestor_server_block: node,
1769
- server_block_locals,
1770
- server_exported_names: [],
1771
- })
1772
- );
1773
-
1774
- if (exports.size === 0) {
1775
- return {
1776
- ...block,
1777
- body: [...server_block_locals, ...block.body],
1778
- };
1779
- }
1780
-
1781
- const file_path = context.state.filename;
1782
- const rpc_modules = globalThis.rpc_modules;
1783
-
1784
- if (rpc_modules) {
1785
- for (const name of exports) {
1786
- const func_path = file_path + '#' + name;
1787
- // needs to be a sha256 hash of func_path, to avoid leaking file structure
1788
- const hash = createHash('sha256').update(func_path).digest('hex').slice(0, 8);
1789
- rpc_modules.set(hash, [file_path, name]);
1790
- }
1791
- }
1792
-
1793
- return b.export(
1794
- b.const(
1795
- '_$_server_$_',
1796
- b.call(
1797
- b.thunk(
1798
- b.block([
1799
- b.var('_$_server_$_', b.object([])),
1800
- ...server_block_locals,
1801
- ...block.body,
1802
- b.return(b.id('_$_server_$_')),
1803
- ]),
1804
- ),
1805
- ),
1806
- ),
1807
- );
1808
- },
1809
-
1810
- Program(node, context) {
1811
- // We need a Program visitor to make sure all top level entities are visited
1812
- // Without it, and without at least one export component
1813
- // other components are not visited
1814
- /** @type {Array<AST.Statement | AST.Directive | AST.ModuleDeclaration>} */
1815
- const statements = [];
1816
-
1817
- for (const statement of node.body) {
1818
- statements.push(
1819
- /** @type {AST.Statement | AST.Directive | AST.ModuleDeclaration} */ (
1820
- context.visit(statement)
1821
- ),
1822
- );
1823
- }
1824
-
1825
- return { ...node, body: statements };
1826
- },
1827
- };
1828
-
1829
- /**
1830
- * @param {string} filename
1831
- * @param {string} source
1832
- * @param {AnalysisResult} analysis
1833
- * @param {boolean} minify_css
1834
- * @param {boolean} [dev]
1835
- * @returns {{ ast: AST.Program; js: { code: string; map: RawSourceMap | null }; css: string; }}
1836
- */
1837
- export function transform_server(filename, source, analysis, minify_css, dev = false) {
1838
- // Use component metadata collected during the analyze phase
1839
- const component_metadata = analysis.component_metadata || [];
1840
-
1841
- /** @type {TransformServerState} */
1842
- const state = {
1843
- imports: new Set(),
1844
- init: null,
1845
- scope: analysis.scope,
1846
- scopes: analysis.scopes,
1847
- serverIdentifierPresent: analysis.metadata.serverIdentifierPresent,
1848
- stylesheets: [],
1849
- component_metadata,
1850
- ancestor_server_block: undefined,
1851
- server_block_locals: [],
1852
- server_exported_names: [],
1853
- filename,
1854
- namespace: 'html',
1855
- // TODO: should we remove all `to_ts` usages we use the client rendering for that?
1856
- to_ts: false,
1857
- metadata: {},
1858
- dev,
1859
- };
1860
-
1861
- state.imports.add(`import * as _$_ from 'ripple/internal/server'`);
1862
-
1863
- const program = /** @type {AST.Program} */ (walk(analysis.ast, { ...state }, visitors));
1864
-
1865
- const css = render_stylesheets(state.stylesheets, minify_css);
1866
-
1867
- // Add CSS registration if there are stylesheets
1868
- if (state.stylesheets.length > 0 && css) {
1869
- // Register each stylesheet's CSS
1870
- for (const stylesheet of state.stylesheets) {
1871
- const css_for_component = render_stylesheets([stylesheet]);
1872
- /** @type {AST.Program} */ (program).body.push(
1873
- b.stmt(
1874
- b.call('_$_.register_css', b.literal(stylesheet.hash), b.literal(css_for_component)),
1875
- ),
1876
- );
1877
- }
1878
- }
1879
-
1880
- // Add async property to component functions
1881
- for (const import_node of state.imports) {
1882
- if (typeof import_node === 'string') {
1883
- program.body.unshift(b.stmt(b.id(import_node)));
1884
- } else {
1885
- program.body.unshift(import_node);
1886
- }
1887
- }
1888
-
1889
- const js = print(program, /** @type {Visitors<AST.Node, TransformServerState>} */ (ts()), {
1890
- sourceMapContent: source,
1891
- sourceMapSource: path.basename(filename),
1892
- });
1893
-
1894
- return {
1895
- ast: /** @type {AST.Program} */ (program),
1896
- js,
1897
- css,
1898
- };
1899
- }