@kithinji/pod 1.0.0

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 (98) hide show
  1. package/build.js +22 -0
  2. package/dist/main.js +4464 -0
  3. package/dist/main.js.map +7 -0
  4. package/dist/types/add/component/component.d.ts +5 -0
  5. package/dist/types/add/component/component.d.ts.map +1 -0
  6. package/dist/types/add/component/index.d.ts +2 -0
  7. package/dist/types/add/component/index.d.ts.map +1 -0
  8. package/dist/types/add/index.d.ts +4 -0
  9. package/dist/types/add/index.d.ts.map +1 -0
  10. package/dist/types/add/module/index.d.ts +2 -0
  11. package/dist/types/add/module/index.d.ts.map +1 -0
  12. package/dist/types/add/module/module.d.ts +3 -0
  13. package/dist/types/add/module/module.d.ts.map +1 -0
  14. package/dist/types/add/new/index.d.ts +2 -0
  15. package/dist/types/add/new/index.d.ts.map +1 -0
  16. package/dist/types/config/config.d.ts +18 -0
  17. package/dist/types/config/config.d.ts.map +1 -0
  18. package/dist/types/config/index.d.ts +2 -0
  19. package/dist/types/config/index.d.ts.map +1 -0
  20. package/dist/types/dev/index.d.ts +2 -0
  21. package/dist/types/dev/index.d.ts.map +1 -0
  22. package/dist/types/dev/project.d.ts +9 -0
  23. package/dist/types/dev/project.d.ts.map +1 -0
  24. package/dist/types/dev/server.d.ts +2 -0
  25. package/dist/types/dev/server.d.ts.map +1 -0
  26. package/dist/types/docker/docker.d.ts +2 -0
  27. package/dist/types/docker/docker.d.ts.map +1 -0
  28. package/dist/types/docker/index.d.ts +2 -0
  29. package/dist/types/docker/index.d.ts.map +1 -0
  30. package/dist/types/macros/expand_macros.d.ts +48 -0
  31. package/dist/types/macros/expand_macros.d.ts.map +1 -0
  32. package/dist/types/macros/index.d.ts +3 -0
  33. package/dist/types/macros/index.d.ts.map +1 -0
  34. package/dist/types/macros/macro_executer.d.ts +12 -0
  35. package/dist/types/macros/macro_executer.d.ts.map +1 -0
  36. package/dist/types/main.d.ts +13 -0
  37. package/dist/types/main.d.ts.map +1 -0
  38. package/dist/types/plugins/analyzers/graph.d.ts +25 -0
  39. package/dist/types/plugins/analyzers/graph.d.ts.map +1 -0
  40. package/dist/types/plugins/css/index.d.ts +7 -0
  41. package/dist/types/plugins/css/index.d.ts.map +1 -0
  42. package/dist/types/plugins/generators/generate_controller.d.ts +2 -0
  43. package/dist/types/plugins/generators/generate_controller.d.ts.map +1 -0
  44. package/dist/types/plugins/generators/generate_rsc.d.ts +2 -0
  45. package/dist/types/plugins/generators/generate_rsc.d.ts.map +1 -0
  46. package/dist/types/plugins/generators/generate_server_component.d.ts +2 -0
  47. package/dist/types/plugins/generators/generate_server_component.d.ts.map +1 -0
  48. package/dist/types/plugins/generators/tsx_server_stub.d.ts +2 -0
  49. package/dist/types/plugins/generators/tsx_server_stub.d.ts.map +1 -0
  50. package/dist/types/plugins/index.d.ts +4 -0
  51. package/dist/types/plugins/index.d.ts.map +1 -0
  52. package/dist/types/plugins/my.d.ts +10 -0
  53. package/dist/types/plugins/my.d.ts.map +1 -0
  54. package/dist/types/plugins/transformers/j2d.d.ts +11 -0
  55. package/dist/types/plugins/transformers/j2d.d.ts.map +1 -0
  56. package/dist/types/store/index.d.ts +2 -0
  57. package/dist/types/store/index.d.ts.map +1 -0
  58. package/dist/types/store/store.d.ts +14 -0
  59. package/dist/types/store/store.d.ts.map +1 -0
  60. package/dist/types/utils/cases.d.ts +4 -0
  61. package/dist/types/utils/cases.d.ts.map +1 -0
  62. package/dist/types/utils/create.d.ts +12 -0
  63. package/dist/types/utils/create.d.ts.map +1 -0
  64. package/dist/types/utils/index.d.ts +3 -0
  65. package/dist/types/utils/index.d.ts.map +1 -0
  66. package/package.json +44 -0
  67. package/src/add/component/component.ts +496 -0
  68. package/src/add/component/index.ts +1 -0
  69. package/src/add/index.ts +3 -0
  70. package/src/add/module/index.ts +1 -0
  71. package/src/add/module/module.ts +521 -0
  72. package/src/add/new/index.ts +135 -0
  73. package/src/config/config.ts +141 -0
  74. package/src/config/index.ts +1 -0
  75. package/src/dev/index.ts +1 -0
  76. package/src/dev/project.ts +45 -0
  77. package/src/dev/server.ts +190 -0
  78. package/src/docker/docker.ts +452 -0
  79. package/src/docker/index.ts +1 -0
  80. package/src/macros/expand_macros.ts +791 -0
  81. package/src/macros/index.ts +2 -0
  82. package/src/macros/macro_executer.ts +189 -0
  83. package/src/main.ts +95 -0
  84. package/src/plugins/analyzers/graph.ts +291 -0
  85. package/src/plugins/css/index.ts +25 -0
  86. package/src/plugins/generators/generate_controller.ts +308 -0
  87. package/src/plugins/generators/generate_rsc.ts +274 -0
  88. package/src/plugins/generators/generate_server_component.ts +279 -0
  89. package/src/plugins/generators/tsx_server_stub.ts +295 -0
  90. package/src/plugins/index.ts +3 -0
  91. package/src/plugins/my.ts +274 -0
  92. package/src/plugins/transformers/j2d.ts +1014 -0
  93. package/src/store/index.ts +1 -0
  94. package/src/store/store.ts +44 -0
  95. package/src/utils/cases.ts +15 -0
  96. package/src/utils/create.ts +26 -0
  97. package/src/utils/index.ts +2 -0
  98. package/tsconfig.json +27 -0
