tova 0.3.0 → 0.3.1
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.
- package/bin/tova.js +1401 -111
- package/package.json +3 -1
- package/src/analyzer/analyzer.js +831 -709
- package/src/analyzer/client-analyzer.js +191 -0
- package/src/analyzer/server-analyzer.js +467 -0
- package/src/analyzer/types.js +20 -4
- package/src/codegen/base-codegen.js +467 -109
- package/src/codegen/client-codegen.js +92 -42
- package/src/codegen/codegen.js +65 -5
- package/src/codegen/server-codegen.js +290 -36
- package/src/diagnostics/error-codes.js +255 -0
- package/src/diagnostics/formatter.js +150 -28
- package/src/docs/generator.js +390 -0
- package/src/lexer/lexer.js +305 -63
- package/src/lexer/tokens.js +19 -0
- package/src/lsp/server.js +892 -30
- package/src/parser/ast.js +81 -368
- package/src/parser/client-ast.js +138 -0
- package/src/parser/client-parser.js +504 -0
- package/src/parser/parser.js +491 -1064
- package/src/parser/server-ast.js +240 -0
- package/src/parser/server-parser.js +602 -0
- package/src/runtime/array-proto.js +32 -0
- package/src/runtime/embedded.js +1 -1
- package/src/runtime/reactivity.js +191 -10
- package/src/stdlib/advanced-collections.js +81 -0
- package/src/stdlib/inline.js +549 -6
- package/src/version.js +1 -1
|
@@ -179,7 +179,7 @@ export class ClientCodegen extends BaseCodegen {
|
|
|
179
179
|
const lines = [];
|
|
180
180
|
|
|
181
181
|
// Runtime imports
|
|
182
|
-
lines.push(`import { createSignal, createEffect, createComputed, mount, hydrate, tova_el, tova_fragment, tova_keyed, tova_inject_css, batch, onMount, onUnmount, onCleanup, createRef, createContext, provide, inject, createErrorBoundary, ErrorBoundary, createRoot, watch, untrack, Dynamic, Portal, lazy } from './runtime/reactivity.js';`);
|
|
182
|
+
lines.push(`import { createSignal, createEffect, createComputed, mount, hydrate, tova_el, tova_fragment, tova_keyed, tova_transition, tova_inject_css, batch, onMount, onUnmount, onCleanup, createRef, createContext, provide, inject, createErrorBoundary, ErrorBoundary, ErrorInfo, createRoot, watch, untrack, Dynamic, Portal, lazy } from './runtime/reactivity.js';`);
|
|
183
183
|
lines.push(`import { rpc } from './runtime/rpc.js';`);
|
|
184
184
|
|
|
185
185
|
// Hoist import lines from shared code to the top of the module
|
|
@@ -362,26 +362,26 @@ export class ClientCodegen extends BaseCodegen {
|
|
|
362
362
|
|
|
363
363
|
_generateEffect(body) {
|
|
364
364
|
const hasRPC = this._containsRPC(body);
|
|
365
|
-
|
|
365
|
+
const p = [];
|
|
366
366
|
if (hasRPC) {
|
|
367
|
-
|
|
368
|
-
|
|
367
|
+
p.push(`createEffect(() => {\n`);
|
|
368
|
+
p.push(`${this.i()} (async () => {\n`);
|
|
369
369
|
this.indent += 2;
|
|
370
370
|
const prevAsync = this._asyncContext;
|
|
371
371
|
this._asyncContext = true;
|
|
372
|
-
|
|
372
|
+
p.push(this.genBlockStatements(body));
|
|
373
373
|
this._asyncContext = prevAsync;
|
|
374
374
|
this.indent -= 2;
|
|
375
|
-
|
|
376
|
-
|
|
375
|
+
p.push(`\n${this.i()} })();\n`);
|
|
376
|
+
p.push(`${this.i()}});`);
|
|
377
377
|
} else {
|
|
378
|
-
|
|
378
|
+
p.push(`createEffect(() => {\n`);
|
|
379
379
|
this.indent++;
|
|
380
|
-
|
|
380
|
+
p.push(this.genBlockStatements(body));
|
|
381
381
|
this.indent--;
|
|
382
|
-
|
|
382
|
+
p.push(`\n${this.i()}});`);
|
|
383
383
|
}
|
|
384
|
-
return
|
|
384
|
+
return p.join('');
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
// Generate a short hash from component name + CSS content (for CSS scoping)
|
|
@@ -424,20 +424,21 @@ export class ClientCodegen extends BaseCodegen {
|
|
|
424
424
|
const savedState = new Set(this.stateNames);
|
|
425
425
|
const savedComputed = new Set(this.computedNames);
|
|
426
426
|
|
|
427
|
-
|
|
427
|
+
const p = [];
|
|
428
|
+
p.push(`function ${comp.name}(${paramStr}) {\n`);
|
|
428
429
|
this.indent++;
|
|
429
430
|
|
|
430
431
|
// Generate reactive prop accessors — each prop is accessed through __props getter
|
|
431
432
|
// This ensures parent signal changes propagate reactively to the child
|
|
432
433
|
if (hasParams) {
|
|
433
|
-
for (const
|
|
434
|
-
this.computedNames.add(
|
|
435
|
-
const def =
|
|
434
|
+
for (const param of comp.params) {
|
|
435
|
+
this.computedNames.add(param.name);
|
|
436
|
+
const def = param.default || param.defaultValue;
|
|
436
437
|
if (def) {
|
|
437
438
|
const defaultExpr = this.genExpression(def);
|
|
438
|
-
|
|
439
|
+
p.push(`${this.i()}const ${param.name} = () => __props.${param.name} !== undefined ? __props.${param.name} : ${defaultExpr};\n`);
|
|
439
440
|
} else {
|
|
440
|
-
|
|
441
|
+
p.push(`${this.i()}const ${param.name} = () => __props.${param.name};\n`);
|
|
441
442
|
}
|
|
442
443
|
}
|
|
443
444
|
}
|
|
@@ -448,7 +449,7 @@ export class ClientCodegen extends BaseCodegen {
|
|
|
448
449
|
const bodyItems = [];
|
|
449
450
|
|
|
450
451
|
for (const node of comp.body) {
|
|
451
|
-
if (node.type === 'JSXElement' || node.type === 'JSXFor' || node.type === 'JSXIf') {
|
|
452
|
+
if (node.type === 'JSXElement' || node.type === 'JSXFragment' || node.type === 'JSXFor' || node.type === 'JSXIf') {
|
|
452
453
|
jsxElements.push(node);
|
|
453
454
|
} else if (node.type === 'ComponentStyleBlock') {
|
|
454
455
|
styleBlocks.push(node);
|
|
@@ -464,7 +465,7 @@ export class ClientCodegen extends BaseCodegen {
|
|
|
464
465
|
const scopeId = this._genScopeId(comp.name, rawCSS);
|
|
465
466
|
this._currentScopeId = scopeId;
|
|
466
467
|
const scopedCSS = this._scopeCSS(rawCSS, `[data-tova-${scopeId}]`);
|
|
467
|
-
|
|
468
|
+
p.push(`${this.i()}tova_inject_css(${JSON.stringify(scopeId)}, ${JSON.stringify(scopedCSS)});\n`);
|
|
468
469
|
}
|
|
469
470
|
|
|
470
471
|
// Generate body items in order (state, computed, effect, other statements)
|
|
@@ -472,38 +473,38 @@ export class ClientCodegen extends BaseCodegen {
|
|
|
472
473
|
if (node.type === 'StateDeclaration') {
|
|
473
474
|
this.stateNames.add(node.name);
|
|
474
475
|
const init = this.genExpression(node.initialValue);
|
|
475
|
-
|
|
476
|
+
p.push(`${this.i()}const [${node.name}, set${capitalize(node.name)}] = createSignal(${init});\n`);
|
|
476
477
|
} else if (node.type === 'ComputedDeclaration') {
|
|
477
478
|
this.computedNames.add(node.name);
|
|
478
479
|
const expr = this.genExpression(node.expression);
|
|
479
|
-
|
|
480
|
+
p.push(`${this.i()}const ${node.name} = createComputed(() => ${expr});\n`);
|
|
480
481
|
} else if (node.type === 'EffectDeclaration') {
|
|
481
482
|
this.indent++;
|
|
482
483
|
const effectCode = this._generateEffect(node.body);
|
|
483
484
|
this.indent--;
|
|
484
|
-
|
|
485
|
+
p.push(`${this.i()}${effectCode}\n`);
|
|
485
486
|
} else {
|
|
486
|
-
|
|
487
|
+
p.push(this.generateStatement(node) + '\n');
|
|
487
488
|
}
|
|
488
489
|
}
|
|
489
490
|
|
|
490
491
|
// Generate JSX return
|
|
491
492
|
if (jsxElements.length === 1) {
|
|
492
|
-
|
|
493
|
+
p.push(`${this.i()}return ${this.genJSX(jsxElements[0])};\n`);
|
|
493
494
|
} else if (jsxElements.length > 1) {
|
|
494
495
|
const children = jsxElements.map(el => this.genJSX(el)).join(', ');
|
|
495
|
-
|
|
496
|
+
p.push(`${this.i()}return tova_fragment([${children}]);\n`);
|
|
496
497
|
}
|
|
497
498
|
|
|
498
499
|
this.indent--;
|
|
499
|
-
|
|
500
|
+
p.push(`}`);
|
|
500
501
|
|
|
501
502
|
// Restore scoped names and scope id
|
|
502
503
|
this.stateNames = savedState;
|
|
503
504
|
this.computedNames = savedComputed;
|
|
504
505
|
this._currentScopeId = savedScopeId;
|
|
505
506
|
|
|
506
|
-
return
|
|
507
|
+
return p.join('');
|
|
507
508
|
}
|
|
508
509
|
|
|
509
510
|
generateStore(store) {
|
|
@@ -528,54 +529,55 @@ export class ClientCodegen extends BaseCodegen {
|
|
|
528
529
|
}
|
|
529
530
|
}
|
|
530
531
|
|
|
531
|
-
|
|
532
|
+
const p = [];
|
|
533
|
+
p.push(`const ${store.name} = (() => {\n`);
|
|
532
534
|
this.indent++;
|
|
533
535
|
|
|
534
536
|
// Generate state signals
|
|
535
537
|
for (const s of storeStates) {
|
|
536
538
|
const init = this.genExpression(s.initialValue);
|
|
537
|
-
|
|
539
|
+
p.push(`${this.i()}const [${s.name}, set${capitalize(s.name)}] = createSignal(${init});\n`);
|
|
538
540
|
}
|
|
539
541
|
|
|
540
542
|
// Generate computed values
|
|
541
543
|
for (const c of storeComputeds) {
|
|
542
544
|
const expr = this.genExpression(c.expression);
|
|
543
|
-
|
|
545
|
+
p.push(`${this.i()}const ${c.name} = createComputed(() => ${expr});\n`);
|
|
544
546
|
}
|
|
545
547
|
|
|
546
548
|
// Generate functions
|
|
547
549
|
for (const fn of storeFunctions) {
|
|
548
|
-
|
|
550
|
+
p.push(this.genFunctionDeclaration(fn) + '\n');
|
|
549
551
|
}
|
|
550
552
|
|
|
551
553
|
// Build return object with getters/setters
|
|
552
|
-
|
|
554
|
+
p.push(`${this.i()}return {\n`);
|
|
553
555
|
this.indent++;
|
|
554
556
|
|
|
555
557
|
for (const s of storeStates) {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
+
p.push(`${this.i()}get ${s.name}() { return ${s.name}(); },\n`);
|
|
559
|
+
p.push(`${this.i()}set ${s.name}(v) { set${capitalize(s.name)}(v); },\n`);
|
|
558
560
|
}
|
|
559
561
|
|
|
560
562
|
for (const c of storeComputeds) {
|
|
561
|
-
|
|
563
|
+
p.push(`${this.i()}get ${c.name}() { return ${c.name}(); },\n`);
|
|
562
564
|
}
|
|
563
565
|
|
|
564
566
|
for (const fn of storeFunctions) {
|
|
565
|
-
|
|
567
|
+
p.push(`${this.i()}${fn.name},\n`);
|
|
566
568
|
}
|
|
567
569
|
|
|
568
570
|
this.indent--;
|
|
569
|
-
|
|
571
|
+
p.push(`${this.i()}};\n`);
|
|
570
572
|
|
|
571
573
|
this.indent--;
|
|
572
|
-
|
|
574
|
+
p.push(`${this.i()}})();`);
|
|
573
575
|
|
|
574
576
|
// Restore state/computed names
|
|
575
577
|
this.stateNames = savedState;
|
|
576
578
|
this.computedNames = savedComputed;
|
|
577
579
|
|
|
578
|
-
return
|
|
580
|
+
return p.join('');
|
|
579
581
|
}
|
|
580
582
|
|
|
581
583
|
// Check if an AST expression references any signal/computed name
|
|
@@ -626,6 +628,7 @@ export class ClientCodegen extends BaseCodegen {
|
|
|
626
628
|
|
|
627
629
|
switch (node.type) {
|
|
628
630
|
case 'JSXElement': return this.genJSXElement(node);
|
|
631
|
+
case 'JSXFragment': return this.genJSXFragment(node);
|
|
629
632
|
case 'JSXText': return this.genJSXText(node);
|
|
630
633
|
case 'JSXExpression': {
|
|
631
634
|
// If expression reads a signal, wrap as () => expr for fine-grained reactivity
|
|
@@ -705,10 +708,24 @@ export class ClientCodegen extends BaseCodegen {
|
|
|
705
708
|
events.change = `(e) => { set${capitalize(exprName)}(${valueExpr}); }`;
|
|
706
709
|
}
|
|
707
710
|
}
|
|
711
|
+
} else if (attr.name === 'show') {
|
|
712
|
+
// show={condition} → toggles display:none instead of removing from DOM
|
|
713
|
+
const expr = this.genExpression(attr.value);
|
|
714
|
+
const reactive = this._exprReadsSignal(attr.value);
|
|
715
|
+
const displayExpr = `(${expr}) ? "" : "none"`;
|
|
716
|
+
// Store show directive to merge with style later
|
|
717
|
+
node._showDirective = { expr: displayExpr, reactive };
|
|
708
718
|
} else if (attr.name.startsWith('class:')) {
|
|
709
719
|
// Conditional class: class:active={cond}
|
|
710
720
|
const className = attr.name.slice(6);
|
|
711
721
|
classDirectives.push({ className, condition: this.genExpression(attr.value), node: attr.value });
|
|
722
|
+
} else if (attr.name.startsWith('transition:')) {
|
|
723
|
+
// transition:fade, transition:slide={duration: 300}, etc.
|
|
724
|
+
const transName = attr.name.slice(11); // 'fade', 'slide', 'scale', 'fly'
|
|
725
|
+
const config = attr.value.type === 'BooleanLiteral' ? '{}' : this.genExpression(attr.value);
|
|
726
|
+
// Store transition info for element wrapping
|
|
727
|
+
if (!node._transitions) node._transitions = [];
|
|
728
|
+
node._transitions.push({ name: transName, config });
|
|
712
729
|
} else if (attr.name.startsWith('on:')) {
|
|
713
730
|
const eventName = attr.name.slice(3);
|
|
714
731
|
events[eventName] = this.genExpression(attr.value);
|
|
@@ -734,6 +751,24 @@ export class ClientCodegen extends BaseCodegen {
|
|
|
734
751
|
attrs.className = isReactive ? `() => ${classExpr}` : classExpr;
|
|
735
752
|
}
|
|
736
753
|
|
|
754
|
+
// Merge show directive with style (show toggles display:none)
|
|
755
|
+
if (node._showDirective) {
|
|
756
|
+
const { expr: displayExpr, reactive } = node._showDirective;
|
|
757
|
+
if (attrs.style) {
|
|
758
|
+
// Merge with existing style object
|
|
759
|
+
const existing = attrs.style;
|
|
760
|
+
if (reactive) {
|
|
761
|
+
attrs.style = `() => Object.assign({}, ${existing}, { display: ${displayExpr} })`;
|
|
762
|
+
} else {
|
|
763
|
+
attrs.style = `Object.assign({}, ${existing}, { display: ${displayExpr} })`;
|
|
764
|
+
}
|
|
765
|
+
} else {
|
|
766
|
+
attrs.style = reactive
|
|
767
|
+
? `() => ({ display: ${displayExpr} })`
|
|
768
|
+
: `{ display: ${displayExpr} }`;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
737
772
|
// Add scoped CSS attribute to HTML elements (not components)
|
|
738
773
|
if (this._currentScopeId && !isComponent) {
|
|
739
774
|
attrs[`"data-tova-${this._currentScopeId}"`] = '""';
|
|
@@ -805,12 +840,22 @@ export class ClientCodegen extends BaseCodegen {
|
|
|
805
840
|
|
|
806
841
|
const tag = JSON.stringify(node.tag);
|
|
807
842
|
|
|
843
|
+
let result;
|
|
808
844
|
if (node.selfClosing || node.children.length === 0) {
|
|
809
|
-
|
|
845
|
+
result = `tova_el(${tag}, ${propsStr})`;
|
|
846
|
+
} else {
|
|
847
|
+
const children = node.children.map(c => this.genJSX(c)).join(', ');
|
|
848
|
+
result = `tova_el(${tag}, ${propsStr}, [${children}])`;
|
|
810
849
|
}
|
|
811
850
|
|
|
812
|
-
|
|
813
|
-
|
|
851
|
+
// Wrap with transition directives if present
|
|
852
|
+
if (node._transitions && node._transitions.length > 0) {
|
|
853
|
+
for (const t of node._transitions) {
|
|
854
|
+
result = `tova_transition(${result}, "${t.name}", ${t.config})`;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
return result;
|
|
814
859
|
}
|
|
815
860
|
|
|
816
861
|
genJSXText(node) {
|
|
@@ -879,6 +924,11 @@ export class ClientCodegen extends BaseCodegen {
|
|
|
879
924
|
return `() => ${result}`;
|
|
880
925
|
}
|
|
881
926
|
|
|
927
|
+
genJSXFragment(node) {
|
|
928
|
+
const children = node.children.map(c => this.genJSX(c)).join(', ');
|
|
929
|
+
return `tova_fragment([${children}])`;
|
|
930
|
+
}
|
|
931
|
+
|
|
882
932
|
// Override to add await for piped RPC calls
|
|
883
933
|
genPipeExpression(node) {
|
|
884
934
|
const result = super.genPipeExpression(node);
|
package/src/codegen/codegen.js
CHANGED
|
@@ -3,10 +3,28 @@
|
|
|
3
3
|
// Blocks with the same name are merged; different names produce separate output files.
|
|
4
4
|
|
|
5
5
|
import { SharedCodegen } from './shared-codegen.js';
|
|
6
|
-
import { ServerCodegen } from './server-codegen.js';
|
|
7
|
-
import { ClientCodegen } from './client-codegen.js';
|
|
8
6
|
import { BUILTIN_NAMES } from '../stdlib/inline.js';
|
|
9
7
|
|
|
8
|
+
// Lazy-loaded codegen modules — only imported when server/client blocks exist
|
|
9
|
+
let _ServerCodegen = null;
|
|
10
|
+
let _ClientCodegen = null;
|
|
11
|
+
|
|
12
|
+
function getServerCodegen() {
|
|
13
|
+
if (!_ServerCodegen) {
|
|
14
|
+
// Dynamic require avoids loading server-codegen.js for client-only builds
|
|
15
|
+
_ServerCodegen = import.meta.require('./server-codegen.js').ServerCodegen;
|
|
16
|
+
}
|
|
17
|
+
return _ServerCodegen;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getClientCodegen() {
|
|
21
|
+
if (!_ClientCodegen) {
|
|
22
|
+
// Dynamic require avoids loading client-codegen.js for server-only builds
|
|
23
|
+
_ClientCodegen = import.meta.require('./client-codegen.js').ClientCodegen;
|
|
24
|
+
}
|
|
25
|
+
return _ClientCodegen;
|
|
26
|
+
}
|
|
27
|
+
|
|
10
28
|
export class CodeGenerator {
|
|
11
29
|
constructor(ast, filename = '<stdin>') {
|
|
12
30
|
this.ast = ast;
|
|
@@ -31,6 +49,7 @@ export class CodeGenerator {
|
|
|
31
49
|
const topLevel = [];
|
|
32
50
|
|
|
33
51
|
const testBlocks = [];
|
|
52
|
+
const benchBlocks = [];
|
|
34
53
|
const dataBlocks = [];
|
|
35
54
|
|
|
36
55
|
for (const node of this.ast.body) {
|
|
@@ -39,12 +58,36 @@ export class CodeGenerator {
|
|
|
39
58
|
case 'ServerBlock': serverBlocks.push(node); break;
|
|
40
59
|
case 'ClientBlock': clientBlocks.push(node); break;
|
|
41
60
|
case 'TestBlock': testBlocks.push(node); break;
|
|
61
|
+
case 'BenchBlock': benchBlocks.push(node); break;
|
|
42
62
|
case 'DataBlock': dataBlocks.push(node); break;
|
|
43
63
|
default: topLevel.push(node); break;
|
|
44
64
|
}
|
|
45
65
|
}
|
|
46
66
|
|
|
67
|
+
// Detect module mode: no blocks, only top-level statements
|
|
68
|
+
const isModule = sharedBlocks.length === 0 && serverBlocks.length === 0
|
|
69
|
+
&& clientBlocks.length === 0 && testBlocks.length === 0
|
|
70
|
+
&& benchBlocks.length === 0 && dataBlocks.length === 0
|
|
71
|
+
&& topLevel.length > 0;
|
|
72
|
+
|
|
73
|
+
if (isModule) {
|
|
74
|
+
const moduleGen = new SharedCodegen();
|
|
75
|
+
moduleGen.setSourceFile(this.filename);
|
|
76
|
+
const moduleCode = topLevel.map(s => moduleGen.generateStatement(s)).join('\n');
|
|
77
|
+
const helpers = moduleGen.generateHelpers();
|
|
78
|
+
const combined = [helpers, moduleCode].filter(s => s.trim()).join('\n').trim();
|
|
79
|
+
return {
|
|
80
|
+
shared: combined,
|
|
81
|
+
server: '',
|
|
82
|
+
client: '',
|
|
83
|
+
isModule: true,
|
|
84
|
+
sourceMappings: moduleGen.getSourceMappings(),
|
|
85
|
+
_sourceFile: this.filename,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
47
89
|
const sharedGen = new SharedCodegen();
|
|
90
|
+
sharedGen.setSourceFile(this.filename);
|
|
48
91
|
|
|
49
92
|
// All shared blocks (regardless of name) are merged into one shared output
|
|
50
93
|
const sharedCode = sharedBlocks.map(b => sharedGen.generate(b)).join('\n');
|
|
@@ -90,7 +133,7 @@ export class CodeGenerator {
|
|
|
90
133
|
// Generate server outputs (one per named group)
|
|
91
134
|
const servers = {};
|
|
92
135
|
for (const [name, blocks] of serverGroups) {
|
|
93
|
-
const gen = new
|
|
136
|
+
const gen = new (getServerCodegen())();
|
|
94
137
|
const key = name || 'default';
|
|
95
138
|
// Build peer blocks map (all named blocks except self)
|
|
96
139
|
let peerBlocks = null;
|
|
@@ -108,7 +151,7 @@ export class CodeGenerator {
|
|
|
108
151
|
// Generate client outputs (one per named group)
|
|
109
152
|
const clients = {};
|
|
110
153
|
for (const [name, blocks] of clientGroups) {
|
|
111
|
-
const gen = new
|
|
154
|
+
const gen = new (getClientCodegen())();
|
|
112
155
|
const key = name || 'default';
|
|
113
156
|
clients[key] = gen.generate(blocks, combinedShared, sharedGen._usedBuiltins);
|
|
114
157
|
}
|
|
@@ -116,7 +159,7 @@ export class CodeGenerator {
|
|
|
116
159
|
// Generate tests if test blocks exist
|
|
117
160
|
let testCode = '';
|
|
118
161
|
if (testBlocks.length > 0) {
|
|
119
|
-
const testGen = new
|
|
162
|
+
const testGen = new (getServerCodegen())();
|
|
120
163
|
testCode = testGen.generateTests(testBlocks);
|
|
121
164
|
|
|
122
165
|
// Add __handleRequest export to server code
|
|
@@ -126,6 +169,13 @@ export class CodeGenerator {
|
|
|
126
169
|
}
|
|
127
170
|
}
|
|
128
171
|
|
|
172
|
+
// Generate benchmarks if bench blocks exist
|
|
173
|
+
let benchCode = '';
|
|
174
|
+
if (benchBlocks.length > 0) {
|
|
175
|
+
const benchGen = new (getServerCodegen())();
|
|
176
|
+
benchCode = benchGen.generateBench(benchBlocks);
|
|
177
|
+
}
|
|
178
|
+
|
|
129
179
|
// Backward-compatible: if only unnamed blocks, return flat structure
|
|
130
180
|
const hasNamedBlocks = [...serverGroups.keys(), ...clientGroups.keys()].some(k => k !== null);
|
|
131
181
|
|
|
@@ -138,8 +188,10 @@ export class CodeGenerator {
|
|
|
138
188
|
server: servers['default'] || '',
|
|
139
189
|
client: clients['default'] || '',
|
|
140
190
|
sourceMappings,
|
|
191
|
+
_sourceFile: this.filename,
|
|
141
192
|
};
|
|
142
193
|
if (testCode) result.test = testCode;
|
|
194
|
+
if (benchCode) result.bench = benchCode;
|
|
143
195
|
return result;
|
|
144
196
|
}
|
|
145
197
|
|
|
@@ -152,8 +204,10 @@ export class CodeGenerator {
|
|
|
152
204
|
clients, // { "admin": code, "dashboard": code, ... }
|
|
153
205
|
multiBlock: true,
|
|
154
206
|
sourceMappings,
|
|
207
|
+
_sourceFile: this.filename,
|
|
155
208
|
};
|
|
156
209
|
if (testCode) result.test = testCode;
|
|
210
|
+
if (benchCode) result.bench = benchCode;
|
|
157
211
|
return result;
|
|
158
212
|
}
|
|
159
213
|
|
|
@@ -167,6 +221,12 @@ export class CodeGenerator {
|
|
|
167
221
|
if (node.type === 'CallExpression' && node.callee && node.callee.type === 'Identifier' && BUILTIN_NAMES.has(node.callee.name)) {
|
|
168
222
|
targetSet.add(node.callee.name);
|
|
169
223
|
}
|
|
224
|
+
// Track namespace builtin usage: math.sin() or math.PI
|
|
225
|
+
if (node.type === 'MemberExpression' &&
|
|
226
|
+
node.object.type === 'Identifier' &&
|
|
227
|
+
BUILTIN_NAMES.has(node.object.name)) {
|
|
228
|
+
targetSet.add(node.object.name);
|
|
229
|
+
}
|
|
170
230
|
for (const key of Object.keys(node)) {
|
|
171
231
|
if (key === 'loc' || key === 'type') continue;
|
|
172
232
|
const val = node[key];
|