@kithinji/pod 1.0.14 → 1.0.16

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.
@@ -1,2 +1,4 @@
1
+ import type { Decorator } from "@swc/core";
1
2
  export declare function generateServerComponent(filePath: string, code: string): string;
3
+ export declare function stringifyDecorator(decorator: Decorator): string;
2
4
  //# sourceMappingURL=generate_server_component.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate_server_component.d.ts","sourceRoot":"","sources":["../../../../src/plugins/generators/generate_server_component.ts"],"names":[],"mappings":"AAoBA,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,MAAM,CAUR"}
1
+ {"version":3,"file":"generate_server_component.d.ts","sourceRoot":"","sources":["../../../../src/plugins/generators/generate_server_component.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,SAAS,EAGV,MAAM,WAAW,CAAC;AA0BnB,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,MAAM,CAqER;AAgED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAiB/D"}
@@ -1 +1 @@
1
- {"version":3,"file":"j2d.d.ts","sourceRoot":"","sources":["../../../../src/plugins/transformers/j2d.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC;AAE3C,UAAU,aAAa;IACrB,KAAK,EAAE,OAAO,UAAU,CAAC;CAC1B;AAED,UAAU,WAAW;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAu0BD,wBAAgB,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,aAAa,GAAG,SAAS,CAAC,WAAW,CAAC,CAqKvE"}
1
+ {"version":3,"file":"j2d.d.ts","sourceRoot":"","sources":["../../../../src/plugins/transformers/j2d.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC;AAE3C,UAAU,aAAa;IACrB,KAAK,EAAE,OAAO,UAAU,CAAC;CAC1B;AAED,UAAU,WAAW;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA23BD,wBAAgB,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,aAAa,GAAG,SAAS,CAAC,WAAW,CAAC,CAmKvE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kithinji/pod",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "",
5
5
  "bin": {
6
6
  "pod": "./dist/main.js"
package/src/main.ts CHANGED
@@ -28,7 +28,7 @@ import { dockerize } from "./docker";
28
28
 
29
29
  const program = new Command();
30
30
 
31
- program.name("pod").description("Pod cli tool").version("1.0.14");
31
+ program.name("pod").description("Pod cli tool").version("1.0.16");
32
32
 
33
33
  program
34
34
  .command("new <name>")
@@ -1,5 +1,10 @@
1
- import { parseSync } from "@swc/core";
2
- import type { ClassDeclaration, Decorator } from "@swc/core";
1
+ import { parseSync, printSync } from "@swc/core";
2
+ import type {
3
+ ClassDeclaration,
4
+ Decorator,
5
+ ImportDeclaration,
6
+ ModuleItem,
7
+ } from "@swc/core";
3
8
 
4
9
  interface MethodParam {
5
10
  name: string;
@@ -13,11 +18,18 @@ interface ComponentMethod {
13
18
  isAsync: boolean;
14
19
  }
15
20
 
16
- interface ComponentInfo {
17
- className: string;
21
+ interface ClassStub {
22
+ name: string;
23
+ propsType: string;
24
+ decorators: string[];
25
+ constructorParams: string[];
18
26
  methods: ComponentMethod[];
19
27
  }
20
28
 
29
+ interface ImportMap {
30
+ [localName: string]: string;
31
+ }
32
+
21
33
  export function generateServerComponent(
22
34
  filePath: string,
23
35
  code: string
@@ -28,39 +40,68 @@ export function generateServerComponent(
28
40
  decorators: true,
29
41
  });
30
42
 
31
- const componentInfo = extractComponentInfo(ast);
32
-
33
- return generateStubCode(componentInfo);
34
- }
43
+ const importMap: ImportMap = {};
44
+ for (const item of ast.body) {
45
+ if (item.type === "ImportDeclaration") {
46
+ const decl = item as ImportDeclaration;
47
+ for (const specifier of decl.specifiers ?? []) {
48
+ let localName: string;
49
+ if (specifier.type === "ImportSpecifier") {
50
+ localName = specifier.local.value;
51
+ } else if (specifier.type === "ImportDefaultSpecifier") {
52
+ localName = specifier.local.value;
53
+ } else {
54
+ continue;
55
+ }
56
+ importMap[localName] = decl.source.value;
57
+ }
58
+ }
59
+ }
35
60
 
36
- function extractComponentInfo(ast: any): ComponentInfo {
37
- let componentClass: ClassDeclaration | null = null;
61
+ const preservedNodes: ModuleItem[] = [];
62
+ const stubbedClasses: ClassStub[] = [];
38
63
 
39
64
  for (const item of ast.body) {
65
+ let shouldStub = false;
66
+
40
67
  if (
41
68
  item.type === "ExportDeclaration" &&
42
- item.declaration.type === "ClassDeclaration"
69
+ item.declaration?.type === "ClassDeclaration"
43
70
  ) {
44
71
  const classDecl = item.declaration as ClassDeclaration;
45
72
 
46
73
  if (hasComponentDecorator(classDecl.decorators)) {
47
- componentClass = classDecl;
48
- break;
74
+ shouldStub = true;
75
+ const stub = extractClassStub(classDecl);
76
+ if (stub) {
77
+ stubbedClasses.push(stub);
78
+ }
49
79
  }
50
80
  }
51
- }
52
81
 
53
- if (!componentClass || !componentClass.identifier) {
54
- throw new Error("Component class is undefined");
82
+ if (!shouldStub) {
83
+ preservedNodes.push(item);
84
+ }
55
85
  }
56
86
 
57
- const className = componentClass.identifier.value;
58
- const methods = extractMethods(componentClass);
59
-
60
- return {
61
- className,
62
- methods,
63
- };
87
+ const preservedCode =
88
+ preservedNodes.length > 0
89
+ ? printSync({
90
+ type: "Module",
91
+ span: ast.span,
92
+ body: preservedNodes,
93
+ interpreter: ast.interpreter,
94
+ }).code
95
+ : "";
96
+
97
+ const stubCode = stubbedClasses
98
+ .map((stub) => generateStubCode(stub))
99
+ .join("\n\n");
100
+
101
+ return `
102
+ ${preservedCode}
103
+ ${stubCode}
104
+ `.trim();
64
105
  }
65
106
 
66
107
  function hasComponentDecorator(decorators?: Decorator[]): boolean {
@@ -86,6 +127,136 @@ function hasComponentDecorator(decorators?: Decorator[]): boolean {
86
127
  });
87
128
  }
88
129
 
130
+ function extractClassStub(classDecl: ClassDeclaration): ClassStub | null {
131
+ const className = classDecl.identifier?.value;
132
+ if (!className) return null;
133
+
134
+ let propsType = "{}";
135
+ const decorators: string[] = [];
136
+ const constructorParams: string[] = [];
137
+
138
+ if (classDecl.decorators) {
139
+ for (const dec of classDecl.decorators) {
140
+ const str = stringifyDecorator(dec);
141
+ if (str) decorators.push(str);
142
+ }
143
+ }
144
+
145
+ for (const member of classDecl.body) {
146
+ if (member.type === "ClassProperty") {
147
+ if (member.key.type === "Identifier" && member.key.value === "props") {
148
+ propsType = extractPropsType(member);
149
+ }
150
+ } else if (member.type === "Constructor") {
151
+ for (const param of member.params) {
152
+ const paramStr = stringifyParam(param);
153
+ if (paramStr) constructorParams.push(paramStr);
154
+ }
155
+ }
156
+ }
157
+
158
+ const methods = extractMethods(classDecl);
159
+
160
+ return {
161
+ name: className,
162
+ propsType,
163
+ decorators,
164
+ constructorParams,
165
+ methods,
166
+ };
167
+ }
168
+
169
+ export function stringifyDecorator(decorator: Decorator): string {
170
+ const exprCode = printSync({
171
+ type: "Module",
172
+ span: { start: 0, end: 0, ctxt: 0 },
173
+ body: [
174
+ {
175
+ type: "ExpressionStatement",
176
+ expression: decorator.expression,
177
+ span: { start: 0, end: 0, ctxt: 0 },
178
+ },
179
+ ],
180
+ interpreter: "",
181
+ }).code;
182
+
183
+ const cleanCode = exprCode.replace(/^#!.*\n/, "").trim();
184
+
185
+ return `@${cleanCode.replace(/;$/, "")}`;
186
+ }
187
+
188
+ function extractPropsType(member: any): string {
189
+ const typeAnn = member.typeAnnotation?.typeAnnotation;
190
+ if (!typeAnn) return "{}";
191
+
192
+ if (typeAnn.type === "TsTypeLiteral") {
193
+ const props: string[] = [];
194
+ for (const m of typeAnn.members) {
195
+ if (m.type === "TsPropertySignature") {
196
+ const key = m.key.type === "Identifier" ? m.key.value : "?";
197
+ const t = m.typeAnnotation
198
+ ? stringifyType(m.typeAnnotation.typeAnnotation)
199
+ : "any";
200
+ props.push(`${key}: ${t}`);
201
+ }
202
+ }
203
+ return `{ ${props.join("; ")} }`;
204
+ }
205
+
206
+ return stringifyType(typeAnn);
207
+ }
208
+
209
+ function stringifyParam(param: any): string {
210
+ let decorators: string[] = [];
211
+ if (param.decorators) {
212
+ for (const d of param.decorators) {
213
+ const str = stringifyDecorator(d);
214
+ if (str) decorators.push(str);
215
+ }
216
+ }
217
+ const decoratorPrefix = decorators.length ? decorators.join(" ") + " " : "";
218
+
219
+ let typeName = "any";
220
+ let paramName = "";
221
+ let accessibility = "";
222
+
223
+ if (param.type === "TsParameterProperty") {
224
+ accessibility = param.accessibility || "";
225
+ const inner = param.param;
226
+ if (inner.type !== "Identifier") return "";
227
+
228
+ paramName = inner.value;
229
+ if (inner.typeAnnotation?.typeAnnotation) {
230
+ typeName = extractTypeName(inner.typeAnnotation.typeAnnotation);
231
+ }
232
+ } else if (param.type === "Parameter") {
233
+ const pat = param.pat;
234
+ if (pat.type !== "Identifier") return "";
235
+
236
+ paramName = pat.value;
237
+ if (pat.typeAnnotation?.typeAnnotation) {
238
+ typeName = extractTypeName(pat.typeAnnotation.typeAnnotation);
239
+ }
240
+ } else {
241
+ return "";
242
+ }
243
+
244
+ const accessPrefix = accessibility ? `${accessibility} ` : "";
245
+ const result = `${decoratorPrefix}${accessPrefix}${paramName}: ${typeName}`;
246
+
247
+ return result;
248
+ }
249
+
250
+ function extractTypeName(typeNode: any): string {
251
+ if (
252
+ typeNode.type === "TsTypeReference" &&
253
+ typeNode.typeName.type === "Identifier"
254
+ ) {
255
+ return typeNode.typeName.value;
256
+ }
257
+ return stringifyType(typeNode);
258
+ }
259
+
89
260
  function extractMethods(classDecl: ClassDeclaration): ComponentMethod[] {
90
261
  const methods: ComponentMethod[] = [];
91
262
 
@@ -209,33 +380,36 @@ function stringifyType(typeNode: any): string {
209
380
  }
210
381
  }
211
382
 
212
- function generateStubCode(componentInfo: ComponentInfo): string {
213
- const className = componentInfo.className;
383
+ function generateStubCode(stub: ClassStub): string {
384
+ const className = stub.name;
214
385
 
215
- const build = componentInfo.methods.find((p) => p.name == "build");
386
+ const build = stub.methods.find((p) => p.name == "build");
216
387
 
217
388
  if (build == undefined) {
218
389
  throw new Error("Component has no build function");
219
390
  }
220
391
 
392
+ const decoratorsStr =
393
+ stub.decorators.length > 0 ? stub.decorators.join("\n") + "\n" : "";
394
+
221
395
  return `import {
222
- Component,
223
- Inject,
224
- getCurrentInjector,
225
- OrcaComponent,
226
- JSX,
227
- OSC,
228
- HttpClient,
229
- symbolValueReviver
396
+ Inject as _Inject,
397
+ getCurrentInjector as _getCurrentInjector,
398
+ OrcaComponent as _OrcaComponent,
399
+ JSX as _JSX,
400
+ OSC as _OSC,
401
+ HttpClient as _HttpClient,
402
+ symbolValueReviver as _symbolValueReviver
230
403
  } from "@kithinji/orca";
231
404
 
232
- @Component()
233
- export class ${className} extends OrcaComponent {
405
+
406
+ ${decoratorsStr}export class ${className} extends _OrcaComponent {
234
407
  props!: any;
235
408
 
236
409
  constructor(
237
- @Inject("OSC_URL", { maybe: true }) private oscUrl?: string,
238
- private readonly http: HttpClient,
410
+ @_Inject("OSC_URL", { maybe: true }) private oscUrl?: string,
411
+ private readonly http: _HttpClient,
412
+ ${stub.constructorParams.join(", ")}
239
413
  ) {
240
414
  super();
241
415
 
@@ -248,20 +422,20 @@ export class ${className} extends OrcaComponent {
248
422
  const root = document.createElement("div");
249
423
  root.textContent = "loading...";
250
424
 
251
- const injector = getCurrentInjector();
425
+ const injector = _getCurrentInjector();
252
426
 
253
427
  if(injector == null) {
254
428
  throw new Error("Injector is null");
255
429
  }
256
430
 
257
- const osc = new OSC(root);
431
+ const osc = new _OSC(root);
258
432
 
259
- const subscription = this.http.post<JSX.Element>(
433
+ const subscription = this.http.post<_JSX.Element>(
260
434
  \`\${this.oscUrl}?c=${className}\`, {
261
435
  body: this.props,
262
- reviver: symbolValueReviver,
436
+ reviver: _symbolValueReviver,
263
437
  }
264
- ).subscribe((jsx: JSX.Element) => {
438
+ ).subscribe((jsx: _JSX.Element) => {
265
439
  const action = jsx.action || "insert";
266
440
 
267
441
  if (action === "insert") {
@@ -12,7 +12,7 @@ interface ClassStub {
12
12
  name: string;
13
13
  propsType: string;
14
14
  decorators: string[];
15
- constructorParams: string[]; // added for constructor arguments
15
+ constructorParams: string[];
16
16
  }
17
17
 
18
18
  interface ImportMap {
@@ -105,6 +105,43 @@ class ASTUtilities {
105
105
  body.push(...statements);
106
106
  }
107
107
  }
108
+
109
+ addEffectCleanup(
110
+ scope: any,
111
+ effectCall: BabelTypes.CallExpression
112
+ ): BabelTypes.Statement[] {
113
+ const cleanupId = scope.generateUidIdentifier("cleanup");
114
+
115
+ return [
116
+ // const _cleanup1 = $effect(...)
117
+ this.t.variableDeclaration("const", [
118
+ this.t.variableDeclarator(cleanupId, effectCall),
119
+ ]),
120
+ // self.__cleanup = [...(self.__cleanup || []), _cleanup1]
121
+ this.t.expressionStatement(
122
+ this.t.assignmentExpression(
123
+ "=",
124
+ this.t.memberExpression(
125
+ this.t.identifier("self"),
126
+ this.t.identifier("__cleanup")
127
+ ),
128
+ this.t.arrayExpression([
129
+ this.t.spreadElement(
130
+ this.t.logicalExpression(
131
+ "||",
132
+ this.t.memberExpression(
133
+ this.t.identifier("self"),
134
+ this.t.identifier("__cleanup")
135
+ ),
136
+ this.t.arrayExpression([])
137
+ )
138
+ ),
139
+ cleanupId,
140
+ ])
141
+ )
142
+ ),
143
+ ];
144
+ }
108
145
  }
109
146
 
110
147
  class JSXUtilities {
@@ -370,6 +407,7 @@ class ElementTransformer {
370
407
  jsxElement.openingElement.attributes,
371
408
  elId,
372
409
  statements,
410
+ scope,
373
411
  context
374
412
  );
375
413
 
@@ -531,6 +569,7 @@ class ElementTransformer {
531
569
  attributes: Array<BabelTypes.JSXAttribute | BabelTypes.JSXSpreadAttribute>,
532
570
  elId: BabelTypes.Identifier,
533
571
  statements: BabelTypes.Statement[],
572
+ scope: any,
534
573
  context: TransformContext
535
574
  ): {
536
575
  hasRef: boolean;
@@ -555,14 +594,24 @@ class ElementTransformer {
555
594
  context.observableSignals,
556
595
  this.astUtils
557
596
  );
558
- statements.push(
559
- this.t.expressionStatement(
597
+
598
+ const effectCall = this.t.callExpression(this.t.identifier("$effect"), [
599
+ this.t.arrowFunctionExpression(
600
+ [],
560
601
  this.t.callExpression(this.t.identifier("$spread"), [
561
602
  elId,
562
603
  this.astUtils.replaceThisWithSelf(replaced),
563
604
  ])
564
- )
605
+ ),
606
+ ]);
607
+
608
+ const cleanupStatements = this.astUtils.addEffectCleanup(
609
+ scope,
610
+ effectCall
565
611
  );
612
+
613
+ statements.push(...cleanupStatements);
614
+
566
615
  continue;
567
616
  }
568
617
 
@@ -610,7 +659,7 @@ class ElementTransformer {
610
659
  }
611
660
 
612
661
  if (key === "style" && this.t.isJSXExpressionContainer(attr.value)) {
613
- this.processStyleAttribute(attr, elId, statements, context);
662
+ this.processStyleAttribute(attr, elId, statements, scope, context);
614
663
  continue;
615
664
  }
616
665
 
@@ -658,6 +707,7 @@ class ElementTransformer {
658
707
  attr: BabelTypes.JSXAttribute,
659
708
  elId: BabelTypes.Identifier,
660
709
  statements: BabelTypes.Statement[],
710
+ scope: any,
661
711
  context: TransformContext
662
712
  ): void {
663
713
  if (!this.t.isJSXExpressionContainer(attr.value)) return;
@@ -672,17 +722,19 @@ class ElementTransformer {
672
722
  context.observableSignals,
673
723
  this.astUtils
674
724
  );
675
- statements.push(
676
- this.t.expressionStatement(
725
+
726
+ const effectCall = this.t.callExpression(this.t.identifier("$effect"), [
727
+ this.t.arrowFunctionExpression(
728
+ [],
677
729
  this.t.callExpression(this.t.identifier("$style"), [
678
730
  elId,
679
- this.t.arrowFunctionExpression(
680
- [],
681
- this.astUtils.replaceThisWithSelf(replaced)
682
- ),
731
+ this.astUtils.replaceThisWithSelf(replaced),
683
732
  ])
684
- )
685
- );
733
+ ),
734
+ ]);
735
+ const cleanupStatements = this.astUtils.addEffectCleanup(scope, effectCall);
736
+
737
+ statements.push(...cleanupStatements);
686
738
  }
687
739
 
688
740
  private processRegularAttribute(
@@ -872,6 +924,7 @@ export function j2d({ types: t }: PluginContext): PluginObj<PluginState> {
872
924
  { local: "$style", imported: "style" },
873
925
  { local: "$spread", imported: "spread" },
874
926
  { local: "$toSignal", imported: "toSignal" },
927
+ { local: "$effect", imported: "effect" },
875
928
  ];
876
929
 
877
930
  for (const helper of helpers) {
@@ -913,7 +966,6 @@ export function j2d({ types: t }: PluginContext): PluginObj<PluginState> {
913
966
  const body = path.node.body;
914
967
  if (!t.isBlockStatement(body)) return;
915
968
 
916
- // Collect all observables from JSX
917
969
  const observables = new Map<string, BabelTypes.Expression>();
918
970
  path.traverse({
919
971
  JSXElement(jsxPath: NodePath<BabelTypes.JSXElement>) {
@@ -932,14 +984,12 @@ export function j2d({ types: t }: PluginContext): PluginObj<PluginState> {
932
984
  },
933
985
  });
934
986
 
935
- // Add self reference
936
987
  body.body.unshift(
937
988
  t.variableDeclaration("const", [
938
989
  t.variableDeclarator(t.identifier("self"), t.thisExpression()),
939
990
  ])
940
991
  );
941
992
 
942
- // Create signal declarations for observables
943
993
  const observableSignals = new Map<string, BabelTypes.Identifier>();
944
994
  const signalDeclarations: BabelTypes.Statement[] = [];
945
995