@@ -0,0 +1,1014 @@
1
+ import type { PluginObj, NodePath } from "@babel/core";
2
+ import * as BabelTypes from "@babel/types";
3
+
4
+ interface PluginContext {
5
+ types: typeof BabelTypes;
6
+ }
7
+
8
+ interface PluginState {
9
+ helpersImported?: boolean;
10
+ }
11
+
12
+ interface TransformContext {
13
+ observables: Map<string, BabelTypes.Expression>;
14
+ observableSignals: Map<string, BabelTypes.Identifier>;
15
+ }
16
+
17
+ interface TransformResult {
18
+ id: BabelTypes.Identifier;
19
+ statements: BabelTypes.Statement[];
20
+ }
21
+
22
+ class NodeTypeGuards {
23
+ constructor(private t: typeof BabelTypes) {}
24
+
25
+ isSignalMember(expr: BabelTypes.Node): expr is BabelTypes.MemberExpression {
26
+ return (
27
+ this.t.isMemberExpression(expr) &&
28
+ this.t.isIdentifier(expr.property, { name: "value" })
29
+ );
30
+ }
31
+
32
+ isObservableMember(
33
+ expr: BabelTypes.Node
34
+ ): expr is BabelTypes.MemberExpression {
35
+ return (
36
+ this.t.isMemberExpression(expr) &&
37
+ this.t.isIdentifier(expr.property, { name: "$value" })
38
+ );
39
+ }
40
+ }
41
+
42
+ class ASTUtilities {
43
+ constructor(private t: typeof BabelTypes, private guards: NodeTypeGuards) {}
44
+
45
+ getObject(expr: BabelTypes.Expression): BabelTypes.Expression {
46
+ if (
47
+ this.guards.isSignalMember(expr) ||
48
+ this.guards.isObservableMember(expr)
49
+ ) {
50
+ return expr.object as BabelTypes.Expression;
51
+ }
52
+ return expr;
53
+ }
54
+
55
+ replaceThisWithSelf<T extends BabelTypes.Node>(node: T): T {
56
+ const cloned = this.t.cloneNode(node, true) as T;
57
+ this.walkAndTransform(cloned, (n: any) => {
58
+ if (this.t.isThisExpression(n)) {
59
+ Object.assign(n, this.t.identifier("self"));
60
+ }
61
+ });
62
+ return cloned;
63
+ }
64
+
65
+ private walkAndTransform(node: any, transform: (node: any) => void): void {
66
+ if (!node || typeof node !== "object") return;
67
+
68
+ transform(node);
69
+
70
+ for (const key in node) {
71
+ if (this.shouldSkipKey(key)) continue;
72
+
73
+ const value = node[key];
74
+ if (Array.isArray(value)) {
75
+ value.forEach((item) => this.walkAndTransform(item, transform));
76
+ } else if (value && typeof value === "object") {
77
+ this.walkAndTransform(value, transform);
78
+ }
79
+ }
80
+ }
81
+
82
+ private shouldSkipKey(key: string): boolean {
83
+ return ["loc", "start", "end", "extra"].includes(key);
84
+ }
85
+
86
+ buildMemberExpression(name: string): BabelTypes.Expression {
87
+ const parts = name.split(".");
88
+ let expr: BabelTypes.Expression = this.t.identifier(parts[0]);
89
+ for (let i = 1; i < parts.length; i++) {
90
+ expr = this.t.memberExpression(expr, this.t.identifier(parts[i]));
91
+ }
92
+ return expr;
93
+ }
94
+
95
+ insertBeforeReturn(
96
+ body: BabelTypes.Statement[],
97
+ statements: BabelTypes.Statement[]
98
+ ): void {
99
+ const returnIndex = body.findIndex((stmt) =>
100
+ this.t.isReturnStatement(stmt)
101
+ );
102
+ if (returnIndex !== -1) {
103
+ body.splice(returnIndex, 0, ...statements);
104
+ } else {
105
+ body.push(...statements);
106
+ }
107
+ }
108
+ }
109
+
110
+ class JSXUtilities {
111
+ constructor(private t: typeof BabelTypes) {}
112
+
113
+ getComponentName(
114
+ nameNode:
115
+ | BabelTypes.JSXIdentifier
116
+ | BabelTypes.JSXMemberExpression
117
+ | BabelTypes.JSXNamespacedName
118
+ ): string | null {
119
+ if (this.t.isJSXIdentifier(nameNode)) {
120
+ return nameNode.name;
121
+ }
122
+
123
+ if (this.t.isJSXMemberExpression(nameNode)) {
124
+ const parts: string[] = [];
125
+ let current: BabelTypes.JSXMemberExpression | BabelTypes.JSXIdentifier =
126
+ nameNode;
127
+
128
+ while (this.t.isJSXMemberExpression(current)) {
129
+ parts.unshift(current.property.name);
130
+ current = current.object;
131
+ }
132
+
133
+ if (this.t.isJSXIdentifier(current)) {
134
+ parts.unshift(current.name);
135
+ }
136
+
137
+ return parts.join(".");
138
+ }
139
+
140
+ return null;
141
+ }
142
+
143
+ isComponentTag(tag: string | null): boolean {
144
+ return tag ? /^[A-Z]/.test(tag) : false;
145
+ }
146
+ }
147
+
148
+ class ObservableManager {
149
+ constructor(private t: typeof BabelTypes, private guards: NodeTypeGuards) {}
150
+
151
+ getObservableKey(expr: BabelTypes.Node): string {
152
+ return this.stringifyNode(expr);
153
+ }
154
+
155
+ private stringifyNode(node: any): string {
156
+ if (!node) return "";
157
+ if (this.t.isThisExpression(node)) return "this";
158
+ if (this.t.isIdentifier(node)) return node.name;
159
+
160
+ if (this.t.isMemberExpression(node)) {
161
+ const obj = this.stringifyNode(node.object);
162
+ const prop = node.computed
163
+ ? `[${this.stringifyNode(node.property)}]`
164
+ : `.${(node.property as BabelTypes.Identifier).name}`;
165
+ return obj + prop;
166
+ }
167
+
168
+ if (this.t.isCallExpression(node)) {
169
+ const callee = this.stringifyNode(node.callee);
170
+ const args = node.arguments
171
+ .map((arg) => this.stringifyNode(arg))
172
+ .join(",");
173
+ return `${callee}(${args})`;
174
+ }
175
+
176
+ if (this.t.isStringLiteral(node)) return `"${node.value}"`;
177
+ if (this.t.isNumericLiteral(node)) return String(node.value);
178
+
179
+ return node.type + JSON.stringify(node.name || node.value || "");
180
+ }
181
+
182
+ collectObservables(
183
+ node: BabelTypes.Node,
184
+ observables: Map<string, BabelTypes.Expression>,
185
+ astUtils: ASTUtilities
186
+ ): void {
187
+ this.walkNode(node, (n: any) => {
188
+ if (this.guards.isObservableMember(n)) {
189
+ const observable = astUtils.replaceThisWithSelf(
190
+ n.object as BabelTypes.Expression
191
+ );
192
+ const key = this.getObservableKey(observable);
193
+ if (!observables.has(key)) {
194
+ observables.set(key, observable);
195
+ }
196
+ }
197
+ });
198
+ }
199
+
200
+ replaceObservablesWithSignals<T extends BabelTypes.Node>(
201
+ node: T,
202
+ observableSignals: Map<string, BabelTypes.Identifier>,
203
+ astUtils: ASTUtilities
204
+ ): T {
205
+ const cloned = this.t.cloneNode(node, true) as T;
206
+
207
+ this.walkNode(cloned, (n: any) => {
208
+ if (this.guards.isObservableMember(n)) {
209
+ const observable = astUtils.replaceThisWithSelf(n.object);
210
+ const key = this.getObservableKey(observable);
211
+ const signalId = observableSignals.get(key);
212
+
213
+ if (signalId) {
214
+ n.object = signalId;
215
+ n.property = this.t.identifier("value");
216
+ }
217
+ }
218
+ });
219
+
220
+ return cloned;
221
+ }
222
+
223
+ private walkNode(node: any, callback: (node: any) => void): void {
224
+ if (!node || typeof node !== "object") return;
225
+
226
+ callback(node);
227
+
228
+ for (const key in node) {
229
+ if (["loc", "start", "end", "extra"].includes(key)) continue;
230
+
231
+ const value = node[key];
232
+ if (Array.isArray(value)) {
233
+ value.forEach((item) => this.walkNode(item, callback));
234
+ } else if (value && typeof value === "object") {
235
+ this.walkNode(value, callback);
236
+ }
237
+ }
238
+ }
239
+ }
240
+
241
+ class ElementTransformer {
242
+ constructor(
243
+ private t: typeof BabelTypes,
244
+ private guards: NodeTypeGuards,
245
+ private astUtils: ASTUtilities,
246
+ private jsxUtils: JSXUtilities,
247
+ private observableManager: ObservableManager
248
+ ) {}
249
+
250
+ transformElement(
251
+ path: { node: BabelTypes.JSXElement | BabelTypes.JSXFragment },
252
+ scope: any,
253
+ context: TransformContext
254
+ ): TransformResult {
255
+ if (this.t.isJSXFragment(path.node)) {
256
+ return this.transformFragment(
257
+ path as { node: BabelTypes.JSXFragment },
258
+ scope,
259
+ context
260
+ );
261
+ }
262
+ return this.transformJSXElement(
263
+ path as { node: BabelTypes.JSXElement },
264
+ scope,
265
+ context
266
+ );
267
+ }
268
+
269
+ private transformJSXElement(
270
+ path: { node: BabelTypes.JSXElement },
271
+ scope: any,
272
+ context: TransformContext
273
+ ): TransformResult {
274
+ const jsxElement = path.node;
275
+ const tag = this.jsxUtils.getComponentName(jsxElement.openingElement.name);
276
+ const isComponent = this.jsxUtils.isComponentTag(tag);
277
+
278
+ if (isComponent && tag) {
279
+ return this.transformComponentElement(jsxElement, tag, scope, context);
280
+ } else if (tag) {
281
+ return this.transformDOMElement(jsxElement, tag, scope, context);
282
+ }
283
+
284
+ return {
285
+ id: scope.generateUidIdentifier("el"),
286
+ statements: [],
287
+ };
288
+ }
289
+
290
+ private transformComponentElement(
291
+ jsxElement: BabelTypes.JSXElement,
292
+ tag: string,
293
+ scope: any,
294
+ context: TransformContext
295
+ ): TransformResult {
296
+ const elId = scope.generateUidIdentifier("el");
297
+ const statements: BabelTypes.Statement[] = [];
298
+ const props: Array<
299
+ | BabelTypes.ObjectProperty
300
+ | BabelTypes.ObjectMethod
301
+ | BabelTypes.SpreadElement
302
+ > = [];
303
+ const children: Array<BabelTypes.Expression> = [];
304
+
305
+ this.processComponentAttributes(
306
+ jsxElement.openingElement.attributes,
307
+ props,
308
+ context
309
+ );
310
+
311
+ this.processChildren(
312
+ jsxElement.children,
313
+ children,
314
+ statements,
315
+ scope,
316
+ context
317
+ );
318
+
319
+ if (children.length > 0) {
320
+ props.push(
321
+ this.t.objectProperty(
322
+ this.t.identifier("children"),
323
+ children.length === 1 ? children[0] : this.t.arrayExpression(children)
324
+ )
325
+ );
326
+ }
327
+
328
+ statements.push(
329
+ this.t.variableDeclaration("var", [
330
+ this.t.variableDeclarator(
331
+ elId,
332
+ this.t.callExpression(this.t.identifier("$createComponent"), [
333
+ this.astUtils.buildMemberExpression(tag),
334
+ this.t.objectExpression(props),
335
+ this.t.identifier("self"),
336
+ ])
337
+ ),
338
+ ])
339
+ );
340
+
341
+ return { id: elId, statements };
342
+ }
343
+
344
+ private transformDOMElement(
345
+ jsxElement: BabelTypes.JSXElement,
346
+ tag: string,
347
+ scope: any,
348
+ context: TransformContext
349
+ ): TransformResult {
350
+ const elId = scope.generateUidIdentifier("el");
351
+ const statements: BabelTypes.Statement[] = [];
352
+
353
+ statements.push(
354
+ this.t.variableDeclaration("var", [
355
+ this.t.variableDeclarator(
356
+ elId,
357
+ this.t.callExpression(
358
+ this.t.memberExpression(
359
+ this.t.identifier("document"),
360
+ this.t.identifier("createElement")
361
+ ),
362
+ [this.t.stringLiteral(tag)]
363
+ )
364
+ ),
365
+ ])
366
+ );
367
+
368
+ const { hasRef, refValue, hasDangerousHTML, dangerousHTMLValue } =
369
+ this.processDOMAttributes(
370
+ jsxElement.openingElement.attributes,
371
+ elId,
372
+ statements,
373
+ context
374
+ );
375
+
376
+ if (hasRef && refValue) {
377
+ statements.push(
378
+ this.t.expressionStatement(
379
+ this.t.assignmentExpression("=", refValue as BabelTypes.LVal, elId)
380
+ )
381
+ );
382
+ }
383
+
384
+ if (hasDangerousHTML && dangerousHTMLValue) {
385
+ statements.push(
386
+ this.t.expressionStatement(
387
+ this.t.assignmentExpression(
388
+ "=",
389
+ this.t.memberExpression(elId, this.t.identifier("innerHTML")),
390
+ this.t.memberExpression(
391
+ dangerousHTMLValue,
392
+ this.t.identifier("__html")
393
+ )
394
+ )
395
+ )
396
+ );
397
+ }
398
+
399
+ if (!hasDangerousHTML) {
400
+ this.processDOMChildren(
401
+ jsxElement.children,
402
+ elId,
403
+ statements,
404
+ scope,
405
+ context
406
+ );
407
+ }
408
+
409
+ return { id: elId, statements };
410
+ }
411
+
412
+ private transformFragment(
413
+ path: { node: BabelTypes.JSXFragment },
414
+ scope: any,
415
+ context: TransformContext
416
+ ): TransformResult {
417
+ const fragId = scope.generateUidIdentifier("frag");
418
+ const statements: BabelTypes.Statement[] = [];
419
+
420
+ statements.push(
421
+ this.t.variableDeclaration("var", [
422
+ this.t.variableDeclarator(
423
+ fragId,
424
+ this.t.callExpression(
425
+ this.t.memberExpression(
426
+ this.t.identifier("document"),
427
+ this.t.identifier("createDocumentFragment")
428
+ ),
429
+ []
430
+ )
431
+ ),
432
+ ])
433
+ );
434
+
435
+ this.processDOMChildren(
436
+ path.node.children,
437
+ fragId,
438
+ statements,
439
+ scope,
440
+ context
441
+ );
442
+
443
+ return { id: fragId, statements };
444
+ }
445
+
446
+ private processComponentAttributes(
447
+ attributes: Array<BabelTypes.JSXAttribute | BabelTypes.JSXSpreadAttribute>,
448
+ props: Array<
449
+ | BabelTypes.ObjectProperty
450
+ | BabelTypes.ObjectMethod
451
+ | BabelTypes.SpreadElement
452
+ >,
453
+ context: TransformContext
454
+ ): void {
455
+ for (const attr of attributes) {
456
+ if (this.t.isJSXSpreadAttribute(attr)) {
457
+ this.observableManager.collectObservables(
458
+ attr.argument,
459
+ context.observables,
460
+ this.astUtils
461
+ );
462
+ const replaced = this.observableManager.replaceObservablesWithSignals(
463
+ attr.argument,
464
+ context.observableSignals,
465
+ this.astUtils
466
+ );
467
+ props.push(
468
+ this.t.spreadElement(this.astUtils.replaceThisWithSelf(replaced))
469
+ );
470
+ continue;
471
+ }
472
+
473
+ const key = (attr.name as BabelTypes.JSXIdentifier).name;
474
+
475
+ if (this.t.isStringLiteral(attr.value)) {
476
+ props.push(this.t.objectProperty(this.t.identifier(key), attr.value));
477
+ } else if (this.t.isJSXExpressionContainer(attr.value)) {
478
+ const expr = attr.value.expression as BabelTypes.Expression;
479
+ this.observableManager.collectObservables(
480
+ expr,
481
+ context.observables,
482
+ this.astUtils
483
+ );
484
+
485
+ if (
486
+ this.guards.isSignalMember(expr) ||
487
+ this.guards.isObservableMember(expr)
488
+ ) {
489
+ const replaced = this.observableManager.replaceObservablesWithSignals(
490
+ expr,
491
+ context.observableSignals,
492
+ this.astUtils
493
+ );
494
+ props.push(
495
+ this.t.objectMethod(
496
+ "get",
497
+ this.t.identifier(key),
498
+ [],
499
+ this.t.blockStatement([
500
+ this.t.returnStatement(
501
+ this.astUtils.replaceThisWithSelf(replaced)
502
+ ),
503
+ ])
504
+ )
505
+ );
506
+ } else {
507
+ const replaced = this.observableManager.replaceObservablesWithSignals(
508
+ expr,
509
+ context.observableSignals,
510
+ this.astUtils
511
+ );
512
+ props.push(
513
+ this.t.objectProperty(
514
+ this.t.identifier(key),
515
+ this.astUtils.replaceThisWithSelf(replaced)
516
+ )
517
+ );
518
+ }
519
+ } else {
520
+ props.push(
521
+ this.t.objectProperty(
522
+ this.t.identifier(key),
523
+ this.t.booleanLiteral(true)
524
+ )
525
+ );
526
+ }
527
+ }
528
+ }
529
+
530
+ private processDOMAttributes(
531
+ attributes: Array<BabelTypes.JSXAttribute | BabelTypes.JSXSpreadAttribute>,
532
+ elId: BabelTypes.Identifier,
533
+ statements: BabelTypes.Statement[],
534
+ context: TransformContext
535
+ ): {
536
+ hasRef: boolean;
537
+ refValue: BabelTypes.Expression | null;
538
+ hasDangerousHTML: boolean;
539
+ dangerousHTMLValue: BabelTypes.Expression | null;
540
+ } {
541
+ let hasRef = false;
542
+ let refValue: BabelTypes.Expression | null = null;
543
+ let hasDangerousHTML = false;
544
+ let dangerousHTMLValue: BabelTypes.Expression | null = null;
545
+
546
+ for (const attr of attributes) {
547
+ if (this.t.isJSXSpreadAttribute(attr)) {
548
+ this.observableManager.collectObservables(
549
+ attr.argument,
550
+ context.observables,
551
+ this.astUtils
552
+ );
553
+ const replaced = this.observableManager.replaceObservablesWithSignals(
554
+ attr.argument,
555
+ context.observableSignals,
556
+ this.astUtils
557
+ );
558
+ statements.push(
559
+ this.t.expressionStatement(
560
+ this.t.callExpression(this.t.identifier("$spread"), [
561
+ elId,
562
+ this.astUtils.replaceThisWithSelf(replaced),
563
+ ])
564
+ )
565
+ );
566
+ continue;
567
+ }
568
+
569
+ const key = (attr.name as BabelTypes.JSXIdentifier).name;
570
+
571
+ if (key === "ref") {
572
+ hasRef = true;
573
+ if (this.t.isJSXExpressionContainer(attr.value)) {
574
+ this.observableManager.collectObservables(
575
+ attr.value.expression,
576
+ context.observables,
577
+ this.astUtils
578
+ );
579
+ const replaced = this.observableManager.replaceObservablesWithSignals(
580
+ attr.value.expression as BabelTypes.Expression,
581
+ context.observableSignals,
582
+ this.astUtils
583
+ );
584
+ refValue = this.astUtils.replaceThisWithSelf(replaced);
585
+ }
586
+ continue;
587
+ }
588
+
589
+ if (key === "dangerouslySetInnerHTML") {
590
+ hasDangerousHTML = true;
591
+ if (this.t.isJSXExpressionContainer(attr.value)) {
592
+ this.observableManager.collectObservables(
593
+ attr.value.expression,
594
+ context.observables,
595
+ this.astUtils
596
+ );
597
+ const replaced = this.observableManager.replaceObservablesWithSignals(
598
+ attr.value.expression as BabelTypes.Expression,
599
+ context.observableSignals,
600
+ this.astUtils
601
+ );
602
+ dangerousHTMLValue = this.astUtils.replaceThisWithSelf(replaced);
603
+ }
604
+ continue;
605
+ }
606
+
607
+ if (/^on[A-Z]/.test(key)) {
608
+ this.processEventListener(key, attr, elId, statements, context);
609
+ continue;
610
+ }
611
+
612
+ if (key === "style" && this.t.isJSXExpressionContainer(attr.value)) {
613
+ this.processStyleAttribute(attr, elId, statements, context);
614
+ continue;
615
+ }
616
+
617
+ this.processRegularAttribute(key, attr, elId, statements, context);
618
+ }
619
+
620
+ return { hasRef, refValue, hasDangerousHTML, dangerousHTMLValue };
621
+ }
622
+
623
+ private processEventListener(
624
+ key: string,
625
+ attr: BabelTypes.JSXAttribute,
626
+ elId: BabelTypes.Identifier,
627
+ statements: BabelTypes.Statement[],
628
+ context: TransformContext
629
+ ): void {
630
+ const eventName = key.slice(2).toLowerCase();
631
+ let handler: BabelTypes.Expression = this.t.nullLiteral();
632
+
633
+ if (this.t.isJSXExpressionContainer(attr.value)) {
634
+ this.observableManager.collectObservables(
635
+ attr.value.expression,
636
+ context.observables,
637
+ this.astUtils
638
+ );
639
+ const replaced = this.observableManager.replaceObservablesWithSignals(
640
+ attr.value.expression as BabelTypes.Expression,
641
+ context.observableSignals,
642
+ this.astUtils
643
+ );
644
+ handler = this.astUtils.replaceThisWithSelf(replaced);
645
+ }
646
+
647
+ statements.push(
648
+ this.t.expressionStatement(
649
+ this.t.callExpression(
650
+ this.t.memberExpression(elId, this.t.identifier("addEventListener")),
651
+ [this.t.stringLiteral(eventName), handler]
652
+ )
653
+ )
654
+ );
655
+ }
656
+
657
+ private processStyleAttribute(
658
+ attr: BabelTypes.JSXAttribute,
659
+ elId: BabelTypes.Identifier,
660
+ statements: BabelTypes.Statement[],
661
+ context: TransformContext
662
+ ): void {
663
+ if (!this.t.isJSXExpressionContainer(attr.value)) return;
664
+
665
+ this.observableManager.collectObservables(
666
+ attr.value.expression,
667
+ context.observables,
668
+ this.astUtils
669
+ );
670
+ const replaced = this.observableManager.replaceObservablesWithSignals(
671
+ attr.value.expression as BabelTypes.Expression,
672
+ context.observableSignals,
673
+ this.astUtils
674
+ );
675
+ statements.push(
676
+ this.t.expressionStatement(
677
+ this.t.callExpression(this.t.identifier("$style"), [
678
+ elId,
679
+ this.t.arrowFunctionExpression(
680
+ [],
681
+ this.astUtils.replaceThisWithSelf(replaced)
682
+ ),
683
+ ])
684
+ )
685
+ );
686
+ }
687
+
688
+ private processRegularAttribute(
689
+ key: string,
690
+ attr: BabelTypes.JSXAttribute,
691
+ elId: BabelTypes.Identifier,
692
+ statements: BabelTypes.Statement[],
693
+ context: TransformContext
694
+ ): void {
695
+ const attrName = key === "className" ? "class" : key;
696
+ let value: BabelTypes.Expression;
697
+
698
+ if (this.t.isStringLiteral(attr.value)) {
699
+ value = attr.value;
700
+ } else if (this.t.isJSXExpressionContainer(attr.value)) {
701
+ this.observableManager.collectObservables(
702
+ attr.value.expression,
703
+ context.observables,
704
+ this.astUtils
705
+ );
706
+ const replaced = this.observableManager.replaceObservablesWithSignals(
707
+ attr.value.expression as BabelTypes.Expression,
708
+ context.observableSignals,
709
+ this.astUtils
710
+ );
711
+ value = this.astUtils.replaceThisWithSelf(replaced);
712
+ } else {
713
+ value = this.t.booleanLiteral(true);
714
+ }
715
+
716
+ statements.push(
717
+ this.t.expressionStatement(
718
+ this.t.callExpression(
719
+ this.t.memberExpression(elId, this.t.identifier("setAttribute")),
720
+ [this.t.stringLiteral(attrName), value]
721
+ )
722
+ )
723
+ );
724
+ }
725
+
726
+ private processChildren(
727
+ children: Array<
728
+ | BabelTypes.JSXText
729
+ | BabelTypes.JSXExpressionContainer
730
+ | BabelTypes.JSXElement
731
+ | BabelTypes.JSXFragment
732
+ | BabelTypes.JSXSpreadChild
733
+ >,
734
+ childExpressions: Array<BabelTypes.Expression>,
735
+ statements: BabelTypes.Statement[],
736
+ scope: any,
737
+ context: TransformContext
738
+ ): void {
739
+ for (const child of children) {
740
+ if (this.t.isJSXText(child)) {
741
+ const text = child.value.trim();
742
+ if (text) childExpressions.push(this.t.stringLiteral(text));
743
+ } else if (this.t.isJSXExpressionContainer(child)) {
744
+ const expr = child.expression;
745
+ if (!this.t.isJSXEmptyExpression(expr)) {
746
+ this.observableManager.collectObservables(
747
+ expr,
748
+ context.observables,
749
+ this.astUtils
750
+ );
751
+ const replaced = this.observableManager.replaceObservablesWithSignals(
752
+ expr as BabelTypes.Expression,
753
+ context.observableSignals,
754
+ this.astUtils
755
+ );
756
+ childExpressions.push(this.astUtils.replaceThisWithSelf(replaced));
757
+ }
758
+ } else if (this.t.isJSXElement(child) || this.t.isJSXFragment(child)) {
759
+ const childEl = this.transformElement({ node: child }, scope, context);
760
+ statements.push(...childEl.statements);
761
+ childExpressions.push(childEl.id);
762
+ }
763
+ }
764
+ }
765
+
766
+ private processDOMChildren(
767
+ children: Array<
768
+ | BabelTypes.JSXText
769
+ | BabelTypes.JSXExpressionContainer
770
+ | BabelTypes.JSXElement
771
+ | BabelTypes.JSXFragment
772
+ | BabelTypes.JSXSpreadChild
773
+ >,
774
+ parentId: BabelTypes.Identifier,
775
+ statements: BabelTypes.Statement[],
776
+ scope: any,
777
+ context: TransformContext
778
+ ): void {
779
+ for (const child of children) {
780
+ if (this.t.isJSXText(child)) {
781
+ const text = child.value.trim();
782
+ if (!text) continue;
783
+ statements.push(
784
+ this.t.expressionStatement(
785
+ this.t.callExpression(this.t.identifier("$insert"), [
786
+ parentId,
787
+ this.t.stringLiteral(text),
788
+ ])
789
+ )
790
+ );
791
+ } else if (this.t.isJSXExpressionContainer(child)) {
792
+ const expr = child.expression;
793
+ if (this.t.isJSXEmptyExpression(expr)) continue;
794
+
795
+ this.observableManager.collectObservables(
796
+ expr,
797
+ context.observables,
798
+ this.astUtils
799
+ );
800
+
801
+ let insertedValue: BabelTypes.Expression;
802
+ if (this.guards.isSignalMember(expr)) {
803
+ insertedValue = this.astUtils.getObject(
804
+ expr as BabelTypes.Expression
805
+ );
806
+ } else if (this.guards.isObservableMember(expr)) {
807
+ const replaced = this.observableManager.replaceObservablesWithSignals(
808
+ expr as BabelTypes.Expression,
809
+ context.observableSignals,
810
+ this.astUtils
811
+ );
812
+ insertedValue = this.astUtils.getObject(replaced);
813
+ } else {
814
+ const replaced = this.observableManager.replaceObservablesWithSignals(
815
+ expr as BabelTypes.Expression,
816
+ context.observableSignals,
817
+ this.astUtils
818
+ );
819
+ insertedValue = this.t.arrowFunctionExpression(
820
+ [],
821
+ this.astUtils.replaceThisWithSelf(replaced)
822
+ );
823
+ }
824
+
825
+ statements.push(
826
+ this.t.expressionStatement(
827
+ this.t.callExpression(this.t.identifier("$insert"), [
828
+ parentId,
829
+ insertedValue,
830
+ ])
831
+ )
832
+ );
833
+ } else if (this.t.isJSXElement(child) || this.t.isJSXFragment(child)) {
834
+ const childEl = this.transformElement({ node: child }, scope, context);
835
+ statements.push(...childEl.statements);
836
+ statements.push(
837
+ this.t.expressionStatement(
838
+ this.t.callExpression(this.t.identifier("$insert"), [
839
+ parentId,
840
+ childEl.id,
841
+ ])
842
+ )
843
+ );
844
+ }
845
+ }
846
+ }
847
+ }
848
+
849
+ export function j2d({ types: t }: PluginContext): PluginObj<PluginState> {
850
+ const guards = new NodeTypeGuards(t);
851
+ const astUtils = new ASTUtilities(t, guards);
852
+ const jsxUtils = new JSXUtilities(t);
853
+ const observableManager = new ObservableManager(t, guards);
854
+ const elementTransformer = new ElementTransformer(
855
+ t,
856
+ guards,
857
+ astUtils,
858
+ jsxUtils,
859
+ observableManager
860
+ );
861
+
862
+ return {
863
+ name: "jsx-to-dom",
864
+ visitor: {
865
+ Program: {
866
+ exit(path: NodePath<BabelTypes.Program>, state: PluginState) {
867
+ if (state.helpersImported) return;
868
+
869
+ const helpers = [
870
+ { local: "$insert", imported: "insert" },
871
+ { local: "$createComponent", imported: "createComponent" },
872
+ { local: "$style", imported: "style" },
873
+ { local: "$spread", imported: "spread" },
874
+ { local: "$toSignal", imported: "toSignal" },
875
+ ];
876
+
877
+ for (const helper of helpers) {
878
+ path.unshiftContainer(
879
+ "body",
880
+ t.importDeclaration(
881
+ [
882
+ t.importSpecifier(
883
+ t.identifier(helper.local),
884
+ t.identifier(helper.imported)
885
+ ),
886
+ ],
887
+ t.stringLiteral("@kithinji/orca")
888
+ )
889
+ );
890
+ }
891
+
892
+ state.helpersImported = true;
893
+ },
894
+ },
895
+
896
+ ClassMethod(path: NodePath<BabelTypes.ClassMethod>) {
897
+ if (path.getData("processed")) return;
898
+
899
+ // Check if method contains JSX
900
+ let hasJSX = false;
901
+ path.traverse({
902
+ JSXElement() {
903
+ hasJSX = true;
904
+ },
905
+ JSXFragment() {
906
+ hasJSX = true;
907
+ },
908
+ });
909
+
910
+ if (!hasJSX) return;
911
+ path.setData("processed", true);
912
+
913
+ const body = path.node.body;
914
+ if (!t.isBlockStatement(body)) return;
915
+
916
+ // Collect all observables from JSX
917
+ const observables = new Map<string, BabelTypes.Expression>();
918
+ path.traverse({
919
+ JSXElement(jsxPath: NodePath<BabelTypes.JSXElement>) {
920
+ observableManager.collectObservables(
921
+ jsxPath.node,
922
+ observables,
923
+ astUtils
924
+ );
925
+ },
926
+ JSXFragment(jsxPath: NodePath<BabelTypes.JSXFragment>) {
927
+ observableManager.collectObservables(
928
+ jsxPath.node,
929
+ observables,
930
+ astUtils
931
+ );
932
+ },
933
+ });
934
+
935
+ // Add self reference
936
+ body.body.unshift(
937
+ t.variableDeclaration("const", [
938
+ t.variableDeclarator(t.identifier("self"), t.thisExpression()),
939
+ ])
940
+ );
941
+
942
+ // Create signal declarations for observables
943
+ const observableSignals = new Map<string, BabelTypes.Identifier>();
944
+ const signalDeclarations: BabelTypes.Statement[] = [];
945
+
946
+ for (const [key, observable] of observables) {
947
+ const signalId = path.scope.generateUidIdentifier("sig");
948
+ observableSignals.set(key, signalId);
949
+ signalDeclarations.push(
950
+ t.variableDeclaration("const", [
951
+ t.variableDeclarator(
952
+ signalId,
953
+ t.callExpression(t.identifier("$toSignal"), [
954
+ observable,
955
+ t.identifier("self"),
956
+ ])
957
+ ),
958
+ ])
959
+ );
960
+ }
961
+
962
+ if (signalDeclarations.length > 0) {
963
+ astUtils.insertBeforeReturn(body.body, signalDeclarations);
964
+ }
965
+
966
+ const context: TransformContext = { observables, observableSignals };
967
+
968
+ // Transform JSX elements and fragments
969
+ path.traverse({
970
+ JSXElement(jsxPath: NodePath<BabelTypes.JSXElement>) {
971
+ if (jsxPath.getData("processed")) return;
972
+ jsxPath.setData("processed", true);
973
+
974
+ const { id, statements } = elementTransformer.transformElement(
975
+ jsxPath as any,
976
+ jsxPath.scope,
977
+ context
978
+ );
979
+
980
+ jsxPath.replaceWith(
981
+ t.callExpression(
982
+ t.arrowFunctionExpression(
983
+ [],
984
+ t.blockStatement([...statements, t.returnStatement(id)])
985
+ ),
986
+ []
987
+ )
988
+ );
989
+ },
990
+ JSXFragment(jsxPath: NodePath<BabelTypes.JSXFragment>) {
991
+ if (jsxPath.getData("processed")) return;
992
+ jsxPath.setData("processed", true);
993
+
994
+ const { id, statements } = elementTransformer.transformElement(
995
+ jsxPath as any,
996
+ jsxPath.scope,
997
+ context
998
+ );
999
+
1000
+ jsxPath.replaceWith(
1001
+ t.callExpression(
1002
+ t.arrowFunctionExpression(
1003
+ [],
1004
+ t.blockStatement([...statements, t.returnStatement(id)])
1005
+ ),
1006
+ []
1007
+ )
1008
+ );
1009
+ },
1010
+ });
1011
+ },
1012
+ },
1013
+ };
1014
+ }