@sprlab/wccompiler 0.5.1 → 0.5.2

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/lib/codegen.js CHANGED
@@ -541,8 +541,6 @@ export function generateComponent(parseResult, options = {}) {
541
541
  const usedRuntime = new Set(['__signal']); // always need __signal
542
542
  if (computeds.length > 0) usedRuntime.add('__computed');
543
543
  if (effects.length > 0 || bindings.length > 0 || showBindings.length > 0 || modelBindings.length > 0 || attrBindings.length > 0 || ifBlocks.length > 0 || forBlocks.length > 0 || watchers.length > 0 || childComponents.length > 0 || slots.some(s => s.slotProps.length > 0)) usedRuntime.add('__effect');
544
- // __batch is available but only needed if user code calls it explicitly — always include for safety
545
- usedRuntime.add('__batch');
546
544
  const imports = [...usedRuntime].join(', ');
547
545
  lines.push(`import { ${imports} } from '${options.runtimeImportPath}';`);
548
546
  } else {
@@ -745,27 +743,28 @@ export function generateComponent(parseResult, options = {}) {
745
743
  lines.push(' if (this.__connected) return;');
746
744
  lines.push(' this.__connected = true;');
747
745
  lines.push(' this.__ac = new AbortController();');
746
+ lines.push(' this.__disposers = [];');
748
747
  lines.push('');
749
748
 
750
749
  // Binding effects — one __effect per binding
751
750
  for (const b of bindings) {
752
751
  if (b.type === 'prop') {
753
- lines.push(' __effect(() => {');
752
+ lines.push(' this.__disposers.push(__effect(() => {');
754
753
  lines.push(` this.${b.varName}.textContent = this._s_${b.name}() ?? '';`);
755
- lines.push(' });');
754
+ lines.push(' }));');
756
755
  } else if (b.type === 'signal') {
757
- lines.push(' __effect(() => {');
756
+ lines.push(' this.__disposers.push(__effect(() => {');
758
757
  lines.push(` this.${b.varName}.textContent = this._${b.name}() ?? '';`);
759
- lines.push(' });');
758
+ lines.push(' }));');
760
759
  } else if (b.type === 'computed') {
761
- lines.push(' __effect(() => {');
760
+ lines.push(' this.__disposers.push(__effect(() => {');
762
761
  lines.push(` this.${b.varName}.textContent = this._c_${b.name}() ?? '';`);
763
- lines.push(' });');
762
+ lines.push(' }));');
764
763
  } else {
765
764
  // method type — call the method
766
- lines.push(' __effect(() => {');
765
+ lines.push(' this.__disposers.push(__effect(() => {');
767
766
  lines.push(` this.${b.varName}.textContent = this._${b.name}() ?? '';`);
768
- lines.push(' });');
767
+ lines.push(' }));');
769
768
  }
770
769
  }
771
770
 
@@ -804,22 +803,22 @@ export function generateComponent(parseResult, options = {}) {
804
803
  } else {
805
804
  ref = `this._${pb.expr}()`;
806
805
  }
807
- lines.push(' __effect(() => {');
806
+ lines.push(' this.__disposers.push(__effect(() => {');
808
807
  lines.push(` this.${cc.varName}.setAttribute('${pb.attr}', ${ref} ?? '');`);
809
- lines.push(' });');
808
+ lines.push(' }));');
810
809
  }
811
810
  }
812
811
 
813
812
  // User effects
814
813
  for (const eff of effects) {
815
814
  const body = transformMethodBody(eff.body, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, refVarNames, constantNames);
816
- lines.push(' __effect(() => {');
815
+ lines.push(' this.__disposers.push(__effect(() => {');
817
816
  // Indent each line of the body
818
817
  const bodyLines = body.split('\n');
819
818
  for (const line of bodyLines) {
820
819
  lines.push(` ${line}`);
821
820
  }
822
- lines.push(' });');
821
+ lines.push(' }));');
823
822
  }
824
823
 
825
824
  // Watcher effects
@@ -837,7 +836,7 @@ export function generateComponent(parseResult, options = {}) {
837
836
  } else {
838
837
  watchRef = `this._${w.target}()`;
839
838
  }
840
- lines.push(' __effect(() => {');
839
+ lines.push(' this.__disposers.push(__effect(() => {');
841
840
  lines.push(` const ${w.newParam} = ${watchRef};`);
842
841
  lines.push(` if (this.__prev_${w.target} !== undefined) {`);
843
842
  lines.push(` const ${w.oldParam} = this.__prev_${w.target};`);
@@ -847,12 +846,12 @@ export function generateComponent(parseResult, options = {}) {
847
846
  }
848
847
  lines.push(' }');
849
848
  lines.push(` this.__prev_${w.target} = ${w.newParam};`);
