tova 0.3.4 → 0.3.6

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.
@@ -9,11 +9,21 @@ export class ClientCodegen extends BaseCodegen {
9
9
  this.componentNames = new Set(); // Track component names for JSX
10
10
  this.storeNames = new Set(); // Track store names
11
11
  this._asyncContext = false; // When true, server.xxx() calls emit `await`
12
+ this._rpcCache = new WeakMap(); // Memoize _containsRPC() results
13
+ this._signalCache = new WeakMap(); // Memoize _exprReadsSignal() results
12
14
  }
13
15
 
14
- // AST-walk to check if a subtree contains server.xxx() RPC calls
16
+ // AST-walk to check if a subtree contains server.xxx() RPC calls (memoized)
15
17
  _containsRPC(node) {
16
18
  if (!node) return false;
19
+ const cached = this._rpcCache.get(node);
20
+ if (cached !== undefined) return cached;
21
+ const result = this._containsRPCImpl(node);
22
+ this._rpcCache.set(node, result);
23
+ return result;
24
+ }
25
+
26
+ _containsRPCImpl(node) {
17
27
  if (node.type === 'CallExpression' && this._isRPCCall(node)) return true;
18
28
  if (node.type === 'BlockStatement') return node.body.some(s => this._containsRPC(s));
19
29
  if (node.type === 'ExpressionStatement') return this._containsRPC(node.expression);
@@ -179,7 +189,7 @@ export class ClientCodegen extends BaseCodegen {
179
189
  const lines = [];
180
190
 
181
191
  // Runtime imports
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';`);
192
+ lines.push(`import { createSignal, createEffect, createComputed, mount, hydrate, tova_el, tova_fragment, tova_keyed, tova_transition, tova_inject_css, batch, onMount, onUnmount, onCleanup, onBeforeUpdate, createRef, createContext, provide, inject, createErrorBoundary, ErrorBoundary, ErrorInfo, createRoot, watch, untrack, Dynamic, Portal, lazy, Suspense, __tova_action } from './runtime/reactivity.js';`);
183
193
  lines.push(`import { rpc } from './runtime/rpc.js';`);
184
194
 
185
195
  // Hoist import lines from shared code to the top of the module
@@ -395,25 +405,118 @@ export class ClientCodegen extends BaseCodegen {
395
405
  }
396
406
 
397
407
  // Scope CSS selectors by appending [data-tova-HASH] to each selector
408
+ // Uses a lightweight tokenizer to properly handle:
409
+ // - @media, @keyframes, @layer blocks (don't scope their content selectors)
410
+ // - :is(), :where(), :has() pseudo-functions
411
+ // - :global() escape hatch (strip wrapper, don't scope)
412
+ // - CSS comments /* */
413
+ // - Nested CSS
414
+ // - Multiple rules in sequence
398
415
  _scopeCSS(css, scopeAttr) {
399
- return css.replace(/([^{}@/]+)\{/g, (match, selectorGroup) => {
400
- const selectors = selectorGroup.split(',').map(s => {
401
- s = s.trim();
402
- if (!s || s.startsWith('@') || s === 'from' || s === 'to' || /^\d+%$/.test(s)) return s;
403
- // Handle pseudo-elements (::before, ::after)
404
- const pseudoElMatch = s.match(/(::[\w-]+)$/);
405
- if (pseudoElMatch) {
406
- return s.slice(0, -pseudoElMatch[0].length) + scopeAttr + pseudoElMatch[0];
416
+ const result = [];
417
+ let i = 0;
418
+ let depth = 0;
419
+ let buf = '';
420
+ const noScopeDepths = new Set(); // Depths where we DON'T scope (property decls, @keyframes, @font-face)
421
+
422
+ while (i < css.length) {
423
+ // Skip CSS comments
424
+ if (css[i] === '/' && css[i + 1] === '*') {
425
+ const end = css.indexOf('*/', i + 2);
426
+ if (end === -1) { buf += css.slice(i); break; }
427
+ buf += css.slice(i, end + 2);
428
+ i = end + 2;
429
+ continue;
430
+ }
431
+
432
+ // Skip quoted strings
433
+ if (css[i] === '"' || css[i] === "'") {
434
+ const q = css[i];
435
+ buf += css[i++];
436
+ while (i < css.length && css[i] !== q) {
437
+ if (css[i] === '\\') buf += css[i++];
438
+ buf += css[i++];
407
439
  }
408
- // Handle pseudo-classes (:hover, :focus, etc.)
409
- const pseudoClsMatch = s.match(/(:[\w-]+(?:\([^)]*\))?)$/);
410
- if (pseudoClsMatch) {
411
- return s.slice(0, -pseudoClsMatch[0].length) + scopeAttr + pseudoClsMatch[0];
440
+ if (i < css.length) buf += css[i++];
441
+ continue;
442
+ }
443
+
444
+ // Opening brace — process accumulated buf as selector or pass through
445
+ if (css[i] === '{') {
446
+ const trimmed = buf.trim();
447
+
448
+ if (noScopeDepths.has(depth)) {
449
+ // Inside a no-scope context (property declarations, @keyframes) — pass through
450
+ result.push(buf + '{');
451
+ } else if (trimmed.startsWith('@')) {
452
+ // @keyframes, @font-face: mark inner as no-scope
453
+ if (/^@keyframes\s/.test(trimmed) || /^@font-face/.test(trimmed)) {
454
+ noScopeDepths.add(depth + 1);
455
+ }
456
+ // @media, @supports, @layer: keep scoping inside (don't mark)
457
+ result.push(buf + '{');
458
+ } else {
459
+ // Regular selector — scope it and mark inner depth as no-scope (property declarations)
460
+ const scopedSelectors = buf.split(',').map(s => {
461
+ s = s.trim();
462
+ if (!s || s === 'from' || s === 'to' || /^\d+%$/.test(s)) return s;
463
+ return this._scopeSelector(s, scopeAttr);
464
+ }).join(', ');
465
+ result.push(scopedSelectors + '{');
466
+ noScopeDepths.add(depth + 1);
412
467
  }
413
- return s + scopeAttr;
414
- }).join(', ');
415
- return selectors + ' {';
416
- });
468
+
469
+ depth++;
470
+ buf = '';
471
+ i++;
472
+ continue;
473
+ }
474
+
475
+ // Closing brace
476
+ if (css[i] === '}') {
477
+ result.push(buf + '}');
478
+ buf = '';
479
+ noScopeDepths.delete(depth);
480
+ depth--;
481
+ i++;
482
+ continue;
483
+ }
484
+
485
+ // Accumulate character
486
+ buf += css[i];
487
+ i++;
488
+ }
489
+
490
+ if (buf) result.push(buf);
491
+ return result.join('');
492
+ }
493
+
494
+ // Scope a single CSS selector
495
+ _scopeSelector(selector, scopeAttr) {
496
+ let s = selector.trim();
497
+
498
+ // :global() escape hatch — strip wrapper, don't scope
499
+ if (s.startsWith(':global(') && s.endsWith(')')) {
500
+ return s.slice(8, -1);
501
+ }
502
+ // Inline :global() in the middle of a selector
503
+ s = s.replace(/:global\(([^)]+)\)/g, '$1');
504
+
505
+ // Handle pseudo-elements (::before, ::after, ::placeholder, etc.)
506
+ const pseudoElMatch = s.match(/(::[\w-]+(?:\([^)]*\))?)$/);
507
+ if (pseudoElMatch) {
508
+ return s.slice(0, -pseudoElMatch[0].length) + scopeAttr + pseudoElMatch[0];
509
+ }
510
+ // Handle pseudo-classes with functions (:is(), :where(), :has(), :not(), :hover, etc.)
511
+ const pseudoClsMatch = s.match(/((?::[\w-]+(?:\([^)]*\))?)+)$/);
512
+ if (pseudoClsMatch) {
513
+ const pseudoPart = pseudoClsMatch[0];
514
+ const basePart = s.slice(0, -pseudoPart.length);
515
+ if (basePart.trim()) {
516
+ return basePart + scopeAttr + pseudoPart;
517
+ }
518
+ }
519
+ return s + scopeAttr;
417
520
  }
418
521
 
419
522
  generateComponent(comp) {
@@ -580,10 +683,19 @@ export class ClientCodegen extends BaseCodegen {
580
683
  return p.join('');
581
684
  }
582
685
 
583
- // Check if an AST expression references any signal/computed name
686
+ // Check if an AST expression references any signal/computed name (memoized)
584
687
  _exprReadsSignal(node) {
585
688
  if (!node) return false;
689
+ // Cannot cache Identifier lookups — result depends on current stateNames/computedNames
586
690
  if (node.type === 'Identifier') return this.stateNames.has(node.name) || this.computedNames.has(node.name);
691
+ const cached = this._signalCache.get(node);
692
+ if (cached !== undefined) return cached;
693
+ const result = this._exprReadsSignalImpl(node);
694
+ this._signalCache.set(node, result);
695
+ return result;
696
+ }
697
+
698
+ _exprReadsSignalImpl(node) {
587
699
  if (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') {
588
700
  return this._exprReadsSignal(node.left) || this._exprReadsSignal(node.right);
589
701
  }
@@ -649,6 +761,30 @@ export class ClientCodegen extends BaseCodegen {
649
761
  }
650
762
 
651
763
  genJSXElement(node) {
764
+ // <slot /> or <slot name="header" /> — render children passed from parent
765
+ if (node.tag === 'slot') {
766
+ const nameAttr = node.attributes.find(a => a.name === 'name');
767
+ const slotProps = node.attributes.filter(a => a.name !== 'name');
768
+
769
+ if (nameAttr && nameAttr.value.type === 'StringLiteral') {
770
+ // Named slot: <slot name="header" />
771
+ const slotName = nameAttr.value.value;
772
+ return `(__props.${slotName} || '')`;
773
+ }
774
+
775
+ if (slotProps.length > 0) {
776
+ // Scoped slot: <slot count={count()} /> — pass props to render function
777
+ const propParts = slotProps.map(a => {
778
+ const val = this.genExpression(a.value);
779
+ return `${a.name}: ${val}`;
780
+ });
781
+ return `(typeof __props.children === 'function' ? __props.children({${propParts.join(', ')}}) : (__props.children || ''))`;
782
+ }
783
+
784
+ // Default slot: <slot />
785
+ return `(__props.children || '')`;
786
+ }
787
+
652
788
  const isComponent = node.tag[0] === node.tag[0].toUpperCase() && /^[A-Z]/.test(node.tag);
653
789
 
654
790
  // Attributes
@@ -728,16 +864,82 @@ export class ClientCodegen extends BaseCodegen {
728
864
  // Conditional class: class:active={cond}
729
865
  const className = attr.name.slice(6);
730
866
  classDirectives.push({ className, condition: this.genExpression(attr.value), node: attr.value });
867
+ } else if (attr.name.startsWith('use:')) {
868
+ // use:action directive: use:tooltip={params}
869
+ const actionName = attr.name.slice(4);
870
+ const param = attr.value.type === 'BooleanLiteral' ? 'undefined' : this.genExpression(attr.value);
871
+ const reactive = attr.value.type !== 'BooleanLiteral' && this._exprReadsSignal(attr.value);
872
+ if (!node._actions) node._actions = [];
873
+ node._actions.push({ name: actionName, param, reactive });
874
+ } else if (attr.name.startsWith('in:')) {
875
+ // in:fade — enter-only transition
876
+ const transName = attr.name.slice(3);
877
+ const config = attr.value.type === 'BooleanLiteral' ? '{}' : this.genExpression(attr.value);
878
+ node._inTransition = { name: transName, config };
879
+ } else if (attr.name.startsWith('out:')) {
880
+ // out:slide — leave-only transition
881
+ const transName = attr.name.slice(4);
882
+ const config = attr.value.type === 'BooleanLiteral' ? '{}' : this.genExpression(attr.value);
883
+ node._outTransition = { name: transName, config };
731
884
  } else if (attr.name.startsWith('transition:')) {
732
885
  // transition:fade, transition:slide={duration: 300}, etc.
733
886
  const transName = attr.name.slice(11); // 'fade', 'slide', 'scale', 'fly'
887
+ const builtins = new Set(['fade', 'slide', 'scale', 'fly']);
734
888
  const config = attr.value.type === 'BooleanLiteral' ? '{}' : this.genExpression(attr.value);
735
889
  // Store transition info for element wrapping
736
890
  if (!node._transitions) node._transitions = [];
737
- node._transitions.push({ name: transName, config });
891
+ node._transitions.push({ name: transName, config, custom: !builtins.has(transName) });
892
+ } else if (attr.name === 'bind:this') {
893
+ // bind:this={ref} → ref: refValue (works with both ref objects and functions)
894
+ attrs.ref = this.genExpression(attr.value);
738
895
  } else if (attr.name.startsWith('on:')) {
739
- const eventName = attr.name.slice(3);
740
- events[eventName] = this.genExpression(attr.value);
896
+ const fullName = attr.name.slice(3); // e.g. "click.stop.prevent"
897
+ const parts = fullName.split('.');
898
+ const eventName = parts[0];
899
+ const modifiers = parts.slice(1);
900
+ let handler = this.genExpression(attr.value);
901
+
902
+ if (modifiers.length > 0) {
903
+ const guards = [];
904
+ let useCapture = false;
905
+ let useOnce = false;
906
+
907
+ // Key modifier map for keydown/keyup events
908
+ const keyMap = {
909
+ enter: '"Enter"', escape: '"Escape"', tab: '"Tab"', space: '" "',
910
+ up: '"ArrowUp"', down: '"ArrowDown"', left: '"ArrowLeft"', right: '"ArrowRight"',
911
+ delete: '"Delete"', backspace: '"Backspace"',
912
+ };
913
+
914
+ for (const mod of modifiers) {
915
+ if (mod === 'prevent') {
916
+ guards.push('e.preventDefault()');
917
+ } else if (mod === 'stop') {
918
+ guards.push('e.stopPropagation()');
919
+ } else if (mod === 'self') {
920
+ guards.push('if (e.target !== e.currentTarget) return');
921
+ } else if (mod === 'capture') {
922
+ useCapture = true;
923
+ } else if (mod === 'once') {
924
+ useOnce = true;
925
+ } else if (keyMap[mod]) {
926
+ guards.push(`if (e.key !== ${keyMap[mod]}) return`);
927
+ }
928
+ }
929
+
930
+ if (guards.length > 0) {
931
+ handler = `(e) => { ${guards.join('; ')}; (${handler})(e); }`;
932
+ }
933
+
934
+ if (useCapture || useOnce) {
935
+ const opts = [];
936
+ if (useCapture) opts.push('capture: true');
937
+ if (useOnce) opts.push('once: true');
938
+ handler = `{ handler: ${handler}, options: { ${opts.join(', ')} } }`;
939
+ }
940
+ }
941
+
942
+ events[eventName] = handler;
741
943
  } else {
742
944
  const attrName = attr.name === 'class' ? 'className' : attr.name;
743
945
  const expr = this.genExpression(attr.value);
@@ -784,12 +986,22 @@ export class ClientCodegen extends BaseCodegen {
784
986
  }
785
987
 
786
988
  const propParts = [];
989
+ const memoizedProps = []; // Computed memoization for complex expressions
787
990
  for (const [key, val] of Object.entries(attrs)) {
788
991
  // For component props, convert reactive () => wrappers to JS getter syntax
789
992
  // so the prop stays reactive through the __props access pattern
790
993
  if (isComponent && spreads.length === 0 && typeof val === 'string' && val.startsWith('() => ')) {
791
994
  const rawExpr = val.slice(6);
792
- propParts.push(`get ${key}() { return ${rawExpr}; }`);
995
+ // Simple signal read: just use a getter (no overhead)
996
+ // Complex expressions: memoize with createComputed
997
+ const isSimple = /^[a-zA-Z_$]\w*\(\)$/.test(rawExpr);
998
+ if (isSimple) {
999
+ propParts.push(`get ${key}() { return ${rawExpr}; }`);
1000
+ } else {
1001
+ const memoName = `__memo_${key}`;
1002
+ memoizedProps.push(`const ${memoName} = createComputed(() => ${rawExpr})`);
1003
+ propParts.push(`get ${key}() { return ${memoName}(); }`);
1004
+ }
793
1005
  } else {
794
1006
  propParts.push(`${key}: ${val}`);
795
1007
  }
@@ -844,7 +1056,10 @@ export class ClientCodegen extends BaseCodegen {
844
1056
  propsStr = `{${propParts.join(', ')}}`;
845
1057
  }
846
1058
  }
847
- return `(() => { const __v = ${node.tag}(${propsStr}); if (__v && __v.__tova) __v._componentName = "${node.tag}"; return __v; })()`;
1059
+ if (memoizedProps.length > 0) {
1060
+ return `(() => { ${memoizedProps.join('; ')}; const __v = ${node.tag}(${propsStr}); if (__v && __v.__tova) __v._componentName = "${node.tag}"; return __v; })()`;
1061
+ }
1062
+ return `((__tova_v) => (__tova_v && __tova_v.__tova && (__tova_v._componentName = "${node.tag}"), __tova_v))(${node.tag}(${propsStr}))`;
848
1063
  }
849
1064
 
850
1065
  const tag = JSON.stringify(node.tag);
@@ -860,7 +1075,30 @@ export class ClientCodegen extends BaseCodegen {
860
1075
  // Wrap with transition directives if present
861
1076
  if (node._transitions && node._transitions.length > 0) {
862
1077
  for (const t of node._transitions) {
863
- result = `tova_transition(${result}, "${t.name}", ${t.config})`;
1078
+ if (t.custom) {
1079
+ result = `tova_transition(${result}, ${t.name}, ${t.config})`;
1080
+ } else {
1081
+ result = `tova_transition(${result}, "${t.name}", ${t.config})`;
1082
+ }
1083
+ }
1084
+ }
1085
+
1086
+ // Wrap with directional transitions if present
1087
+ if (node._inTransition || node._outTransition) {
1088
+ const inPart = node._inTransition ? `in: { name: "${node._inTransition.name}", config: ${node._inTransition.config} }` : '';
1089
+ const outPart = node._outTransition ? `out: { name: "${node._outTransition.name}", config: ${node._outTransition.config} }` : '';
1090
+ const parts = [inPart, outPart].filter(Boolean).join(', ');
1091
+ result = `tova_transition(${result}, { ${parts} })`;
1092
+ }
1093
+
1094
+ // Wrap with use: action directives if present
1095
+ if (node._actions && node._actions.length > 0) {
1096
+ for (const a of node._actions) {
1097
+ if (a.reactive) {
1098
+ result = `__tova_action(${result}, ${a.name}, () => ${a.param})`;
1099
+ } else {
1100
+ result = `__tova_action(${result}, ${a.name}, ${a.param})`;
1101
+ }
864
1102
  }
865
1103
  }
866
1104
 
@@ -882,25 +1120,36 @@ export class ClientCodegen extends BaseCodegen {
882
1120
  return this.genExpression(node.value);
883
1121
  }
884
1122
 
1123
+ _genJSXForVar(variable) {
1124
+ if (typeof variable === 'string') return variable;
1125
+ if (variable.type === 'ArrayPattern') {
1126
+ return `[${variable.elements.join(', ')}]`;
1127
+ }
1128
+ if (variable.type === 'ObjectPattern') {
1129
+ return `{${variable.properties.map(p => p.value ? `${p.key}: ${p.value}` : p.key).join(', ')}}`;
1130
+ }
1131
+ return String(variable);
1132
+ }
1133
+
885
1134
  genJSXFor(node) {
886
- const varName = node.variable;
1135
+ const varName = this._genJSXForVar(node.variable);
887
1136
  const iterable = this.genExpression(node.iterable);
888
1137
  const children = node.body.map(c => this.genJSX(c));
1138
+ const needsReactive = this._exprReadsSignal(node.iterable);
1139
+ const wrap = needsReactive ? '() => ' : '';
889
1140
 
890
- // Wrap in reactive closure so the runtime creates a dynamic block that
891
- // re-evaluates when the iterable signal changes
892
1141
  if (node.keyExpr) {
893
1142
  const keyExpr = this.genExpression(node.keyExpr);
894
1143
  if (children.length === 1) {
895
- return `() => ${iterable}.map((${varName}) => tova_keyed(${keyExpr}, ${children[0]}))`;
1144
+ return `${wrap}${iterable}.map((${varName}) => tova_keyed(${keyExpr}, ${children[0]}))`;
896
1145
  }
897
- return `() => ${iterable}.map((${varName}) => tova_keyed(${keyExpr}, tova_fragment([${children.join(', ')}])))`;
1146
+ return `${wrap}${iterable}.map((${varName}) => tova_keyed(${keyExpr}, tova_fragment([${children.join(', ')}])))`;
898
1147
  }
899
1148
 
900
1149
  if (children.length === 1) {
901
- return `() => ${iterable}.map((${varName}) => ${children[0]})`;
1150
+ return `${wrap}${iterable}.map((${varName}) => ${children[0]})`;
902
1151
  }
903
- return `() => ${iterable}.map((${varName}) => tova_fragment([${children.join(', ')}]))`;
1152
+ return `${wrap}${iterable}.map((${varName}) => tova_fragment([${children.join(', ')}]))`;
904
1153
  }
905
1154
 
906
1155
  genJSXIf(node) {
@@ -929,8 +1178,13 @@ export class ClientCodegen extends BaseCodegen {
929
1178
  result += ` : null`;
930
1179
  }
931
1180
 
932
- // Wrap in reactive closure so the runtime creates a dynamic block
933
- return `() => ${result}`;
1181
+ // Only wrap in reactive closure if the condition reads signals
1182
+ const needsReactive = this._exprReadsSignal(node.condition) ||
1183
+ (node.alternates && node.alternates.some(a => this._exprReadsSignal(a.condition)));
1184
+ if (needsReactive) {
1185
+ return `() => ${result}`;
1186
+ }
1187
+ return result;
934
1188
  }
935
1189
 
936
1190
  genJSXMatch(node) {
@@ -962,8 +1216,11 @@ export class ClientCodegen extends BaseCodegen {
962
1216
  }
963
1217
 
964
1218
  p.push(`})(${subject})`);
965
- // Wrap in reactive closure
966
- return `() => ${p.join('')}`;
1219
+ // Only wrap in reactive closure if the subject reads signals
1220
+ if (this._exprReadsSignal(node.subject)) {
1221
+ return `() => ${p.join('')}`;
1222
+ }
1223
+ return p.join('');
967
1224
  }
968
1225
 
969
1226
  genJSXFragment(node) {
@@ -4,31 +4,22 @@
4
4
 
5
5
  import { SharedCodegen } from './shared-codegen.js';
6
6
  import { BUILTIN_NAMES } from '../stdlib/inline.js';
7
-
8
- // Lazy-loaded codegen modules only imported when server/client blocks exist
9
- let _ServerCodegen = null;
10
- let _ClientCodegen = null;
7
+ import { ServerCodegen } from './server-codegen.js';
8
+ import { ClientCodegen } from './client-codegen.js';
11
9
 
12
10
  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;
11
+ return ServerCodegen;
18
12
  }
19
13
 
20
14
  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;
15
+ return ClientCodegen;
26
16
  }
27
17
 
28
18
  export class CodeGenerator {
29
- constructor(ast, filename = '<stdin>') {
19
+ constructor(ast, filename = '<stdin>', options = {}) {
30
20
  this.ast = ast;
31
21
  this.filename = filename;
22
+ this._sourceMaps = options.sourceMaps !== false; // default true; pass false for REPL/check
32
23
  }
33
24
 
34
25
  // Group blocks by name (null name = "default")
@@ -72,6 +63,7 @@ export class CodeGenerator {
72
63
 
73
64
  if (isModule) {
74
65
  const moduleGen = new SharedCodegen();
66
+ moduleGen._sourceMapsEnabled = this._sourceMaps;
75
67
  moduleGen.setSourceFile(this.filename);
76
68
  const moduleCode = topLevel.map(s => moduleGen.generateStatement(s)).join('\n');
77
69
  const helpers = moduleGen.generateHelpers();
@@ -87,6 +79,7 @@ export class CodeGenerator {
87
79
  }
88
80
 
89
81
  const sharedGen = new SharedCodegen();
82
+ sharedGen._sourceMapsEnabled = this._sourceMaps;
90
83
  sharedGen.setSourceFile(this.filename);
91
84
 
92
85
  // All shared blocks (regardless of name) are merged into one shared output
@@ -134,6 +127,7 @@ export class CodeGenerator {
134
127
  const servers = {};
135
128
  for (const [name, blocks] of serverGroups) {
136
129
  const gen = new (getServerCodegen())();
130
+ gen._sourceMapsEnabled = this._sourceMaps;
137
131
  const key = name || 'default';
138
132
  // Build peer blocks map (all named blocks except self)
139
133
  let peerBlocks = null;
@@ -152,6 +146,7 @@ export class CodeGenerator {
152
146
  const clients = {};
153
147
  for (const [name, blocks] of clientGroups) {
154
148
  const gen = new (getClientCodegen())();
149
+ gen._sourceMapsEnabled = this._sourceMaps;
155
150
  const key = name || 'default';
156
151
  clients[key] = gen.generate(blocks, combinedShared, sharedGen._usedBuiltins);
157
152
  }