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