850
- lines.push(' });');
849
+ lines.push(' }));');
851
850
  } else {
852
851
  // kind === 'getter' — transform the getter expression and use it directly
853
852
  const getterExpr = transformMethodBody(w.target, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, refVarNames, constantNames);
854
853
  const prevName = `__prev_watch${idx}`;
855
- lines.push(' __effect(() => {');
854
+ lines.push(' this.__disposers.push(__effect(() => {');
856
855
  lines.push(` const ${w.newParam} = ${getterExpr};`);
857
856
  lines.push(` if (this.${prevName} !== undefined) {`);
858
857
  lines.push(` const ${w.oldParam} = this.${prevName};`);
@@ -862,7 +861,7 @@ export function generateComponent(parseResult, options = {}) {
862
861
  }
863
862
  lines.push(' }');
864
863
  lines.push(` this.${prevName} = ${w.newParam};`);
865
- lines.push(' });');
864
+ lines.push(' }));');
866
865
  }
867
866
  }
868
867
 
@@ -875,28 +874,28 @@ export function generateComponent(parseResult, options = {}) {
875
874
  // Show effects — one __effect per ShowBinding
876
875
  for (const sb of showBindings) {
877
876
  const expr = transformExpr(sb.expression, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, constantNames, methodNames);
878
- lines.push(' __effect(() => {');
877
+ lines.push(' this.__disposers.push(__effect(() => {');
879
878
  lines.push(` this.${sb.varName}.style.display = (${expr}) ? '' : 'none';`);
880
- lines.push(' });');
879
+ lines.push(' }));');
881
880
  }
882
881
 
883
882
  // Model effects — signal → DOM (one __effect per ModelBinding)
884
883
  for (const mb of modelBindings) {
885
884
  if (mb.prop === 'checked' && mb.radioValue !== null) {
886
885
  // Radio: compare signal value to radioValue
887
- lines.push(' __effect(() => {');
886
+ lines.push(' this.__disposers.push(__effect(() => {');
888
887
  lines.push(` this.${mb.varName}.checked = (this._${mb.signal}() === '${mb.radioValue}');`);
889
- lines.push(' });');
888
+ lines.push(' }));');
890
889
  } else if (mb.prop === 'checked') {
891
890
  // Checkbox: coerce to boolean
892
- lines.push(' __effect(() => {');
891
+ lines.push(' this.__disposers.push(__effect(() => {');
893
892
  lines.push(` this.${mb.varName}.checked = !!this._${mb.signal}();`);
894
- lines.push(' });');
893
+ lines.push(' }));');
895
894
  } else {
896
895
  // Value-based (text, number, textarea, select): nullish coalesce to ''
897
- lines.push(' __effect(() => {');
896
+ lines.push(' this.__disposers.push(__effect(() => {');
898
897
  lines.push(` this.${mb.varName}.value = this._${mb.signal}() ?? '';`);
899
- lines.push(' });');
898
+ lines.push(' }));');
900
899
  }
901
900
  }
902
901
 
@@ -918,44 +917,44 @@ export function generateComponent(parseResult, options = {}) {
918
917
  for (const ab of attrBindings) {
919
918
  const expr = transformExpr(ab.expression, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, constantNames, methodNames);
920
919
  if (ab.kind === 'attr') {
921
- lines.push(' __effect(() => {');
920
+ lines.push(' this.__disposers.push(__effect(() => {');
922
921
  lines.push(` const __v = ${expr};`);
923
922
  lines.push(` if (__v || __v === '') { this.${ab.varName}.setAttribute('${ab.attr}', __v); }`);
924
923
  lines.push(` else { this.${ab.varName}.removeAttribute('${ab.attr}'); }`);
925
- lines.push(' });');
924
+ lines.push(' }));');
926
925
  } else if (ab.kind === 'bool') {
927
- lines.push(' __effect(() => {');
926
+ lines.push(' this.__disposers.push(__effect(() => {');
928
927
  lines.push(` this.${ab.varName}.${ab.attr} = !!(${expr});`);
929
- lines.push(' });');
928
+ lines.push(' }));');
930
929
  } else if (ab.kind === 'class') {
931
930
  if (ab.expression.trimStart().startsWith('{')) {
932
931
  // Object expression: iterate entries, classList.add/remove
933
- lines.push(' __effect(() => {');
932
+ lines.push(' this.__disposers.push(__effect(() => {');
934
933
  lines.push(` const __obj = ${expr};`);
935
934
  lines.push(' for (const [__k, __val] of Object.entries(__obj)) {');
936
935
  lines.push(` __val ? this.${ab.varName}.classList.add(__k) : this.${ab.varName}.classList.remove(__k);`);
937
936
  lines.push(' }');
938
- lines.push(' });');
937
+ lines.push(' }));');
939
938
  } else {
940
939
  // String expression: set className
941
- lines.push(' __effect(() => {');
940
+ lines.push(' this.__disposers.push(__effect(() => {');
942
941
  lines.push(` this.${ab.varName}.className = ${expr};`);
943
- lines.push(' });');
942
+ lines.push(' }));');
944
943
  }
945
944
  } else if (ab.kind === 'style') {
946
945
  if (ab.expression.trimStart().startsWith('{')) {
947
946
  // Object expression: iterate entries, set style[key]
948
- lines.push(' __effect(() => {');
947
+ lines.push(' this.__disposers.push(__effect(() => {');
949
948
  lines.push(` const __obj = ${expr};`);
950
949
  lines.push(' for (const [__k, __val] of Object.entries(__obj)) {');
951
950
  lines.push(` this.${ab.varName}.style[__k] = __val;`);
952
951
  lines.push(' }');
953
- lines.push(' });');
952
+ lines.push(' }));');
954
953
  } else {
955
954
  // String expression: set cssText
956
- lines.push(' __effect(() => {');
955
+ lines.push(' this.__disposers.push(__effect(() => {');
957
956
  lines.push(` this.${ab.varName}.style.cssText = ${expr};`);
958
- lines.push(' });');
957
+ lines.push(' }));');
959
958
  }
960
959
  }
961
960
  }
@@ -963,7 +962,7 @@ export function generateComponent(parseResult, options = {}) {
963
962
  // ── if effects ──
964
963
  for (const ifBlock of ifBlocks) {
965
964
  const vn = ifBlock.varName;
966
- lines.push(' __effect(() => {');
965
+ lines.push(' this.__disposers.push(__effect(() => {');
967
966
  lines.push(' let __branch = null;');
968
967
  for (let i = 0; i < ifBlock.branches.length; i++) {
969
968
  const branch = ifBlock.branches[i];
@@ -1002,7 +1001,7 @@ export function generateComponent(parseResult, options = {}) {
1002
1001
  }
1003
1002
  lines.push(' }');
1004
1003
  lines.push(` this.${vn}_active = __branch;`);
1005
- lines.push(' });');
1004
+ lines.push(' }));');
1006
1005
  }
1007
1006
 
1008
1007
  // ── each effects ──
@@ -1016,7 +1015,7 @@ export function generateComponent(parseResult, options = {}) {
1016
1015
  // Transform the source expression
1017
1016
  const sourceExpr = transformForExpr(source, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet);
1018
1017
 
1019
- lines.push(' __effect(() => {');
1018
+ lines.push(' this.__disposers.push(__effect(() => {');
1020
1019
  lines.push(` const __source = ${sourceExpr};`);
1021
1020
  lines.push('');
1022
1021
  lines.push(" const __iter = typeof __source === 'number'");
@@ -1058,7 +1057,7 @@ export function generateComponent(parseResult, options = {}) {
1058
1057
  lines.push('');
1059
1058
  lines.push(` this.${vn}_nodes = __newNodes;`);
1060
1059
  lines.push(` this.${vn}_keyMap = __newMap;`);
1061
- lines.push(' });');
1060
+ lines.push(' }));');
1062
1061
  } else {
1063
1062
  // ── Non-keyed: destroy all and recreate (original behavior) ──
1064
1063
  lines.push(` for (const n of this.${vn}_nodes) n.remove();`);
@@ -1073,7 +1072,7 @@ export function generateComponent(parseResult, options = {}) {
1073
1072
  lines.push(` this.${vn}_anchor.parentNode.insertBefore(node, this.${vn}_anchor);`);
1074
1073
  lines.push(` this.${vn}_nodes.push(node);`);
1075
1074
  lines.push(' });');
1076
- lines.push(' });');
1075
+ lines.push(' }));');
1077
1076
  }
1078
1077
  }
1079
1078
 
@@ -1098,10 +1097,11 @@ export function generateComponent(parseResult, options = {}) {
1098
1097
  lines.push(' }');
1099
1098
  lines.push('');
1100
1099
 
1101
- // disconnectedCallback (cleanup: abort listeners + user hooks)
1100
+ // disconnectedCallback (cleanup: abort listeners + dispose effects + user hooks)
1102
1101
  lines.push(' disconnectedCallback() {');
1103
1102
  lines.push(' this.__connected = false;');
1104
1103
  lines.push(' this.__ac.abort();');
1104
+ lines.push(' this.__disposers.forEach(d => d());');
1105
1105
  if (onDestroyHooks.length > 0) {
1106
1106
  for (const hook of onDestroyHooks) {
1107
1107
  const body = transformMethodBody(hook.body, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, refVarNames, constantNames);
@@ -64,7 +64,9 @@ function __computed(fn) {
64
64
 
65
65
  function __effect(fn) {
66
66
  let _cleanup = null;
67
+ let _active = true;
67
68
  const run = () => {
69
+ if (!_active) return;
68
70
  if (typeof _cleanup === 'function') _cleanup();
69
71
  const prev = __currentEffect;
70
72
  __currentEffect = run;
@@ -72,6 +74,7 @@ function __effect(fn) {
72
74
  __currentEffect = prev;
73
75
  };
74
76
  run();
77
+ return () => { _active = false; if (typeof _cleanup === 'function') _cleanup(); };
75
78
  }
76
79
 
77
80
  function __batch(fn) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sprlab/wccompiler",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Zero-runtime compiler that transforms .wcc single-file components into native web components with signals-based reactivity",
5
5
  "type": "module",
6
6
  "bin": {